Wednesday, April 11, 2012

Building a Node.js Generator for Express-Spdy

‹prev | My Chain | next›

I would like to make the install process for express-spdy slightly less... large:


The vanilla express.js comes with a nice little generator to get started. I ought to be able to adapt that for use with express-spdy. First up, I copy a recent version of the express.js generator script into express-spdy:
➜  express-spdy git:(master) cp ~/tmp/express-test/node_modules/express-spdy/node_modules/express/bin/express bin/express-spdy
Yes, I am just going to edit that in place. It is just a series of functions, some of which I need to edit and some of which I need to replace. It is pretty awful not being able to re-use the code. To mitigate the pain somewhat, I check the express.js version into git first, then make my changes. In an ideal world, that will make for a clean patch whenever I need to incorporate new express.js generator code.

Next up, I add express-spdy as an executable for express-spdy to the packagage.json:
{
  "author": "Chris Strom  (http://eeecomputes.com)",
  "name": "express-spdy",
  "description": "SPDY-ize express.js sites.",
  // ...
  "bin": { "express-spdy": "./bin/express-spdy" }
}
NPM will use that upon install to install the binaries properly.

Then it is time for the somewhat tedious process of copying working express-spdy code into the generator templates. The only real pain involved is escaping single quotes:
var app = [
    ''
  , '/**'
  , ' * Module dependencies.'
  , ' */'
  , ''
  , 'var express = require(\'express-spdy\')'
  , '  , routes = require(\'./routes\')'
  , '  , fs = require(\'fs\')'
  , '  , host = \'https://localhost:3000/\';'
  , ''

  , '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\', \'http/1.1\'],'
  , '  push: awesome_push'
  , '});'
  , ''
  ...
].join(eol);
Ick. I very much miss Dart multi-line strings at times like this.

Anyhow, in a temporary directory, I install this package from my local repo (~/repos/express-spdy) and try to run it:
➜  express-spdy-bin  npm install ~/repos/express-spdy/
....
➜  express-spdy-bin  ./node_modules/express-spdy/bin/express-spdy --help

node.js:201
        throw e; // process.nextTick error, or 'error' event on first tick
              ^
Error: Cannot find module 'mkdirp'
    at Function._resolveFilename (module.js:332:11)
    at Function._load (module.js:279:25)
    at Module.require (module.js:354:17)
    at require (module.js:370:17)
    at Object.<anonymous> (/home/cstrom/tmp/express-spdy-bin/node_modules/express-spdy/bin/express-spdy:10:14)
...
Looks like I need to add mkdirp as a dependency in express-spdy's package.json:
{
  "author": "Chris Strom  (http://eeecomputes.com)",
  "name": "express-spdy",
  "description": "SPDY-ize express.js sites.",
  // ...
  "dependencies": {
    "express": ">= 2.5.0 < 2.6.0",
    "connect-spdy": ">= 0.1.0",
    "spdy": ">= 0.1.0 < 1.0.0",
    "mkdirp": "0.3.0"
  },
  // ...
}
After re-installing from my local repository, I am able to generate the site:
➜  express-spdy-bin  ./node_modules/express-spdy/bin/express-spdy test

   create : test
   create : test/package.json
   create : test/app.js
   create : test/public
   create : test/public/javascripts
   create : test/public/images
   create : test/public/stylesheets
   create : test/public/stylesheets/style.css
   create : test/routes
   create : test/routes/index.js
   create : test/views
   create : test/views/layout.jade
   create : test/views/index.jade

   dont forget to install dependencies:
   $ cd test && npm install
But, when I fire up the generated app, I get:
➜  test  node app

node.js:201
        throw e; // process.nextTick error, or 'error' event on first tick
              ^
Error: ENOENT, no such file or directory '/home/cstrom/tmp/express-spdy-bin/test/keys/spdy-key.pem'
    at Object.openSync (fs.js:238:18)
    at Object.readFileSync (fs.js:128:15)
    at Object.<anonymous> (/home/cstrom/tmp/express-spdy-bin/test/app.js:12:11)
...
Aw, nuts. I forgot to copy in the SSL certificates from node-spdy. The quickest and simplest way to accomplish this seems to be to define the certificate and keys inside the generator:
/**
 * Public key
 */
var cert = [
    '-----BEGIN CERTIFICATE-----'
  , 'MIICHzCCAYgCCQCPPSUAa8QZojANBgkqhkiG9w0BAQUFADBUMQswCQYDVQQGEwJS'
  // ... 
  , 'Uws6Lif3P9UbsuRiYPxMgg98wg=='
  , '-----END CERTIFICATE-----'
].join(eol);
Then, in the createApplicationAt() function, I can use those variables to write out certificates:
function createApplicationAt(path) {
  // ...
  // SSL support
  mkdir(path + '/keys', function(){
    write(path + '/keys/spdy-key.pem', key);
    write(path + '/keys/spdy-cert.pem', cert);
    write(path + '/keys/spdy-csr.pem', csr);
  });
}
That seems to do the trick as the freshly installed generator now creates the SSL keys:
➜  express-spdy-bin  ./node_modules/express-spdy/bin/express-spdy test

   create : test
   create : test/package.json
   create : test/app.js
   create : test/keys
   create : test/keys/spdy-key.pem
   create : test/keys/spdy-cert.pem
   create : test/keys/spdy-csr.pem
   create : test/public
   ....

   dont forget to install dependencies:
   $ cd test && npm install
And loading the app up in Chrome, I even see a nice SPDY push of the stylesheet in Chrome's about:net-internals's SPDY tab:
...
t=1334200777373 [st=   77]    SPDY_SESSION_PUSHED_SYN_STREAM
                              --> associated_stream = 1
                              --> flags = 2
                              --> last-modified: Thu, 12 Apr 2012 03:19:37 GMT
                                  status: 200
                                  url: https://localhost:3000/stylesheets/style.css
                                  version: http/1.1
                              --> id = 2
And it is definitely working because I see my beloved "adopted push stream" message after all of the data has been sent:
t=1334200777514 [st=  218]    SPDY_STREAM_ADOPTED_PUSH_STREAM
All that is left is to update the documentation and publish the changes to NPM. This should make it much easier for SPDY newbies to get started doing cool stuff.


Day #353

No comments:

Post a Comment