Saturday, May 5, 2012

Parsing SPDY SETTINGS in Node-Spdy

‹prev | My Chain | next›

I believe that I have a more or less robust control flow implementation in the spdy-v3 branch of node-spdy. Aside from some questionable variable naming, a quick review yesterday makes me think that I have got a reasonable implementation, that should not leak memory or resources, and approximately follows existing coding conventions.

I even think that it will handle the race condition mentioned in version 3 of the spec. Well… almost.

As the spec says, it is possible for the server to send 64kb of data, the entire data transfer window, before the browser has a chance to limit the transfer window to something smaller. In such a case, node-spdy should internally lower its transfer window setting and wait until the client has processed both the excess data already sent and is ready to handle the next batch (which it would signal via WINDOW_UPDATE).

And all that should work except that the internal data transfer window size is currently hard coded in node-spdy. Oh, and SETTINGS are currently ignored by node-spdy:
      // Ignore SETTINGS for now
      } else if (frame.type === 'SETTINGS' || frame.type === 'NOOP') {
        // TODO: Handle something?
      } else if (frame.type === 'GOAWAY') {
      // ...
Since NOOP has been removed from the version 3 specification, I convert that line to actually handle something. For the time being, I only handle setting the transfer window size:
      } else if (frame.type === 'SETTINGS') {
        self.setDefaultTransferWindow(frame.settings);
      } else if (frame.type === 'GOAWAY') {
      // ...
I tracer-bullet the setDefaultTransferWindow() method for now:
// TODO: Document
Connection.prototype.setDefaultTransferWindow = function(settings) {
  console.log('[setDefaultTransferWindow] !!!');
  console.log(settings);
};
Instead, I need to work my way down in to the Parser class so that I can teach it how to parse SETTINGS frames:
protocol.parseSettings = function parseSettings(data) {
  var settings = {},
      number = data.readUInt32BE(0, true);

  for (var i=0; i<number; i++) {
    var id = data.readUInt32BE(4 + (i*8), true) & 0x00ffffff;
    settings[id] = data.readUInt32BE(8 + (i*8), true);
  }

  return {
    type: 'SETTINGS',
    settings: settings
  };
};
Given that a SETTINGS frame body starts with the number of entries, parseSettings() grabs that from the first 32 bit integer. The remainder of the SETTINGS FRAME is key-value pairs (with a flag that I ignore):
+----------------------------------+
 | Flags(8) |      ID (24 bits)     |
 +----------------------------------+
 |          Value (32 bits)         |
 +----------------------------------+
The flags are mostly useful for the client to indicate whether the values should be persisted or are persisted. In fact, when node-spdy generates SETTINGS frames, it instructs the client that SETTINGS should be persisted:
    // ...
    settings.writeUInt32BE(0x01000004, 12, true); // Entry ID and Persist flag
    settings.writeUInt32BE(count, 16, true); // 100 Streams

    settings.writeUInt32BE(0x01000007, 20, true); // Entry ID and Persist flag
    settings.writeUInt32BE(1024 & 0x7fffffff, 24, true); // Window Size (KB)
    // ...
If I have built the parse correctly, those persisted values should be sent back to the server and, thanks to the tracer bullets, I see that they are:
➜  express-spdy-test  node app
Express server listening on port 3000 in development mode
[setDefaultTransferWindow] !!!
{ '4': 100, '7': 1024 }
With my SETTINGS parse working, I call it a night. I will pick back up tomorrow with an implementation for varying the data window size.


Day #377

No comments:

Post a Comment