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

No comments:

Post a Comment