Saturday, April 21, 2012

SPDY Ping

‹prev | My Chain | next›

I would very much like to move on to exploring flow control in SPDY, but first feel like it is a good idea to address a small bug in express-spdy. Express-spdy will be going away very soon since recent versions of node-spdy proper support express.js integration out of the box. But that will not be ready until the next node.js stable release. Since that may not be in time for the impending 1.1 edition of SPDY Book, I should get my express-spdy house in order.

The particular bug is that, after the initial SPDY connection goes through, the follow error occurs:
SPDY_SESSION_PING
--> type = "sent"
--> unique_id = 1
SPDY_SESSION_CLOSE
--> description = "Failed ping."
--> status = -352
SPDY_SESSION_POOL_REMOVE_SESSION
--> source_dependency = 683722 (SPDY_SESSION)
The browser sends a PING to the server, but the server fails to respond. In fact, the server is incapable of responding because the older version of node-spdy being used is PING-ignorant. Naturally, this all works in the recent node-spdy, but that does not help.

In the core SPDY server, the SPDY connection is piped through to an instance of a SPDY parser. I add a check for PINGs to the end of that parser:
// ...
    c.pipe(parser);

    parser.on('cframe', function(cframe) {
      if (cframe.headers.type == enums.SYN_STREAM) {
        // Do SYN_STREAM stuff...
      } else if (cframe.headers.type == enums.RST_STREAM) {
        // Do RST_STREAM stuff ...
      } else if (cframe.headers.type == enums.PING) {
        console.log("PINGed!");
      }
    });
// ...
After reloading the server and refreshing the browser, I see that the server does recognize this PING:
➜  express-spdy-old  node app
Express server listening on port 3000 in development mode
PINGed!
The old node-spdy defined methods like createRstFrame and createSettingsFrame but, since it was PING ignorant, there is not a corresponding createPingFrame. I will eventually create one, but for now, I use the lower level createControlFrame() method to respond to the first PING:

      // ...
      } else if (cframe.headers.type == enums.PING) {
        console.log("PINGed! " + cframe.data.streamID);
        var streamID = 1;

        c.write(createControlFrame(c.zlib, {
          type: enums.PING
        }, new Buffer([
          (streamID >> 24) & 255, (streamID >> 16) & 255,
          (streamID >> 8) & 255, streamID & 255
        ])));
      }
      // ...
That does the trick as the browser now gets back its desired PING:
SPDY_SESSION_RECV_DATA
--> flags = 0
--> size = 0
--> stream_id = 3
SPDY_SESSION_PING
--> type = "sent"
--> unique_id = 1
SPDY_SESSION_PING
--> type = "received"
--> unique_id = 1
And, more importantly, the connection remains open:


The only problem is that I have hard-coded the SPDY session ID to one. The parser seems unable to extract the ID from the PING request:
➜  express-spdy-old  node app
Express server listening on port 3000 in development mode
PINGed! undefined
This may be due to PING using a 32 bit ID. I will pick back up here tomorrow and hopefully close the book on the old express-spdy.

Update: The cause had nothing to do with the size of the ID and everything to do with not parsing PING packets—the old node-spdy simplely did not do it. So I add that capability:
Parser.prototype.parse = function() {
  // ...
  if (headers.c) {
    if (headers.type === enums.SYN_STREAM) { /* ... */ }
    if (headers.type === enums.SYN_REPLY) { /* ... */ }
    if (headers.type === enums.RST_STREAM) { /* ... */ }
    if (headers.type === enums.PING) {
      var parsed = {
        streamID: data.readUInt32BE(0) & 0xffffffff
      };
      data = parsed;
    }
    // ...
  }

  this.emit(headers.c ? 'cframe' : 'dframe', {
    headers: headers,
    data: data
  });
  // ...
};
With that, not only to I see the initial stream ID of "1", but I also see the correct stream ID for subsequent PINGs:
➜  express-spdy-old  node app
Express server listening on port 3000 in development mode
PINGed! 1
PINGed! 3
...

Day #263

No comments:

Post a Comment