Monday, July 18, 2011

BDD: Book Driven Development

‹prev | My Chain | next›

While working through one of the chapters on SPDY server push in SPDY Book, I think I finally settled on the API that I want for this sort of thing in express-spdy.

This is how I'd like it to read:
var app = module.exports = express.createServer({
key: fs.readFileSync(__dirname + '/keys/spdy-key.pem'),
cert: fs.readFileSync(__dirname + '/keys/spdy-cert.pem'),
ca: fs.readFileSync(__dirname + '/keys/spdy-csr.pem'),
NPNProtocols: ['spdy/2'],
push: simplePush
});
For the push callback, I would like something like:
function simplePush(pusher) {
// Only push in response to the first request
if (pusher.streamID > 1) return;

// Push CSS data out before the web page response
pusher.pushFile("public/stylesheets/style.css", "https://localhost:3000/stylesheets/style.css");

// Push secondary web pages out after the web page response
pusher.pushLater([
["public/one.html", "https://localhost:3000/one.html"],
["public/two.html", "https://localhost:3000/two.html"],
["public/three.html", "https://localhost:3000/three.html"]
]);
}
Essentially, I want the callback to instruct the response what to push before the response (pushFirst) and what to push after the response (pushLater). I think I would like to make push an alias for pushLater because, more often than not, pushing resources after the response has been sent is the right thing to do.

The pushFirst method is already in place. I just need to get pushLater working.

To do so, I loop over each resource passed into pushLater() and initialize a push-stream:
Response.prototype.pushLater = function(resources) {
var that = this;

this.deferred_streams = [];

// Send headers for each post-response server push stream, but DO
// NOT sent data yet
resources.forEach(function(push_contents) {
var filename = push_contents[0]
, url = push_contents[1]
, data = fs.readFileSync(filename)
, push_stream = createPushStream(that.cframe, that.c, url);

push_stream._flushHead();
push_stream._written = true;
that.deferred_streams.push([push_stream, data]);
});
};
I only flush the header of the push stream to the browser. The data gets squirreled away, along with a reference to the push streams so that the data can be sent after the original response is handled. The push stream headers need to be sent before the original response is closed, but the data can be deferred until later.

Along with pushLater, I create a _pushDataLater which pushes the actual data after the response is sent:
Response.prototype._pushLaterData = function(resources) {
if (typeof(this.deferred_streams) == 'undefined') return;

this.deferred_streams.forEach(function(stream_and_data) {
var stream = stream_and_data[0]
, data = stream_and_data[1];

stream.write(data);
stream.end();
});
};
And it works! Almost.

When I access the site, the secondary web pages are pushed. So is the CSS:
t=1311041561541 [st=  197]     SPDY_SESSION_PUSHED_SYN_STREAM  
--> associated_stream = 1
--> flags = 2
--> content-type: text/css
status: 200
url: https://localhost:3000/stylesheets/style.css
version: http/1.1
--> id = 2
...
t=1311041561841 [st= 497] SPDY_SESSION_RECV_DATA
--> flags = 0
--> size = 254
--> stream_id = 2
t=1311041561841 [st= 497] SPDY_SESSION_RECV_DATA
--> flags = 0
--> size = 0
--> stream_id = 2
...
But, when I request the next page on the site, which uses the same CSS, it is requesting the CSS again:
...
t=1311041603258 [st=41914] SPDY_SESSION_SYN_STREAM
--> flags = 1
--> accept: text/css,*/*;q=0.1
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
referer: https://localhost:3000/one.html
scheme: https
url: /stylesheets/style.css
user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.814.0 Safari/535.1
version: HTTP/1.1
--> id = 5
....
It is not as if server push does not work—there was no request for this HTML page. It is also not as if the CSS server push is not entirely working—when requesting the first page, there is no CSS request. Maybe this version of Chrome is buggy with respect to CSS and caching. More likely I am just missing something.

I will investigate more. Tomorrow.


Day #78

No comments:

Post a Comment