Friday, April 27, 2012

Getting Started with SPDY/3 Flow Control

‹prev | My Chain | next›

I had thought to mess about with spdy/2 and spdy/3 side-by-side some more, but I seem to have hit a snag. I disable spdy/3 in Chrome's about:flags:


But, when I reload my express.js site, powered by node-spdy, I am still using spdy/3:


That's what I get for mucking with experimental flags.

No matter how many times I try flipping that bit and restarting the browser, I continue to get spdy/3 connections. Hopefully that will get fixed in some future Chrome update.

Anyhow, flow control.

The SPDY v3 specifiction introduces flow control in the form of a WINDOW_UPDATE control frame.

In node-spdy parlance, this will look something like:
//
// ### function windowUpdateFrame (id)
// #### @id {Buffer} WindowUpdate ID
// Sends WINDOW_UPDATE frame
//
Framer.prototype.windowUpdateFrame = function windowUpdateFrame(id, delta) {
  var header = new Buffer(16);

  header.writeUInt32BE(0x80030009, 0, true); // Version and type
  header.writeUInt32BE(0x00000008, 4, true); // Length
  header.writeUInt32BE(id & 0x7fffffff, 8, true); // ID
  header.writeUInt32BE(delta & 0x7fffffff, 12, true); // delta

  return header;
};
The first four octets are version and type, the second 4 are the length (which is always 8 for WINDOW_UPDATE), the next four are for the stream ID, and the last four are the amount of data that can be sent beyond the initial window size (established in a SETTINGS frame). Actually, take my definition of delta with a grain of salt—I really do not have a firm grasp on the concept yet, which is why I am experimenting.

I think my first experiment will cause protocol errors. Even so, it seems the smallest step I can take. I am going to send a WINDOW_UPDATE immediately before my first SYN_REPLY. I think that this is wrong because the recipient of the data (the browser) should be sending WINDOW_UPDATEs per the spec, not the server. If I get a protocol error, then I can hope that I somewhat understand this and can build on it tomorrow. If I do not get a protocol error, then I can go back to the drawing board.

So, back in the server, I manually write a WINDOW_UPDATE after initializing the first SPDY stream:
function Connection(socket, pool, options) {
  // ...
  this.parser.on('frame', function (frame) {
    // ...
    // Create new stream
    if (frame.type === 'SYN_STREAM') {
      // ...
      if (frame.id == 1) {
        self.write(self.framer.windowUpdateFrame(frame.id, 8));
      }
      // ...
    }
    // ...
  }
  // ...
}
I specify a window size of 8 bytes. That is intentionally small so that it might have a noticeable effect should it be accepted.

So I restart the server, reload the page and...
SPDY_SESSION_SYN_STREAM
--> flags = 1
--> :host: localhost:3000
    :method: GET
    :path: /
    :scheme: https
    :version: HTTP/1.1
    ....
--> id = 1
SPDY_SESSION_RECV_SETTING
--> flags = 1
--> id = 4
--> value = 100
SPDY_SESSION_RECEIVED_WINDOW_UPDATE
--> delta = 8
--> stream_id = 1
The browser gets, and seemingly accepts, my WINDOW_UPDATE of a delta of 8 bytes. The rest of the SPDY conversation takes place as if this frame were not present. Specifically, the server issues a SYN_REPLY with more than 8 bytes in the DATA frame and no errors are raised:
SPDY_SESSION_SYN_REPLY
--> flags = 0
--> :status: 200
    :version: HTTP/1.1
--> id = 1
SPDY_SESSION_RECV_DATA 
--> flags = 0
--> size = 339
--> stream_id = 1
SPDY_SESSION_RECV_DATA
--> flags = 0
--> size = 0
--> stream_id = 1
I am not surprised that the SYN_REPLY and subsequent DATA are unchanged—I have not done anything in the server to alter their behavior. I am also not surprised that the browser does not barf on receiving them—the WINDOW_UPDATE is sent by the recipient, but, in this case, the recipient is the browser which sent no WINDOW_UPDATE and should therefore be unaffected.

The only thing that surprises me is that the client accepted the WINDOW_UPDATE in the first place. Then again, SPDY conversations are bi-directional, so maybe that should not surprise me. Technically, the server could be a recipient of DATA (e.g. a POST body).

Satisfied that, if nothing else, my WINDOW_UPDATE is crafted well, I call it a night here. Tomorrow, I plan to start by POSTing data after a WINDOW_UPDATE to see if it has any effect. Then the fun begins...


Day #369

No comments:

Post a Comment