Thursday, June 30, 2011

Firefox CWND

‹prev | My Chain | next›

Up today, I release the beta version of SPDY Book! But the gods of my chain must first be appeased. Today is not the day to draw their ire.

So I take a break from my recent deep-in-the-bowels coding of node-spdy to play a bit with, of all things, Firefox. The latest version of Firefox boasts sorting of open HTTP pipes by TCP/IP congestion window (CWND). The CWND is the amount of data a TCP/IP connection will accept before an acknowledgement is sent. The CWND starts small, but increases quickly as the TCP/IP tubes are warmed by fresh data flowing through them.

Browsers open up 6 TCP/IP connections to a server. Sorting them by CWND does not address nearly the breadth of what SPDY is meant for, but it still might be a nice performance boost.

The test page put out by Patrick McManus includes 2 very large images and several smaller images. Loading this page should open up two interweb tubes for the large images and several other tubes for the small images.

By the the time the page is done loading the large images, the two tubes used to download those images should have very large CWNDs. The tubes that transport only tiny images will not have enough data flow through them to warm them (and increase their CWNDs). If Firefox truly does sort tubes by CWND, another click on the site should use one of those tubes.

To the Wiresharks!

I am only interested in which tubes are used for which HTTP requests, so I use Wireshark filters to only show packets with HTTP requests or responses in them. I then move through each tube (Wireshark calls them streams for some reason) in turn increasing the tube in in the filter expression: "(http.request or http.response) and tcp.stream eq 0".

The first tube contains, as expected the web page itself and one of the small images:



The second tube contains just a small image:



The third tube also holds only a small image.

On the fourth tube, I hit the jackpot:



It is the tube that downloaded the first large image and, as promised, it is also used by Firefox to download the secondary request that I made (a PNG) after the homepage and big images had finished loading.

Nice! That is a clever little change from the Firefox devs that did not require much code change, does not muck with TCP/IP, but ought to give users a nice performance boost. Kudos Firefox devs!

Day #62

Wednesday, June 29, 2011

Ugly, But SPDY, Vows.js Hacks

‹prev | My Chain | next›

Up today, I continue my efforts to write vows.js tests for the SPDY push streams in node-spdy.

It took a bit of doing, but yesterday I was able to describe one part of SPDY server push:
➜  node-spdy git:(post-response-push) vows ./test/response-test.js --spec

♢ Pushing Additional Resources

A response with a push_stream
✓ should push data on write

✓ OK » 1 honored (0.003s)
That vow covers a "normal" server push—pushing resources into browser cache before SPDY responds with the requested resource. I have been experimenting with pushing resources after the response (e.g. secondary web pages that the user is likely to request).

I am not terribly happy with my implementation of post-response server push in node-spdy so far (which is part of the reason I am writing vows). For each Response, I am adding a post-response callback via a method named setAfterResponsePush(). Adapting the pre-response push vow, I can write the post-response vow as:
    'with a push_after_stream': {
topic: function (response) {
var callback = this.callback;
response.setAfterResponsePush(function(pusher) {callback(null, pusher)});
response._write();
},
'should push data on write': function (pusher) {
assert.equal(pusher, response);
}
}
With that, I have two honored vows:
➜  node-spdy git:(post-response-push) ✗ vows ./test/response-test.js --spec

♢ Pushing Additional Resources

A response with a push_stream
✓ should push data on write
A response with a push_after_stream
✓ should push data on write


✓ OK » 2 honored (0.004s)
My API for post-response push may change, but that should just require a change to the topic of my vows.

Since I am writing vows after the code (I know, I know), I need to verify that the test covers what I actually think is does. So I comment out my setAfterResponsePush() call:
      topic: function (response) {
var callback = this.callback;
// response.setAfterResponsePush(function(pusher) {callback(null, pusher)});
response._write();
}
Re-running my vows, I do get a failure:
➜  node-spdy git:(post-response-push) ✗ vows ./test/response-test.js --spec

♢ Pushing Additional Resources

A response with a push_stream
✓ should push data on write

✗ Errored » callback not fired
in A response with a push_after_stream
in Pushing Additional Resources
in test/response-test.js

✗ Errored » 1 honored ∙ 1 errored ∙ 1 dropped
Cool. So at least my test is covering what I think it is.

Last up today, I would like to verify that the the post-response push occurs... well, after the response.

I have no idea how to do something like that without resorting to bad hacks. So that's what I do. I create a global test variable named last_called to hold the name of the last called function. If the response is written by the connetion object, then the last_called will be 'connection.write':
    connection = {
zlib: spdy.createZLib(),
write: function() {last_called = 'connection.write';}
}
If the post-response callback is called last, the value of last_called will be 'setAfterResponsePush':
      topic: function (response) {
var callback = this.callback;
response.setAfterResponsePush(function(pusher) {
last_called = 'setAfterResponsePush';
callback(null, pusher);
});
response._write();
}
Wow, do I feel dirty about that, but with the following vow:
      'should push _after_ write': function() {
assert.equal(last_called, 'setAfterResponsePush');
}
It works:
➜  node-spdy git:(post-response-push) ✗ vows ./test/response-test.js --spec

♢ Pushing Additional Resources

A response with a push_stream
✓ should push data on write
A response with a push_after_stream
✓ should push data on write
✓ should push _after_ write

✓ OK » 3 honored (0.004s)
The problem with globals is, of course, that they are global. I have the feeling that it won't take long for this to come back to bite me, but I will defer that until tomorrow.

For now, I am satisfied that I have verified that the post-response push is tested and that the test confirms that the call comes after the response to the original request. If I can think of a better approach than setPostResponsePush I may pick this back up tomorrow. I may also put this on the back-burner and begin investigating SPDY alternatives. There is a gaping hole in the SPDY Book in need of filling.


Day #61

Tuesday, June 28, 2011

Vows to Make SPDY Push Better

‹prev | My Chain | next›

I think that I want a better API for pushing SPDY data before and after responses than:
var app = module.exports = express.createServer({
// ...
push: function(pusher) {
// Only push in response to the first request
if (pusher.streamID > 1) return;

pusher.push_file("public/stylesheets/style.css", "https://localhost:3000/stylesheets/style.css");
},
push_after: function(pusher) {
// Only push in response to the first request
if (pusher.streamID > 1) return;

pusher.push_file("public/one.html", "https://localhost:3000/one.html");
pusher.push_file("public/two.html", "https://localhost:3000/two.html");
pusher.push_file("public/three.html", "https://localhost:3000/three.html");
}
});
Ultimately, I think I would like to be able to tell the SPDY server that, for a given resource, push this data before the response and that data after the response. I think I want that, but I am not sure. What I do know is that I made a bit of a mess of the Response#write method in node-spdy.

Uncertainty and messy code. Sounds like a good time to write some tests.

Node-spdy uses vows.js for its testing, so I start on a test/response-test.js file.

There is a bit of setup for a control frame that is required for createResponse(), so I make that setup the topic of my first batch of tests:
vows.describe('Pushing Additional Resources').addBatch({
'A response': {
topic: function() {
var cframe = spdy.createControlFrame(spdy.createZLib(), {
type: spdy.enums.SYN_STREAM,
streamID: 1,
priority: 0,
flags: 0
}, {
version: 'HTTP/1.1',
url: '/',
method: 'GET'
});

var connection = {
zlib: spdy.createZLib(),
write: function() {}
};

return spdy.createResponse(cframe, connection);
}
}
}).run();
For now, I do not want to test directly on that Response object. Rather, I want to verify that my push-stream callbacks are being called. Fortunately, vows makes it easy to verify that a callback is called with the this.callback construct. If this.callback is called, its arguments become the topic of the test:
vows.describe('Pushing Additional Resources').addBatch({
'A response': {
topic: function() {
// ...
return spdy.createResponse(cframe, connection);
},

'with a push_stream call': {
topic: function (response) {
var callback = this.callback;
response.setPush(callback);
response._write('foo');
}
}

}
}).run();
Finally, I can verify not only that my push-stream callbacks are called, but are called with the Response object itself:
vows.describe('Pushing Additional Resources').addBatch({
'A response': {
topic: function() {
// ...
return spdy.createResponse(cframe, connection);
},

'with a push_stream call': {
topic: function (response) {
var callback = this.callback;
response.setPush(callback);
response._write('foo');
},
'should push data on write': function (pusher) {
assert.equal(pusher, response);
}

}
}
}).run();
Unfortunately, when I run my assembled vows, I am greeted with:
➜ node-spdy git:(post-response-push) ✗ node test/response-test.js

node.js:183
throw e; // process.nextTick error, or 'error' event on first tick
^
TypeError: Cannot read property 'streamID' of undefined
at new <anonymous> (/home/cstrom/repos/node-spdy/lib/spdy/response.js:22:30)
...
That undefined bit of code from Response is the data attribute of the cframe that I created in my original topic.

Hrmm...

Ah, it seems that cframess and dframes, at least when downstream objects receive them, are not Buffers wrapped in an object. Rather they are just objects:
  this.emit(headers.c ? 'cframe' : 'dframe', {
headers: headers,
data: data
});
That simplifies my testing a bit, but now when I run my vows, I get:
➜  node-spdy git:(post-response-push) ✗ vows test/response-test.js


A response with a push_stream call
✗ should push data on write
» An unexpected error was caught: {"cframe":{"0":128,"1":2,"2":0,"3":1,"4":0,"5":0,"6":0,"7":50,"8":0,"9":0,"10":0,"11":1,"12":0,"13":0,"14":0,"15":0,"16":189,"17":0,"18":120,"19":187,"20":223,"21":162,"22":81,"23":178,"24":98,"25":96,"26":102,"27":96,"28":135,"29":114,"30":25,"31":56,"32":96,"33":170,"34":24,"35":152,
...
"An unexpected error was caught"? What does that mean? Did I throw that object somewhere deep in the bowels of node-spdy?

Googling this error, I come across this post from a very clever author. Reading through that post leads me to ask...

How can I be the only idiot making that mistake?!

OK, so callback needs to be called with two arguments. My push-streams only callback with a single argument.
var response = spdy.createResponse(cframe, connection);

vows.describe('Pushing Additional Resources').addBatch({
'A response': {
topic: response,

'with a push_stream call': {
topic: function (response) {
var callback = this.callback;
response.setPush(function(pusher) {callback(null, pusher)});
response._write();
},
'should push data on write': function (pusher) {
assert.equal(pusher, response);
}
}
}
}).export(module);
With that, I finally have my vows in working order:
➜  node-spdy git:(post-response-push) ✗ vows test/response-test.js --spec

♢ Pushing Additional Resources

function
A response with a push_stream call
✓ should push data on write

✓ OK » 1 honored (0.003s)
I call it a day there. I have made a good faith attempt at getting started on specs for server push in node-spdy. Tomorrow, I will build on this by adding tests for post-response push.

But, for now, I'm off to write SPDY Book.


Day #60

Monday, June 27, 2011

Post Response SPDY Server Push

‹prev | My Chain | next›

I figured out a minor issue with SPDY server push and HTML pages last night. It occurs to me that pushing other HTML pages into the browser's cache before the original server request is fulfilled is a tad silly.

I makes far more sense to push out only resources needed for the page itself (e.g. images, Javascript, CSS) at the same time as the requested page. Only once all of those resources have been sent should secondary web pages get sent.

Currently, my express-spdy app is sending out three pages and a CSS stylesheet thusly:
var app = module.exports = express.createServer({
//...
push: function(pusher) {
// Only push in response to the first request
if (pusher.streamID > 1) return;

pusher.push_file("public/one.html", "https://localhost:3000/one.html");
pusher.push_file("public/two.html", "https://localhost:3000/two.html");
pusher.push_file("public/three.html", "https://localhost:3000/three.html");
pusher.push_file("public/stylesheets/style.css", "https://localhost:3000/stylesheets/style.css");

}
});
I am unsure how I would like the post-response push API to work, but the simplest thing that I can think of for experimentation is:
var app = module.exports = express.createServer({
// ...
push: function(pusher) {
// Only push in response to the first request
if (pusher.streamID > 1) return;

pusher.push_file("public/stylesheets/style.css", "https://localhost:3000/stylesheets/style.css");
},
push_after: function(pusher) {
// Only push in response to the first request
if (pusher.streamID > 1) return;

pusher.push_file("public/one.html", "https://localhost:3000/one.html");
pusher.push_file("public/two.html", "https://localhost:3000/two.html");
pusher.push_file("public/three.html", "https://localhost:3000/three.html");

}
});
The express-spdy package already supports the push callback (by way of node-spdy), so I would only need to add support for push_after. Let's see if that's easy...

In the Response class, I use a bit of indirection to invoke that push callback. It ultimately get called as part of the write() method, just after sending out headers:

Response.prototype._write = function(data, encoding, fin) {
if (!this._written) {
this._flushHead();
this._push_stream();
}
//
};
To get the desired effect, I could push out the post-response resources after the data frame has been written:
Response.prototype._write = function(data, encoding, fin) {
// ...

this.c.write(dframe);

this._push_after_response_stream();
};
But... SPDY data frames can include a FIN flag. If set, a FIN flag indicates that the stream is closed. This means that no more data can be sent on the stream and no more pushed data associated with that stream can be initiated. If the headers for a push stream had been initiated via SYN_STREAM frame prior to sending a data FIN (or just plain FIN), then the data could still be pushed. But, as that code is written, it is possible to write() a FIN data frame and then attempt to initiate a server push which is a SPDY no-no.

A quick and not-too-dirty solution would be to write the post-response push data after write(), but not in an end():
Response.prototype.write = function(data, encoding) {
var stream = this._write(data, encoding, false);

this._push_after_response_stream();

return stream;
};

Response.prototype.end = function(data, encoding) {
this.writable = false;
return this._write(data, encoding, true);
};
An end() sets the FIN flag whereas a write() does not. Unfortunately, express.js seems to favor calls to end().

So instead, I opt for an ugly hack. In the "private" _write() method, I send a normal data frame and a separate FIN data frame. In between those, I push any post-response push streams:
Response.prototype._write = function(data, encoding, fin) {
//...

// Write the data frame
var dframe = createDataFrame(this.getStreamCompressor(), {
streamID: this.streamID,
flags: 0
}, Buffer.isBuffer(data) ? data : new Buffer(data, encoding));

this.c.write(dframe);

// Push any post-response push streams
this._push_after_response_stream();

// Write the data FIN if this if fin
if (fin) {
var dfin = createDataFrame(this.getStreamCompressor(), {
streamID: this.streamID,
flags: enums.DATA_FLAG_FIN
}, new Buffer(0));
this.c.write(dfin);
}
};
That ain't pretty, but it ought to get the job done for today. Firing up the browser, I find that it does, indeed, work:
#####
# Response headers
t=1309198692649 [st= 80] SPDY_SESSION_SYN_REPLY
--> flags = 0
--> connection: keep-alive
content-length: 303
content-type: text/html
status: 200 OK
version: HTTP/1.1
x-powered-by: Express
--> id = 1

#####
# Pre-response push (CSS)
t=1309198692657 [st= 88] SPDY_SESSION_PUSHED_SYN_STREAM
--> associated_stream = 1
--> flags = 2
--> status: 200
url: https://localhost:3000/stylesheets/style.css
version: http/1.1
--> id = 2

#####
# Response data (the requested web page)
t=1309198692658 [st= 89] SPDY_SESSION_RECV_DATA
--> flags = 0
--> size = 303
--> stream_id = 1

######
# Post response push

# Post response push #1
t=1309198692658 [st= 89] SPDY_SESSION_PUSHED_SYN_STREAM
--> associated_stream = 1
--> flags = 2
--> content-type: text/html
status: 200
url: https://localhost:3000/one.html
version: http/1.1
--> id = 4

# Post response push #2
t=1309198692658 [st= 89] SPDY_SESSION_PUSHED_SYN_STREAM
--> associated_stream = 1
--> flags = 2
--> content-type: text/html
status: 200
url: https://localhost:3000/two.html
version: http/1.1
--> id = 6


# Post response push #3
t=1309198692658 [st= 89] SPDY_SESSION_PUSHED_SYN_STREAM
--> associated_stream = 1
--> flags = 2
--> content-type: text/html
status: 200
url: https://localhost:3000/three.html
version: http/1.1
--> id = 8

#####
# Finally, the data FIN for the response:
t=1309198692658 [st= 89] SPDY_SESSION_RECV_DATA
--> flags = 0
--> size = 0
--> stream_id = 1

t=1309198692666 [st= 97] SPDY_STREAM_ADOPTED_PUSH_STREAM
Checking out the failure case, I move the post-response push after the data FIN has been sent:
Response.prototype._write = function(data, encoding, fin) {
// ...

// Write the data FIN if this if fin
if (fin) {
var dfin = createDataFrame(this.getStreamCompressor(), {
streamID: this.streamID,
flags: enums.DATA_FLAG_FIN
}, new Buffer(0));
this.c.write(dfin);
}

// Push any post-response push streams
this._push_after_response_stream();
};
After trying to load this in the browser, I find that it does fail as expected:
t=1309199284990 [st= 61]     SPDY_SESSION_RECV_DATA  
--> flags = 0
--> size = 303
--> stream_id = 1
t=1309199284990 [st= 61] SPDY_SESSION_RECV_DATA
--> flags = 0
--> size = 0
--> stream_id = 1
t=1309199284990 [st= 61] SPDY_SESSION_PUSHED_SYN_STREAM
--> associated_stream = 1
--> flags = 2
--> content-type: text/html
status: 200
url: https://localhost:3000/one.html
version: http/1.1
--> id = 4
t=1309199284990 [st= 61] SPDY_SESSION_SEND_RST_STREAM
--> status = 8
--> stream_id = 4
After attempting to push on a stream that was just closed (#1), Chrome correctly sends a RST_STREAM packet informing the server that it behaving badly. Nice.

That is a good stopping point for today. I will pick back up tomorrow attempting to come up with a nice API for post-response push.

Day #59

Sunday, June 26, 2011

SPDY Push HTML

‹prev | My Chain | next›

Up today, I try to address a small issue with SPDY server push in / node-spdy. When I push out HTML and subsequently request that page, I see:



SPDY server push works by pushing resources into the browser cache before a user requests it. I am fairly certain that things go wrong here by pushing without an associated content-type. Way back when, I thought the only required fields for server push were url, HTTP status and HTTP version. It seems that there is a third.

Although...

It only seems to be required for HTML. I wonder if adding something like the following to node-spdy would do the trick:
var PushStream = exports.PushStream = function(associated_cframe, c, url) {
// ...

this._headers = {
status: 200,
version: "http/1.1",
url: url
};

if (/\.html?$/.test(url))
this._headers["content-type"] = "text/html";

};
I have already seen that images and stylesheets work just fine without a content type. Until I know that something more sophisticated is required in node-spdy, why bother adding complexity?

But first, I need to try it out. Loading up the app in Chrome and then navigating to the first pushed page, I find:



Ah, much better. Checking out the SPDY tab in Chrome's about:net-internals, I find no request for the one.html page, just the server push into cache. And the three HTML pushes have the correct content type:
t=1309109914117 [st=  62]     SPDY_SESSION_PUSHED_SYN_STREAM  
--> associated_stream = 1
--> flags = 2
--> content-type: text/html
status: 200
url: https://localhost:3000/one.html
version: http/1.1
--> id = 2
...

t=1309109914121 [st= 66] SPDY_SESSION_PUSHED_SYN_STREAM
--> associated_stream = 1
--> flags = 2
--> content-type: text/html
status: 200
url: https://localhost:3000/two.html
version: http/1.1
--> id = 4
...

t=1309109914121 [st= 66] SPDY_SESSION_PUSHED_SYN_STREAM
--> associated_stream = 1
--> flags = 2
--> content-type: text/html
status: 200
url: https://localhost:3000/three.html
version: http/1.1
--> id = 6
And the the stylesheet is sent without a content type (and still applies just fine):
t=1309109914122 [st=  67]     SPDY_SESSION_PUSHED_SYN_STREAM  
--> associated_stream = 1
--> flags = 2
--> status: 200
url: https://localhost:3000/stylesheets/style.css
version: http/1.1
--> id = 8
Cool beans. I will push that into node-spdy this afternoon and call it a day for my chain at this point. Must write more SPDY Book.


Day #59

Saturday, June 25, 2011

SPDY Server Push in express-spdy

‹prev | My Chain | next›

Having verified that multi-page sites work just fine in express-spdy, I am ready to investigate SPDY server push.

To test out navigating through multi-page SPDY sites, I added three static pages: one.html, two.html, and three.html. To test SPDY server push, I think I will try to push those pages out, along with the CSS for all three pages.

The syntax that I added to node-spdy looks like:
var options = {
// ...
push: function(pusher) {
// Only push in response to the first request
if (pusher.streamID > 1) return;

// It's also possible to conditionally respond based on the request
// var req = pusher.cframe.data.nameValues;
// if (req.url != "/") return;
// if (req.method != "GET") return;

pusher.push_file("pub/style.css", "https://localhost:8082/style.css");
pusher.push_file("pub/spdy.jpg", "https://localhost:8082/spdy.jpg");
}
};

var server = spdy.createServer(options, function(req, res) {
// ...
});
I will not fiddle with the request based push (described in the comment above) today. Instead I opt for a simpler route in my express-spdy app:
var app = module.exports = express.createServer({
//...
push: function(pusher) {
// Only push in response to the first request
if (pusher.streamID > 1) return;

pusher.push_file("public/one.html", "https://localhost:3000/one.html");
pusher.push_file("public/two.html", "https://localhost:3000/two.html");
pusher.push_file("public/three.html", "https://localhost:3000/three.html");
pusher.push_file("public/stylesheets/style.css", "https://localhost:3000/stylesheets/style.css");
}
});
When I make my first request to the express-spdy app (on stream ID #1), this should push out the three additional HTML pages along with style.css

And, checking things out in the SPDY tab of about:net-internals, I find:
# Request and SYN_REPLY for first page

...

#####
# The first pushed stream (one.html)
# Along with the associated data frames

t=1309021949666 [st= 98] SPDY_SESSION_PUSHED_SYN_STREAM
--> associated_stream = 1
--> flags = 2
--> status: 200
url: https://localhost:3000/one.html
version: http/1.1
--> id = 2
t=1309021949666 [st= 98] SPDY_SESSION_RECV_DATA
--> flags = 0
--> size = 208
--> stream_id = 2
t=1309021949666 [st= 98] SPDY_SESSION_RECV_DATA
--> flags = 0
--> size = 0
--> stream_id = 2

#####
# Second push frame (two.html)
t=1309021949667 [st= 99] SPDY_SESSION_PUSHED_SYN_STREAM
--> associated_stream = 1
--> flags = 2
--> status: 200
url: https://localhost:3000/two.html
version: http/1.1
--> id = 4
# Data frames omitted


#####
# Third push frame (three.html)
t=1309021949667 [st= 99] SPDY_SESSION_PUSHED_SYN_STREAM
--> associated_stream = 1
--> flags = 2
--> status: 200
url: https://localhost:3000/three.html
version: http/1.1
--> id = 6
# Data frames omitted

#####
# Fourth push frame (CSS)
t=1309021949667 [st= 99] SPDY_SESSION_PUSHED_SYN_STREAM
--> associated_stream = 1
--> flags = 2
--> status: 200
url: https://localhost:3000/stylesheets/style.css
version: http/1.1
--> id = 8
# Data frames omitted

#####
# Data frames for the original request (the homepage)
t=1309021949667 [st= 99] SPDY_SESSION_RECV_DATA
--> flags = 0
--> size = 303
--> stream_id = 1
t=1309021949667 [st= 99] SPDY_SESSION_RECV_DATA
--> flags = 0
--> size = 0
--> stream_id = 1

#####
# Yay! It's a real push stream now:
t=1309021949681 [st= 113] SPDY_STREAM_ADOPTED_PUSH_STREAM
...
That all looks good. It would be nice to send the three subsequent pages after the homepage data. The homepage only needs the HTML data and the stylesheet to render. The other stuff is nice-to-have for immediate (from browser cache) page loads, but the CSS is the only thing that really needs to be in cache before the browser starts rendering the homepage.

I will worry about that another day. For now, I just want to test out the secondary pages. If this has worked, there will be no subsequent requests to the server since SPDY server push has pushed everything into browser cache.

And it works! Kind of. There are no more page requests of the server, but the page one.html renders like:



Heh. Clearly I need to supply a content-type when pushing out HTML. Back when I had first tested out SPDY server push, I had only pushed CSS and images. It seems that Chrome is a bit more forgiving of those resources without content-type than it is of content-type-less HTML.

I am not going to solve that tonight. My initial goal of ensuring that SPDY server push works with express-spdy is satisfied (it works for CSS and image data). Tomorrow and the following day I will pick up with getting HTML to work and allowing data to be sent after the requested resource is sent to the browser.

For now, I am off to write more SPDY Book.

Day #58

Friday, June 24, 2011

SPDY in Non-SPDY Browsers

‹prev | My Chain | next›

I need to focus on writing SPDY Book for the rest of the month. To keep my chain alive, I will pick up some necessary, but hopefully low-hanging fruit. Up today, ensuring that express-spdy still works with browsers that are not SPDY-enabled.

Firing up Firefox, I observe a perfectly normal HTTPS session in Wireshark:



Nice. Looking at Wireshark output against the same site, but using Chrome, I get a normal SPDY session, as evidenced by the highlighted SYN_REPLY (octet #4 is 0x02):



If I switch the Next Protocol Negotiation (NPN) in my sample app's configuration to http/1.1 only:
var app = module.exports = express.createServer({
key: fs.readFileSync(__dirname + '/keys/spdy-key.pem'),
cert: fs.readFileSync(__dirname + '/keys/spdy-cert.pem'),
ca: fs.readFileSync(__dirname + '/keys/spdy-csr.pem'),
NPNProtocols: ['http/1.1']
});
When I access the site in Chrome, I also observe a perfectly normal HTTPS sessions in Wireshark:



Cool. I still cannot quite believe that this stuff just works.

Last up tonight, I add a few more pages to my sample site. After clicking around the site some, I check my Wireshark output again:



Again, nice. The site works just like a normal site should, but it was all done in the same SPDY session. The last request resulted in a data frame over stream 0x1b—the 27th stream as I have been clicking around my sample site.

Cool beans. This really seems to be working. Up tomorrow, I will see if I can get SPDY server push working in express-spdy. I think that ought to be low hanging fruit as well...


Day #58

Thursday, June 23, 2011

Express-spdy Has No Style

‹prev | My Chain | next›

Taking a closer look at the express-spdy sample app, I notice a subtle lack of style:



The sample app generated by express.js does not do much, but it does specify a sans-serif font:
body {
padding: 50px;
font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
}
There are most definitely serifs on the sample page.

Inspecting the SPDY session in Chrome's SPDY tab (under about:net-internals), I notice that no actual data is sent for the stylesheet:
t=1308858355333 [st=171]     SPDY_SESSION_SYN_STREAM  
--> flags = 1
--> accept: text/css,*/*;q=0.1
accept-charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
accept-encoding: gzip,deflate,sdch
accept-language: en-US,en;q=0.8
host: localhost:3000
method: GET
referer: https://localhost:3000/
scheme: https
url: /stylesheets/style.css
user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.797.0 Safari/535.1
version: HTTP/1.1
--> id = 3
t=1308858355340 [st=178] SPDY_SESSION_SYN_REPLY
--> flags = 0
--> Accept-Ranges: bytes
Cache-Control: public, max-age=0
Content-Length: 128
Content-Type: text/css; charset=UTF-8
ETag: "128-1308857965000"
Last-Modified: Thu, 23 Jun 2011 19:39:25 GMT
X-Powered-By: Express
status: 200 OK
version: HTTP/1.1
--> id = 3
t=1308858355341 [st=179] SPDY_SESSION_RECV_DATA
--> flags = 0
--> size = 0
--> stream_id = 3
Immediately after the SYN_REPLY for the stylesheet, an empty data frame is sent.

Thinking that Chrome may be ignoring a (possibly malformed) packet, I drop down to Wireshark to inspect the SYN_REPLY and packet immediately following:
              +----------------------------------+
80 02 00 02 |1| 1 | 2 |
+----------------------------------+
00 00 00 8b | Flags (8) | Length (24 bits) |
+----------------------------------+
00 00 00 03 |X| Stream-ID (31bits) |
+----------------------------------+
00 00 62 e0 | Unused | |
c4 65 13 86 +---------------- |
81 86 46 16 | Name/value header block |
0c bc ce e0 | ... |
44 e4 0c 49
44 0c 82 10 0f eb 28 40 bd 6c 6b c0 c0 eb 03 8a
45 5f 68 2c 32 c8 02 e3 49 47 c1 c8 58 01 18 09
0a c0 4c 67 a8 60 68 69 65 6c 69 65 64 aa e0 ee
1b c2 c0 e2 1a 92 98 ce 20 ac 04 34 5b 17 98 f7
80 c1 64 6e 69 06 cc 3b 06 4a 68 3e 15 07 fb 34
b9 b8 d8 5a 01 16 ae a1 21 6e ba 40 17 39 42 f2
55 10 38 5f 31 b0 26 55 96 00 29 c2 1e 07 00 00
00 ff ff


+----------------------------------+
00 00 00 03 |C| Stream-ID (31bits) |
+----------------------------------+
01 00 00 00 | Flags (8) | Length (24 bits) |
+----------------------------------+
| Data |
+----------------------------------+
Nope, Chrome is not missing a thing. The SYN_REPLY is immediately followed by an empty data frame. The flag in the empty data packet, 0x01 is a FLAG_FIN. So express-spdy is definitely not sending out data even though it must be reading the data in order to get the length of the stylesheet.

After a bit of digging, I locate the pipe() in connect's static middleware:
    // stream
var stream = fs.createReadStream(path, opts);
stream.pipe(res);
That ought to be piping data directly back through the SPDY response.

Reading through node's Stream#pipe() source code a bit, I happen across:
Stream.prototype.pipe = function(dest, options) {
var source = this;

function ondata(chunk) {
if (dest.writable) {
if (false === dest.write(chunk)) source.pause();
}
}

source.on('data', ondata);

// ...
}
Hrm... I wonder if writable is set anywhere.

Checking the node-spdy Response class installed into node_modules, I find that writable is, in fact, not set anywhere. So I add it to the constructor:
var Response = exports.Response = function(cframe, c) {
stream.Stream.call(this);
this.streamID = cframe.data.streamID;
this.c = c;

this.statusCode = 200;
this._headers = {};
this._written = false;
this._reasonPhrase = 'OK';

this.writable = true;
};
Reloading the app, I see



Ahhhh. No serifs. That has the stylesheet applied. Checking the SPDY tab in Chrome, I see that data was, indeed, sent:
   t=1308885003974 [st=25673]     SPDY_SESSION_SYN_REPLY  
--> flags = 0
--> Accept-Ranges: bytes
Cache-Control: public, max-age=0
Content-Length: 110
Content-Type: text/css; charset=UTF-8
ETag: "110-1308882421000"
Last-Modified: Fri, 24 Jun 2011 02:27:01 GMT
X-Powered-By: Express
status: 200 OK
version: HTTP/1.1
--> id = 17
t=1308885003975 [st=25674] SPDY_SESSION_RECV_DATA
--> flags = 0
--> size = 110
--> stream_id = 17

t=1308885003976 [st=25675] SPDY_SESSION_RECV_DATA
--> flags = 0
--> size = 0
--> stream_id = 17
Looking through the latest node-spdy code, it looks as though this is a solved problem. That explains why I did not notice the lack of a stylesheet until now—I had been using my local node-spdy rather than installing from npm until 2 days ago.

I will ask Fedor Indutny to publish a new version of node-spdy and move on to my next TODO item tomorrow.


Day #57

Wednesday, June 22, 2011

express-spdy: From the Ground Up

‹prev | My Chain | next›

Now that I have my first release of express-spdy out the door, it is time to circle back and deliver on some of the TODOs in the README. First up, is better documentation. Specifically, I am going to attempt to install express-spdy on a brand new system.

I will install onto a 64-bit Debian system. I am going with 64-bit because bitter experience has taught me that 64-bit is slightly more problematic than 32 bit. These instructions should work just fine for a 32 bit system as well. I am using Debian instead of Ubuntu to cut down slightly on the size of the VirtualBox image that I will use. Since Debian and Ubuntu share the same repository and since I will do this all at the command line, Ubuntu really offers no benefit in this case.

As my installer, I use debian-6.0.1a-amd64-i386-netinst.iso. For the install, I choose all defaults except the Software Packages at the end of the install—I choose none.

The only package that I install on my Debian box before getting started is ssh (for ease of copy & paste).

First up, I install Debian's build-essential. I do this out of habit on any new system, but it pulls in g++ for C++ code:
cstrom@debian:~$ sudo apt-get install build-essential
Next it is time to install openssl. This has to be the latest trunk to pull in the new Next Protocol Negotiation (NPN) stuff needed for SPDY. Since I am installing from a tarball and not a source code repository, I install in $HOME/src:
cstrom@debian:~$ mkdir src
cstrom@debian:~$ cd !$
cd src
cstrom@debian:~/src$ wget ftp://ftp.openssl.org/snapshot/openssl-SNAP-20110622.tar.gz
--2011-06-22 20:43:19-- ftp://ftp.openssl.org/snapshot/openssl-SNAP-20110622.tar.gz
Note: The openssl snapshots are only available for a rolling window of 4 days. In the future, I will check out ftp://ftp.openssl.org/snapshot/ for the most recent snapshot. This is a slight inconvenience, but beats installing CVS.

Now I can configure openssl:
./Configure shared --prefix=$HOME/local no-idea no-mdc2 no-rc5 zlib  enable-tlsext linux-elf
make depend
make
make install
And make depend. Unfortunately, I run into:
...
making depend in crypto/comp...
make[2]: Entering directory `/home/cstrom/src/openssl-SNAP-20110622/crypto/comp'
../../util/domd ../.. -MD gcc -- -fPIC -DOPENSSL_PIC -DZLIB -DOPENSSL_THREADS -D_REENTRANT -DDSO_DLFCN -DHAVE_DLFCN_H -DL_ENDIAN -DTERMIO -O3 -fomit-frame-pointer -Wall -DOPENSSL_BN_ASM_PART_WORDS -DOPENSSL_IA32_SSE2 -DOPENSSL_BN_ASM_MONT -DOPENSSL_BN_ASM_GF2m -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DMD5_ASM -DRMD160_ASM -DAES_ASM -DWHIRLPOOL_ASM -DGHASH_ASM -I.. -I../.. -I../modes -I../asn1 -I../evp -I../../include -DOPENSSL_NO_DEPRECATED -DOPENSSL_NO_EC_NISTP224_64_GCC_128 -DOPENSSL_NO_GMP -DOPENSSL_NO_IDEA -DOPENSSL_NO_JPAKE -DOPENSSL_NO_MD2 -DOPENSSL_NO_MDC2 -DOPENSSL_NO_RC5 -DOPENSSL_NO_RFC3779 -DOPENSSL_NO_STORE -- comp_lib.c comp_err.c c_rle.c c_zlib.c
c_zlib.c:25:18: error: zlib.h: No such file or directory
make[2]: *** [depend] Error 1
make[2]: Leaving directory `/home/cstrom/src/openssl-SNAP-20110622/crypto/comp'
make[1]: *** [depend] Error 1
make[1]: Leaving directory `/home/cstrom/src/openssl-SNAP-20110622/crypto'
make: *** [depend] Error 1
After searching through Debian packages, I determine that I need to install lib64z1. But:
cstrom@debian:~/src/openssl-SNAP-20110622$ sudo apt-get install lib64z1
Reading package lists... Done
Building dependency tree
Reading state information... Done
E: Unable to locate package lib64z1
Er... What the hell? It most certainly is. Grr...

Will the 32-bit version install and work?
cstrom@debian:~/src/openssl-SNAP-20110622$ sudo apt-get install lib32z1-dev
That actually seems to do the trick as make depend completes successfully now. make depend completes, but make does not. Ugh.

After a bit of googling, I find that maybe I need:
cstrom@debian:~/src/openssl-SNAP-20110622$ sudo apt-get install zlib1g-dev
But I still end up with the same failures in make:
...
gcc -c -I. -I.. -I../include -fPIC -DOPENSSL_PIC -DZLIB -DOPENSSL_THREADS -D_REENTRANT -DDSO_DLFCN -DHAVE_DLFCN_H -DL_ENDIAN -DTERMIO -O3 -fomit-frame-pointer -Wall -DOPENSSL_BN_ASM_PART_WORDS -DOPENSSL_IA32_SSE2 -DOPENSSL_BN_ASM_MONT -DOPENSSL_BN_ASM_GF2m -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DMD5_ASM -DRMD160_ASM -DAES_ASM -DWHIRLPOOL_ASM -DGHASH_ASM -c -o x86cpuid.o x86cpuid.s
x86cpuid.s: Assembler messages:
x86cpuid.s:8: Error: suffix or operands invalid for `push'
x86cpuid.s:9: Error: suffix or operands invalid for `push'
x86cpuid.s:10: Error: suffix or operands invalid for `push'
x86cpuid.s:11: Error: suffix or operands invalid for `push'
x86cpuid.s:13: Error: suffix or operands invalid for `pushf'
x86cpuid.s:14: Error: suffix or operands invalid for `pop'
x86cpuid.s:17: Error: suffix or operands invalid for `push'
x86cpuid.s:18: Error: suffix or operands invalid for `popf'
x86cpuid.s:19: Error: suffix or operands invalid for `pushf'
x86cpuid.s:20: Error: suffix or operands invalid for `pop'
...
Ugh. It seems that my Configure line was erroneous. I am working on 64-bit here, but am targeting vanilla linux-elf. So I try again:
cstrom@debian:~/src/openssl-SNAP-20110622$ ./Configure shared --prefix=$HOME/local no-idea no-mdc2 no-rc5 zlib  enable-tlsext linux-x86_64
With that, I am ready to install node. I need node 0.5.0-pre, which means installing from the git repository. That means git:
cstrom@debian:~/repos$ sudo apt-get install git-core
Since this is coming from a source code repository, I put the copy into $HOME/repos:
cstrom@debian:~$ mkdir repos
cstrom@debian:~$ cd !$
cd repos
And clone the node repository:
cstrom@debian:~/repos$ git clone https://github.com/joyent/node.git
Now I can configure node to use my freshly installed edge-openssl:
cstrom@debian:~/repos/node$ ./configure --openssl-includes=$HOME/local/include --openssl-libpath=$HOME/local/lib --prefix=$HOME/local/node-v0.5.0-pre
/usr/bin/env: python: No such file or directory
But first, I need to install python:
cstrom@debian:~/repos/node$ sudo apt-get install python
That does the trick. After that, I can:
./configure --openssl-includes=$HOME/local/include --openssl-libpath=$HOME/local/lib --prefix=$HOME/local/node-v0.5.0-pre
make
make install
To easily use my locally installed openssl and node binaries, shared objects, etc., I add the following to my $HOME/.bashrc:
# For locally installed binaries
export LD_LIBRARY_PATH=$HOME/local/lib
PATH=$HOME/local/bin:$PATH
PKG_CONFIG_PATH=$HOME/local/lib/pkgconfig
CPATH=$HOME/local/include
export MANPATH=$HOME/local/share/man:/usr/share/man

# For node.js work. For more info, see:
# http://blog.nodejs.org/2011/04/04/development-environment/
for i in $HOME/local/*; do
[ -d $i/bin ] && PATH="${i}/bin:${PATH}"
[ -d $i/sbin ] && PATH="${i}/sbin:${PATH}"
[ -d $i/include ] && CPATH="${i}/include:${CPATH}"
[ -d $i/lib ] && LD_LIBRARY_PATH="${i}/lib:${LD_LIBRARY_PATH}"
[ -d $i/lib/pkgconfig ] && PKG_CONFIG_PATH="${i}/lib/pkgconfig:${PKG_CONFIG_PATH}"
[ -d $i/share/man ] && MANPATH="${i}/share/man:${MANPATH}"
done
I log out and log back in to ensure that my .bashrc changes are picked up (I could also have manually sourced ~/.bashrc). Now it is time to install the node package manager (NPM):
cstrom@debian:~$ which node
/home/cstrom/local/node-v0.5.0-pre/bin/node
cstrom@debian:~$ curl http://npmjs.org/install.sh | sh
-bash: curl: command not found
But first I need to install curl:
cstrom@debian:~$ sudo apt-get install curl
Now, my npm installs successfully:
cstrom@debian:~$ curl http://npmjs.org/install.sh | sh
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 3902 100 3902 0 0 12247 0 --:--:-- --:--:-- --:--:-- 32247
fetching: http://registry.npmjs.org/npm/-/npm-1.0.14.tgz
0.5.0-pre
1.0.14
prefix=/home/cstrom/local/node-v0.5.0-pre

This script will find and eliminate any shims, symbolic
links, and other cruft that was installed by npm 0.x.

Is this OK? enter 'yes' or 'no'
yes

All clean!
! [ -d .git ] || git submodule update --init
node cli.js cache clean
node cli.js rm npm -g -f --loglevel error
node cli.js install -g -f
/home/cstrom/local/node-v0.5.0-pre/bin/npm -> /home/cstrom/local/node-v0.5.0-pre/lib/node_modules/npm/bin/npm.js
/home/cstrom/local/node-v0.5.0-pre/bin/npm_g -> /home/cstrom/local/node-v0.5.0-pre/lib/node_modules/npm/bin/npm.js
/home/cstrom/local/node-v0.5.0-pre/bin/npm-g -> /home/cstrom/local/node-v0.5.0-pre/lib/node_modules/npm/bin/npm.js
npm@1.0.14 /home/cstrom/local/node-v0.5.0-pre/lib/node_modules/npm
It worked
I am close now.

Next I install express.js express.js "globally". I do this so that I can run the express executable generator that comes with express:
cstrom@debian:~$ npm install -g express
/home/cstrom/local/node-v0.5.0-pre/bin/express -> /home/cstrom/local/node-v0.5.0-pre/lib/node_modules/express/bin/express
mime@1.2.2 /home/cstrom/local/node-v0.5.0-pre/lib/node_modules/express/node_modules/mime
qs@0.1.0 /home/cstrom/local/node-v0.5.0-pre/lib/node_modules/express/node_modules/qs
connect@1.5.1 /home/cstrom/local/node-v0.5.0-pre/lib/node_modules/express/node_modules/connect
express@2.3.12 /home/cstrom/local/node-v0.5.0-pre/lib/node_modules/express
Since I am likely to put my SPDY-ized express.js application under source code control, I create my express.js app in $HOME/repos:
cstrom@debian:~$ cd repos/
cstrom@debian:~/repos$ express test
create : test
create : test/package.json
create : test/app.js
create : test/public/javascripts
create : test/logs
create : test/pids
create : test/public/images
create : test/views
create : test/views/layout.jade
create : test/views/index.jade
create : test/public/stylesheets
create : test/public/stylesheets/style.css
In my test app, I install the new express-spdy package from npm:
cstrom@debian:~/repos$ cd test
cstrom@debian:~/repos/test$ npm install express-spdy

> zlibcontext@1.0.7 install /home/cstrom/repos/test/node_modules/express-spdy/node_modules/spdy/node_modules/zlibcontext
> ./configure && make

Setting srcdir to : /home/cstrom/repos/test/node_modules/express-spdy/node_modules/spdy/node_modules/zlibcontext
Setting blddir to : /home/cstrom/repos/test/node_modules/express-spdy/node_modules/spdy/node_modules/zlibcontext/build
Checking for program g++ or c++ : /usr/bin/g++
Checking for program cpp : /usr/bin/cpp
Checking for program ar : /usr/bin/ar
Checking for program ranlib : /usr/bin/ranlib
Checking for g++ : ok
Checking for node path : not found
Checking for node prefix : ok /home/cstrom/local/node-v0.5.0-pre
Checking for library z : yes
'configure' finished successfully (0.431s)
node-waf build
Waf: Entering directory `/home/cstrom/repos/test/node_modules/express-spdy/node_modules/spdy/node_modules/zlibcontext/build'
[1/2] cxx: src/node_zlib.cc -> build/default/src/node_zlib_1.o
/home/cstrom/local/node-v0.5.0-pre/include/node/ev/ev.h:568: warning: ‘int ev_is_default_loop()’ defined but not used
/home/cstrom/local/node-v0.5.0-pre/include/node/ev/ev.h:804: warning: ‘void ev_loop(int)’ defined but not used
/home/cstrom/local/node-v0.5.0-pre/include/node/ev/ev.h:805: warning: ‘void ev_unloop(int)’ defined but not used
/home/cstrom/local/node-v0.5.0-pre/include/node/ev/ev.h:806: warning: ‘void ev_default_destroy()’ defined but not used
/home/cstrom/local/node-v0.5.0-pre/include/node/ev/ev.h:807: warning: ‘void ev_default_fork()’ defined but not used
/home/cstrom/local/node-v0.5.0-pre/include/node/ev/ev.h:809: warning: ‘unsigned int ev_loop_count()’ defined but not used
/home/cstrom/local/node-v0.5.0-pre/include/node/ev/ev.h:810: warning: ‘unsigned int ev_loop_depth()’ defined but not used
/home/cstrom/local/node-v0.5.0-pre/include/node/ev/ev.h:811: warning: ‘void ev_loop_verify()’ defined but not used
[2/2] cxx_link: build/default/src/node_zlib_1.o -> build/default/zlib_bindings.node
Waf: Leaving directory `/home/cstrom/repos/test/node_modules/express-spdy/node_modules/spdy/node_modules/zlibcontext/build'
'build' finished successfully (0.973s)
express-spdy@0.0.1 ./node_modules/express-spdy
├── express@2.3.12 (mime@1.2.2 qs@0.1.0 connect@1.5.1)
├── connect-spdy@0.0.1 (connect@1.5.1)
└── spdy@0.0.1
SPDY requires SSL, which requires keys. I copy them from the node-spdy package, which was installed as a dependency of express-spdy:
cstrom@debian:~/repos/test$ mkdir keys
cstrom@debian:~/repos/test$ cp node_modules/express-spdy/node_modules/spdy/keys/spdy* keys/
Lastly (I hope), I modify the generated app.js to use those keys and to use express-spdy instead of vanilla express.js:
var express = require('express-spdy')
, fs = require('fs');

var app = module.exports = express.createServer({
key: fs.readFileSync(__dirname + '/keys/spdy-key.pem'),
cert: fs.readFileSync(__dirname + '/keys/spdy-cert.pem'),
ca: fs.readFileSync(__dirname + '/keys/spdy-csr.pem'),
NPNProtocols: ['spdy/2']
});

//var express = require('express');

//var app = module.exports = express.createServer();
After starting the app up, however, I am greeted with:
cstrom@debian:~/repos/test$ node app.js
Express server listening on port 3000 in development mode
Error: Cannot find module 'jade'
...
Arrrgh. I never remember to install the jade templating engine. Even though the express.js generated app includes jade templates. Ah well, that is easily enough resolved:
cstrom@debian:~/repos/test$ npm install jade
jade@0.12.3 ./node_modules/jade
With that, the apps starts just fine:
cstrom@debian:~/repos/test$ node app.js
Express server listening on port 3000 in development mode
More importantly, I get a valid express site when I access the site with Chrome:



Most importantly, I get an actual legitimate SPDY session as evidenced by the SPDY tab in about:net-internals:



Yay! That ought to sufficient to write up some express-spdy install instructions.


Day #56

Tuesday, June 21, 2011

SPDY: express-spdy

‹prev | My Chain | next›

After seeming success with connect-spdy last night, I am ready to try the same with express-spdy. First up, initialize an NPM package:
➜  express-spdy git:(master) ✗ npm init
Package name: (express-spdy)
Description: SPDY-ize express.js sites.
Package version: (0.0.0)
Project homepage: (none) https://github.com/eee-c/express-spdy
Project git repository: (none) https://github.com/eee-c/express-spdy
Author name: Chris Strom
Author email: (none) chris@eeecomputes.com
Author url: (none) http://eeecomputes.com
Main module/entry point: (none) index.js
Test command: (none)
What versions of node does it run on? (~v0.5.0-pre)
About to write to /home/cstrom/repos/express-spdy/package.json

{
"author": "Chris Strom (http://eeecomputes.com)",
"name": "express-spdy",
"description": "SPDY-ize express.js sites.",
"version": "0.0.0",
"homepage": "https://github.com/eee-c/express-spdy",
"repository": {
"type": "git",
"url": "git://github.com/eee-c/express-spdy.git"
},
"main": "index.js",
"engines": {
"node": "~v0.5.0-pre"
},
"dependencies": {},
"devDependencies": {}
}

Is this ok? (yes)
Similar to what I had to do with connect-spdy, I write the core express-spdy to use a SPDY server if options are passed in, otherwise I assume that a normal HTTP server from express proper is desired:
var express = require('express')
, expressCreateServer = express.createServer
, SPDYServer = require('./spdy');

var exports = module.exports = express;

exports.createServer = function(options){
if ('object' == typeof options) {
return new SPDYServer(options, Array.prototype.slice.call(arguments, 1));
} else {
return expressCreateServer(Array.prototype.slice.call(arguments));
}
};
I resolve some package location changes and do some more clean-up work and.. I think I am ready.

To test things out, I copy my express generated sample app into an express-test directory. I then install express-spdy, connect-spdy, and node-spdy from my local repos:
➜  express-test  npm install ~/repos/node-spdy

> zlibcontext@1.0.7 install /home/cstrom/repos/express-test/node_modules/spdy/node_modules/zlibcontext
> ./configure && make
...
'build' finished successfully (0.661s)
zlibcontext@1.0.7 ./node_modules/spdy/node_modules/zlibcontext
spdy@0.0.1 ./node_modules/spdy

➜ express-test npm install ~/repos/connect-spdy
mime@1.2.2 ./node_modules/connect-spdy/node_modules/connect/node_modules/mime
qs@0.1.0 ./node_modules/connect-spdy/node_modules/connect/node_modules/qs
connect@1.5.1 ./node_modules/connect-spdy/node_modules/connect
connect-spdy@0.0.0 ./node_modules/connect-spdy

➜ express-test npm install ~/repos/express-spdy
express-spdy@0.0.0 ./node_modules/express-spdy
That results in a nice, compact npm ls output:
➜  express-test  npm ls
/home/cstrom/repos/express-test
├─┬ connect-spdy@0.0.0
│ └─┬ connect@1.5.1
│ ├── mime@1.2.2
│ └── qs@0.1.0
├─┬ express@2.3.11
│ ├── connect@1.5.1
│ ├── mime@1.2.2
│ └── qs@0.1.0
├── express-spdy@0.0.0
└─┬ spdy@0.0.1
└── zlibcontext@1.0.7
After installing the jade templating engine (used by the generated sample app), everything works! Not only does the site load in Chrome, but the SPDY tab in about:net-internals reports that I have a legit SPDY session:
...
t=1308705422043 [st=19902] SPDY_SESSION_SYN_REPLY
--> flags = 0
--> connection: keep-alive
content-length: 277
content-type: text/html
status: 200 OK
version: HTTP/1.1
x-powered-by: Express
--> id = 7
...
t=1308705422199 [st=20058] SPDY_SESSION_RECV_DATA
--> flags = 0
--> size = 0
--> stream_id = 11
Even after all of that re-organization, it still works. Crazy.

While I am already tempting fate, I might as well publish this as a legit NPM package.

First up, I need to publish connect-spdy. Running npn publish, however, I find:
➜  connect-spdy git:(master) npm publish
npm ERR! Failed PUT response undefined
npm ERR! Error: Cannot insert data into the registry without authorization
npm ERR! See: npm-adduser(1)
...
That was actually entirely expected. I have previously published an NPM package of my own, but not on this machine. After entering the proper credentials:
➜  connect-spdy git:(master) npm adduser
Username: eee-c
Password:
Email: chris@eeecomputes.com
...the npm publish works:
➜  connect-spdy git:(master) npm publish
npm WARN Sending authorization over insecure channel.
I do the same for express-spdy and am then ready to actually try installing directly from NPM. If I have all of my dependencies correct, I should need to install express-spdy, the jade templating engine and nothing else:
➜  express-test  rm -rf node_modules
➜ express-test npm install express-spdy
...
zlibcontext@1.0.7 ./node_modules/express-spdy/node_modules/spdy/node_modules/zlibcontext
spdy@0.0.1 ./node_modules/express-spdy/node_modules/spdy
mime@1.2.2 ./node_modules/express-spdy/node_modules/connect-spdy/node_modules/connect/node_modules/mime
qs@0.1.0 ./node_modules/express-spdy/node_modules/connect-spdy/node_modules/connect/node_modules/qs
connect@1.5.1 ./node_modules/express-spdy/node_modules/connect-spdy/node_modules/connect
connect-spdy@0.0.1 ./node_modules/express-spdy/node_modules/connect-spdy
mime@1.2.2 ./node_modules/express-spdy/node_modules/express/node_modules/mime
connect@1.5.1 ./node_modules/express-spdy/node_modules/express/node_modules/connect
qs@0.1.0 ./node_modules/express-spdy/node_modules/express/node_modules/qs
express@2.3.11 ./node_modules/express-spdy/node_modules/express
express-spdy@0.0.1 ./node_modules/express-spdy
➜ express-test npm ls
/home/cstrom/repos/express-test
└─┬ express-spdy@0.0.1
├─┬ connect-spdy@0.0.1
│ └─┬ connect@1.5.1
│ ├── mime@1.2.2
│ └── qs@0.1.0
├─┬ express@2.3.11
│ ├── connect@1.5.1
│ ├── mime@1.2.2
│ └── qs@0.1.0
└─┬ spdy@0.0.1
└── zlibcontext@1.0.7
➜ express-test npm install jade
jade@0.12.3 ./node_modules/jade
I then run my test express app and....

...it just works.

I still have a valid SYN_REPLY to the initial SYN_STREAM request all the way to the last DATA frame:
...
t=1308708324275 [st= 47] SPDY_SESSION_SYN_REPLY
--> flags = 0
--> Content-Length: 277
Content-Type: text/html
X-Powered-By: Express
status: 200 OK
version: HTTP/1.1
--> id = 1
...
t=1308708324490 [st=262] SPDY_SESSION_RECV_DATA
--> flags = 0
--> size = 23
--> stream_id = 5
t=1308708324490 [st=262] SPDY_SESSION_RECV_DATA
--> flags = 0
--> size = 0
--> stream_id = 5
I can't believe that worked. But I'll take it as a good stopping point for the night.

I have some instructions available at https://github.com/eee-c/express-spdy. I will get started on that TODO list tomorrow.

Day #55

Monday, June 20, 2011

SPDY Connect

‹prev | My Chain | next›

Last night I cataloged what was needed to get a SPDY enabled express.js by way of node-spdy. Tonight I am going to start on the biggest unknown: writing a module to SPDY-ize connect.

The changes that I had to make directly to lib/connect.js are highlighted:
var HTTPServer = require('./http').Server
, HTTPSServer = require('./https').Server
, SPDYServer = require('./spdy').Server
, fs = require('fs');

// ...

function createServer() {
if ('object' == typeof arguments[0]) {
// return new HTTPSServer(arguments[0], Array.prototype.slice.call(arguments, 1));
return new SPDYServer(arguments[0], Array.prototype.slice.call(arguments, 1));

} else {
return new HTTPServer(Array.prototype.slice.call(arguments));
}

//...

exports.HTTPServer = HTTPServer;
exports.HTTPSServer = HTTPSServer;
exports.SPDYServer = SPDYServer;
So, the connect library needs to be able to export, as SPDYServer, the SPDY server that I defined separately in lib/spdy.js. I also need to override the exported connect.createServer function to send back a SPDY Server instead of an HTTPS Server. Exporting connect.createServer is made more complicated by virtue of lib/connect.js exporting createServer() as the export module itself:
// expose createServer() as the module

exports = module.exports = createServer;
I am unfamiliar with overriding the module like that. I wonder what effect it would have on doing something along the lines of:
original_connect = connect.createServer;
connect.createServer = function() {
if (SPDY_ENABLED) {
// do SPDY stuff
}
else {
original_connect()
}
}
Only one way to find out.

First up, I create a NPM package:
➜  connect-spdy  npm init
Package name: (connect-spdy)
Description: SPDY-ized connect server.
Package version: (0.0.0)
Project homepage: (none) https://github.com/eee-c/connect-spdy
Project git repository: (none) https://github.com/eee-c/connect-spdy
Author name: Chris Strom
Author email: (none) chris@eeecomputes.com
Author url: (none) http://eeecomputes.com
Main module/entry point: (none) index
Test command: (none)
What versions of node does it run on? (~v0.5.0-pre) >= 0.5.0-pre
About to write to /home/cstrom/repos/connect-spdy/package.json

{
"author": "Chris Strom (http://eeecomputes.com)",
"name": "connect-spdy",
"description": "SPDY-ized connect server.",
"version": "0.0.0",
"homepage": "https://github.com/eee-c/connect-spdy",
"repository": {
"type": "git",
"url": "git://github.com/eee-c/connect-spdy.git"
},
"main": "index",
"engines": {
"node": ">= 0.5.0-pre"
},
"dependencies": {},
"devDependencies": {}
}

Is this ok? (yes) yes
Now... how to hook into the existing connect.createServer? Will it always be defined when my connect-spdy is loaded...?

Ooh! Maybe I can expose a createServer() in connect-spdy in such a way that it mimics the spike code:
exports = module.exports = createServer;

function createServer() {
if ('object' == typeof arguments[0]) {
return new SPDYServer(arguments[0], Array.prototype.slice.call(arguments, 1));
} else {
return connectCreateServer(Array.prototype.slice.call(arguments));
}
};
If arguments are passed in, then I assume that we are trying to establish a SPDY server otherwise, I allow the default connect createServer() to do its thing. For the SPDYServer class, I just re-use the same class from my spike which, itself, was just a copy of connect's https.js.

With that, I revert my local copy of connect to the original package installed from npm. In express.js, I then load connect-spdy instead of connect:
-var connect = require('connect')
+var connect = require('connect-spdy')
And that actually seems to work. When I fire up the test express app.js server, with vanilla connect + my newly created connect-spdy, I see SPDY sessions in Chrome's about:net-internals:
    t=1308619806288 [st=8994]     SPDY_SESSION_SYN_REPLY  
--> flags = 0
--> connection: keep-alive
content-length: 277
content-type: text/html
status: 200 OK
version: HTTP/1.1
x-powered-by: Express
--> id = 7
Nice! That seems like a reasonable approach and a fine stopping point for the night.

I do not think connect-spdy is quite ready for npm, but I at least put it out on github in case anyone interested. I will get started on express-spdy tomorrow.


Day #54

Sunday, June 19, 2011

SPDY Express: Cleaning Up

‹prev | My Chain | next›

I was finally able to get node-spdy and express.js to establish an actual SPDY session yesterday. I had been spiking pretty heavily, so today I retrace my steps to ensure I understand what I did.

In my express branch of node-spdy, I have only added the express.js skeleton app with SPDY configuration:
* a715da7 (HEAD, express) SPDY configuration for express app
* 687cce4 Skeleton express app
* 9d9e2b7 (origin/master, origin/HEAD, master) Merge branch 'master' of github.com:indutny/node-spdy
Nice. That means that Fedor Indunty wrote node-spdy ready-made for connect-express integration.

As for the changes to express.js (and connect, on which express.js is built), I think that they can be summarized as:
  • connect: create a lib/spdy.js copied from lib/https.js
  • express: create a lib/spdy.js copied from lib/https.js
  • express: decorate the SPDY Response class with render methods from the Response object in express
  • express: decorate the SPDY Response class with send, contentType, header (and probably other) methods from the express Response object
That last bullet item was particularly tricky. The methods that need to be added to the SPDY Response class are not available at the time that the class was evaluated so I had to modify the core lib/express.js module. My hope is that I can improve on that today.

First up, lib/spdy.js in connect (copied almost directly from lib/https.js):

/*!
* Connect - SPDYServer
* Copyright(c) 2010 Sencha Inc.
* Copyright(c) 2011 TJ Holowaychuk
* MIT Licensed
*/

/**
* Module dependencies.
*/

var HTTPServer = require('./http').Server
, spdy = require('spdy');

/**
* Initialize a new `Server` with the given
*`options` and `middleware`. The SPDY api
* is identical to the HTTPS api, which
* is identical to the [HTTP](http.html) server,
* however TLS `options` must be provided before
* passing in the optional middleware.
*
* @params {Object} options
* @params {Array} middleawre
* @return {Server}
* @api public
*/

var Server = exports.Server = function SPDYServer(options, middleware) {
this.stack = [];
middleware.forEach(function(fn){
this.use(fn);
}, this);
spdy.Server.call(this, options, this.handle);
};

/**
* Inherit from `http.Server.prototype`.
*/

Server.prototype.__proto__ = spdy.Server.prototype;

// mixin HTTPServer methods

Object.keys(HTTPServer.prototype).forEach(function(method){
Server.prototype[method] = HTTPServer.prototype[method];
});
Next, I do the same for lib/spdy.js in express.js:

/*!
* Express - SPDYServer
* Copyright(c) 2010 TJ Holowaychuk
* MIT Licensed
*/

/**
* Module dependencies.
*/

var connect = require('connect')
, HTTPServer = require('./http')
, spdy = require('spdy');

/**
* Expose `SPDYServer`.
*/

exports = module.exports = SPDYServer;

/**
* Server proto.
*/

var app = SPDYServer.prototype;

/**
* Initialize a new `SPDYServer` with the
* given `options`, and optional `middleware`.
*
* @param {Object} options
* @param {Array} middleware
* @api public
*/

function SPDYServer(options, middleware){
connect.SPDYServer.call(this, options, []);
this.init(middleware);
};

/**
* Inherit from `connect.SPDYServer`.
*/

app.__proto__ = connect.SPDYServer.prototype;

// mixin HTTPServer methods

Object.keys(HTTPServer.prototype).forEach(function(method){
app[method] = HTTPServer.prototype[method];
});
Also in express.js/lib/spdy.js, I mixin rendering related methods from express.js's Response:
var connect = require('connect')
, HTTPServer = require('./http')
, spdy = require('spdy')
, spdy_res = spdy.Response.prototype
, http = require('http')
, res = http.ServerResponse.prototype;


// ...

// TODO: don't hard-code which methods get mixed-in
spdy_res.partial = res.partial;
spdy_res.render = res.render;
spdy_res._render = res._render;
Lastly, I have to mixin other methods to the SPDY Response class. As noted last night, I cannot do so in lib/spdy.js. I have to do that directly in lib/express.js to ensure that the necessary methods have been defined at runtime:
/**
* Response extensions.
*/

require('./response');

var http = require('http')
, res = http.ServerResponse.prototype
, spdy = require('../../../lib/spdy')
, spdy_res = spdy.Response.prototype;

spdy_res.send = res.send;
spdy_res.header = res.header;
spdy_res.contentType = res.contentType;
Unless I missed something that ought to do it. As it turns out, I did miss something. When I try to start up the server, I get:
➜  node-spdy git:(express) node test/express/app.js

node.js:183
throw e; // process.nextTick error, or 'error' event on first tick
^
TypeError: Cannot read property 'prototype' of undefined
at Object.<anonymous> (/home/cstrom/repos/node-spdy/node_modules/express/lib/spdy.js:49:35)
at Module._compile (module.js:423:26)
at Object..js (module.js:429:10)
at Module.load (module.js:339:31)
at Function._load (module.js:298:12)
at require (module.js:367:19)
at Object.<anonymous> (/home/cstrom/repos/node-spdy/node_modules/express/lib/express.js:14:18)
at Module._compile (module.js:423:26)
at Object..js (module.js:429:10)
at Module.load (module.js:339:31)
Aw nuts, the error on line 49 of express/lib/app.js is coming from:
app.__proto__ = connect.SPDYServer.prototype;
It looks as though, in addition to mucking directly with express/lib/express.js, I also mucked with connect/lib/connect.js.

In connect/lib/connect.js, I need to define and export SPDYServer, which ought to make that error go away. For good measure, I also replace the call to HTTPSServer with a SPDYServer call (node-spdy will fallback to plain-old https if SPDY is not available):
var HTTPServer = require('./http').Server
, HTTPSServer = require('./https').Server
, SPDYServer = require('./spdy').Server
, fs = require('fs');

// ...

function createServer() {
if ('object' == typeof arguments[0]) {
// return new HTTPSServer(arguments[0], Array.prototype.slice.call(arguments, 1));
return new SPDYServer(arguments[0], Array.prototype.slice.call(arguments, 1));

} else {
return new HTTPServer(Array.prototype.slice.call(arguments));
}

//...

exports.HTTPServer = HTTPServer;
exports.HTTPSServer = HTTPSServer;
exports.SPDYServer = SPDYServer;
That does the trick. I again have my SPDY session working with express.js.

In the end, I had to do the following to get express.js SPDY-ized:
  1. connect: create a lib/spdy.js copied from lib/https.js
  2. connect: export SPDYServer from the main connect object and replace the main HTTPS server with the SPDYServer
  3. express: create a lib/spdy.js copied from lib/https.js
  4. express: decorate the SPDY Response class with render methods from the Response object in express (needs to be done in lib/express.js)
  5. express: decorate the SPDY Response class with send, contentType, header (and probably other) methods from the express Response object
I think that #1, #3, and #4 can probably be done as part of a separate express and/or connect module. I might even be able to get #5 done. But #2—providing an alternate connect endpoint—that seems problematic. I will get started on that tomorrow.


Day #55