Monday, July 4, 2011

A Comparison of Post-Response SPDY Push Techniques

‹prev | My Chain | next›

The SPDY protocol boasts a pretty killer feature in server push. In SPDY, server push is meant to push resources directly into the browser's cache.

Being an async protocol, it is possible to push resources before and after a response. Resources pushed before a response are mostly used to supplement the page being requested (e.g. images and stylesheets needed to properly render the page). Resources pushed after a response are mostly used to anticipate subsequent requests (e.g. an about page or Javascript templates).

For the past week or so, I have been experimenting with post-response push. Last night I played with an interesting hybrid approach to post-response push in which headers for the pushed data are sent before the response, but the data is sent after the response is closed.

I think that this approach is a sort of best of both worlds. If I push the headers of a stylesheet before the response, then the browser should know that it does not need to request the resource. In effect, it pre-caches the data. So first up tonight, I verify that my presumption is correct.

In express-spdy, where before I had been pushing the stylesheet before the response (and post-response push data):
var app = module.exports = express.createServer({
// ...
push: function(pusher) {
// Only push in response to the first request
if (pusher.streamID > 1) return;

pusher.push_file("public/stylesheets/style.css", "https://localhost:3000/stylesheets/style.css");

// push later
return [["public/one.html", "https://localhost:3000/one.html"],
["public/two.html", "https://localhost:3000/two.html"],
["public/three.html", "https://localhost:3000/three.html"]];

}
});
I now push the stylesheet as the first post-response push stream:
var app = module.exports = express.createServer({
// ...
push: function(pusher) {
// Only push in response to the first request
if (pusher.streamID > 1) return;

// push later
return [["public/stylesheets/style.css", "https://localhost:3000/stylesheets/style.css"],
["public/one.html", "https://localhost:3000/one.html"],
["public/two.html", "https://localhost:3000/two.html"],
["public/three.html", "https://localhost:3000/three.html"]];

}
});
What I am looking for in the SPDY tab of Chrome's about:net-internals is stylesheet headers being sent before the web page response and the absence of an explicit request from Chrome for the stylesheet (which is linked by the web page response).

Happily, that is just what happens:
#####
# Initial page request
t=1309808069393 [st= 0] SPDY_SESSION_SYN_STREAM
--> flags = 1
--> accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
accept-charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
accept-encoding: gzip,deflate,sdch
accept-language: en-US,en;q=0.8
host: localhost:3000
method: GET
scheme: https
url: /
user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.797.0 Safari/535.1
version: HTTP/1.1
--> id = 1

#####
# Response headers (no data)
t=1309808069483 [st= 90] SPDY_SESSION_SYN_REPLY
--> flags = 0
--> connection: keep-alive
content-length: 303
content-type: text/html
status: 200 OK
version: HTTP/1.1
x-powered-by: Express
--> id = 1

######
# Pushed CSS *headers* only
t=1309808069486 [st= 93] SPDY_SESSION_PUSHED_SYN_STREAM
--> associated_stream = 1
--> flags = 2
--> status: 200
url: https://localhost:3000/stylesheets/style.css
version: http/1.1
--> id = 2


# Other pushed headers

#####
# Web page data and FIN
t=1309808069505 [st=112] SPDY_SESSION_RECV_DATA
--> flags = 0
--> size = 303
--> stream_id = 1
t=1309808069505 [st=112] SPDY_SESSION_RECV_DATA
--> flags = 0
--> size = 0
--> stream_id = 1

#####
# CSS Data Finally pushed
t=1309808069505 [st=112] SPDY_SESSION_RECV_DATA
--> flags = 0
--> size = 110
--> stream_id = 2
t=1309808069505 [st=112] SPDY_SESSION_RECV_DATA
--> flags = 0
--> size = 0
--> stream_id = 2

# Other pushed data

#####
# It's a legit SPDY push!
t=1309808069517 [st=124] SPDY_STREAM_ADOPTED_PUSH_STREAM

# /favicon.ico request / reply
Nice, just as I had hoped. The CSS headers are sent before the response data (and FIN), which is enough for Chrome to not request after it parses the page.

So I am left to wonder what are the relative merits of the two post-response pushes?

I suppose that it would not be wise to send out bunches of post-response headers ahead of the response data. At some point, the simple calculation of everything being pushed would have to impact things. But, is there any noticeable difference in my simple app?

To begin to answer these questions, I fall back to time-sequence diagrams of the packets being sent back from the server. The diagram for the packets above look like:



The response occurs just after sequence 1500. And just prior to the response, you can make out the very tiny SYN_STREAMs for the to-be-pushed-later web pages. Not a very large impact at all.

Taking a look at the same thing, but with the headers and data after the response (but before the response FIN), I see:



Not much difference at all between the two. If you enlarge the graphs and squint real hard, you can see a few more packets sent out ahead of the first one (the one in which the push SYN_STREAMs start prior to the response). But all-in-all, they are so very similar that I believe I can choose whichever is simplest to code up and have very little impact on the performance.

I think tomorrow I will play around with adding copious amounts of lorem ipsum data to the web pages. Each of them is very, very small. Perhaps if I had larger pages it would be easier to see a difference. I am also interested in learning how to muck with the round-trip time. This seems like a worthy opportunity.


Day #65

No comments:

Post a Comment