Tuesday, June 7, 2011

Driving SPDY Server Push

‹prev | My Chain | next›

Last night, I hacked together an integration test for SPDY server push in node-spdy. Today I would like to use that (and the non-push integration test) to drive an implementation that works.

First up, I try including a "push" callback among the server options:
    options = {
push: function(req, c, createPushStream) {
var push_stream = createPushStream(req, c, "http://example.com/foo");
push_stream.write("push data");
push_stream.end();
},
//...
};
Those options are fed into the createServer function in the vows.js topic:
vows.describe('SPDY/basic test').addBatch({
'spdy.createServer': {
topic: function() {
return spdy.createServer(options);
}
The request, connect middleware object and createPushStream function passed in feel heavy handed. An arity of three is never a good thing. Still, it is driven by working code:
Response.prototype._push = function(filename, url) {
var push_stream = createPushStream(this.cframe, this.c, url);

push_stream.write(fs.readFileSync(filename));

// TODO: Combine FIN & write? (once push stream is working)
push_stream.end();
};
So maybe that will be ok...?

At any rate, I implement with:

Response.prototype._pushStuff = function() {
if (this._pushed) return false;
if (this.streamID != 1) return false;

this.pushStuff(this.cframe, this.c, createPushStream);
// this._push("pub/style.css", "https://localhost:8081/style.css");
// this._push("pub/spdy.jpg", "https://localhost:8081/spdy.jpg");

this._pushed = true;
};
If the options do not include a push callback, then an empty anonymous function is called:
var Response = exports.Response = function(cframe, c) {
//...
this.pushStuff = function() {};
//...
};
With that, I have both integration tests (push and non-push) passing. Yay!

But that callback with three parameters is bugging me. I would much rather have something that is closer to the now commented out _push method:
  // this._push("pub/style.css", "https://localhost:8081/style.css");
// this._push("pub/spdy.jpg", "https://localhost:8081/spdy.jpg");
Hrm... I wonder if I call the push callback with just the response object, might that work? It is worth a try, if nothing else. In the Response class, I switch up the push callback to:
Response.prototype._pushStuff = function() {
if (this._pushed) return false;
if (this.streamID != 1) return false;

this.pushStuff(this);

this._pushed = true;
};
An arity of one is much nicer. But how would the callback now look? Actually not too bad:
      push: function(pusher) {
pusher.push("pub/style.css", "http://example.com/foo");
},
Oooh. Both the callback and the callback context got simpler. I like that.

With the integration tests still passing, I would like to try this out with Chrome for a quick sanity check. I copy test/spdy-server.js to test/spdy-push-server.js and change the ports from 8081 to 8082 (I may want to run them side-by-side):
server.listen(8082, function() {
console.log('TLS NPN Server is running on port : %d', 8082);
});
I add the appropriate push callback as well and start up the server:
➜  node-spdy git:(push-stream) ✗ node test/spdy-push-server.js
TLS NPN Server is running on port : 8082
Nice. That seems to work. The web page shows when I load it up, but...

...checking it out in the SPDY tab of about:net-internals, I see:
t=1307502498585 [st= 42]     SPDY_SESSION_PUSHED_SYN_STREAM  
--> associated_stream = 1
--> flags = 2
--> status: 200
url: https://localhost:8081/style.css
version: http/1.1
--> id = 2
t=1307502498585 [st= 42] SPDY_SESSION_SEND_RST_STREAM
--> status = 3
--> stream_id = 2
Bah! Something is not working there.

Hey wait a second, why is that URL being pushed out with a port of 8081? Heh. That would do it.

After switching everything in test/spdy-push-server.js to 8082, I reload and see a right proper SPDY session in the SPDY tab:
#####
# Push the CSS
t=1307503216226 [st= 42] SPDY_SESSION_PUSHED_SYN_STREAM
--> associated_stream = 1
--> flags = 2
--> status: 200
url: https://localhost:8082/style.css
version: http/1.1
--> id = 2

# CSS data here...

#####
# Push the background JPEG
t=1307503216236 [st= 52] SPDY_SESSION_PUSHED_SYN_STREAM
--> associated_stream = 1
--> flags = 2
--> status: 200
url: https://localhost:8082/spdy.jpg
version: http/1.1
--> id = 4


# Image data here...


#####
# It's legit!
t=1307503216250 [st= 66] SPDY_STREAM_ADOPTED_PUSH_STREAM
Nice. But...

...dang. Now that I think about it, there was another reason to include the request in the push callback—it can be used to conditionally push streams.

I will add that back in tomorrow and also take some time to review my work.


Day #43

No comments:

Post a Comment