Monday, May 21, 2012

Spdy/3 Push

‹prev | My Chain | next›

I have not had any brain waves since yesterday's failure to get SPDY server push working with version 3 of the protocol's specification. It is disappointing given that I was able to add support for it to node-spdy back when spdy/2 was the latest version and since I have been able to support the new spdy/3 flow control in node-spdy. I had hoped that SPDY server push would just work. Alas.

Before soliciting others for advice, I would like to throw up a sample site that others might use to debug. Since www.spdybook.com is already spdy/3 powered, that seems like a good place to do it.

So I go about copying over the views, images and stylesheets that make up this awesome, "real" world site:


Next, I need to copy in the express.js routes. I add one for regular access (/real) and one for the currently broken push (/push):
app.get('/real', function(req, res){
  res.render('real', {
    title: 'Hello (real) World!'
  });
});


app.get('/push', function(req, res){
  // do push stuff

  res.render('real', {
    title: 'Hello (real) World!'
  });
});
Say... while looking at the "// do push stuff" section, I notice that I never tried a colon header for status:
app.get('/push', function(req, res){
  var headers = {
    ':scheme': 'https',
    ':host': 'localhost:3000',
    // ':path': '/stylesheets/style.css',
    ':path': '/images/00-h.jpg'
    // 'content-type': 'image/jpeg'
    // status: 200,
    // version: 'http/1.1',
    // url: url,
    // ':method': 'GET',
    // ':version': 'HTTP/1.1',
    // ':last-modified': (new Date).toGMTString()
  };
  // ...
});
Odd, I thought I had tried every combination. It couldn't be that simple, could it?

YUP!

Updating the headers to include a spdy/3 colon header like so:
app.get('/push', function(req, res){
  var headers = {
    ':scheme': 'https',
    ':host': req.headers.host,
    ':status': 200,
    ':version': 'HTTP/1.1',
    ':path': '/images/00-h.jpg'
  };

  res.push('/images/00-h.jpg', {}, function(err, stream) {
    fs.createReadStream('public/images/00-h.jpg').pipe(stream);
  });
  // ...
});
Does the trick. In Chrome's SPDY tab (under chrome://net-internals), I see the SPDY session, the push packets, the adopted SYN_STREAM and, most importantly, no "invalid" stream error:
SPDY_SESSION_PUSHED_SYN_STREAM
--> associated_stream = 1
--> flags = 2
--> :host: localhost:4000
    :path: /images/00-h.jpg
    :scheme: https
    :status: 200
    :version: HTTP/1.1
--> id = 2

SPDY_SESSION_RECV_DATA
--> flags = 0
--> size = 1300
--> stream_id = 2
SPDY_SESSION_RECV_DATA
--> flags = 0
--> size = 1300
--> stream_id = 2
SPDY_SESSION_RECV_DATA
--> flags = 0
--> size = 1300
--> stream_id = 2
SPDY_SESSION_RECV_DATA
--> flags = 0
--> size = 1300
--> stream_id = 2
SPDY_SESSION_RECV_DATA
--> flags = 0
--> size = 1300
--> stream_id = 2
SPDY_SESSION_RECV_DATA
--> flags = 0
--> size = 1300
--> stream_id = 2
SPDY_SESSION_RECV_DATA
--> flags = 0
--> size = 346
--> stream_id = 2
SPDY_SESSION_RECV_DATA
--> flags = 0
--> size = 0
--> stream_id = 2

SPDY_STREAM_ADOPTED_PUSH_STREAM
I had truly despaired that I would not get that working. Turns out I only needed fresh eyes.

I push those two routes out to the SPDY Book marketing site. If you would like to try them out, they are at:Before calling it a night, I make pretty graphs with the Speed Tracer plugin for Chrome. The "real world" site:


From the start of the graph a 2.45s (I really wish Speed Tracer could normalize to 0 seconds) until the last image is transferred past the 3.16 second mark is roughly 0.7 seconds. I should probably add more images and make them a little larger to make my "real world" example more real world. Still that 0.7 seconds is nothing to sneeze at.

How about with push enabled?


From the start at the 9.32 mark until the last image is pushed into browser cache at roughly 9.62 seconds means that SPDY push moved the same already very fast site in 0.3 seconds. Nice!


Day #393

No comments:

Post a Comment