├── .gitignore ├── INSTALL.md ├── README.md ├── bin └── express-spdy ├── express.js ├── index.js ├── package.json ├── spdy.js └── test └── response-test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /INSTALL.md: -------------------------------------------------------------------------------- 1 | INSTALL express-spdy 2 | ==================== 3 | 4 | These instructions will install edge-openssl, unstable node.js and express-spdy in your home directory. This will *not* overwrite or affect any system libraries. 5 | 6 | I work with tarballs in `$HOME/src` and source code controlled code in `$HOME/repos`. 7 | 8 | Dependencies 9 | ------------ 10 | 11 | Dependencies are described for a vanilla Debian 64-bit system, but should work equally well for Ubuntu. It should be relatively easy to extrapolate dependencies for OSX. 12 | 13 | If not already installed, you need: 14 | 15 | * C and C++ compilers for openssl and node.js 16 | * Zlibh for SPDY compression 17 | * Git to obtain the latest node.js source 18 | * Python to configure node.js 19 | * Curl for installing the Node Package Manager (NPM) 20 | 21 | This can be accomplished on Debian / Ubuntu with: 22 | 23 | sudo apt-get install build-essential zlib1g-dev git-core python curl 24 | 25 | Edge openssl 26 | ------------ 27 | 28 | SPDY requires Next Protocol Negotiation (NPN) extensions to work properly. This is only available in edge openssl. This can be obtained from the openssl CVS repository, but that would require installing CVS. It is easier to access the [openssl FTP server](ftp://ftp.openssl.org/snapshot/). Look for tar.gz files that begin with `openssl-SNAP-`. Following that prefix is a datestamp (the tarball from June 22 was `openssl-SNAP-20110622.tar.gz`). Unfortunately those tarballs are only available in a rolling 4 day window so permanent links are not possible. 29 | 30 | Once you have identified the correct openssl SNAP tarball, download and un-tar it in `$HOME/src`: 31 | 32 | mkdir $HOME/src 33 | cd $HOME/src 34 | wget ftp://ftp.openssl.org/snapshot/openssl-SNAP-.tar.gz 35 | tar zxf openssl-SNAP-.tar.gz 36 | cd openssl-SNAP- 37 | 38 | Next configure openssl for your platform. For 32-bit linux, use: 39 | 40 | ./Configure shared --prefix=$HOME/local no-idea no-mdc2 no-rc5 zlib enable-tlsext linux-elf 41 | 42 | If you are using 64-bit linux, replace `linux-elf` with `linux-x86_64`. If unsure what platform to use, run `./Configure` without any options to get a complete list. 43 | 44 | After configuring openssl, build and install it: 45 | 46 | make depend 47 | make 48 | make install 49 | 50 | Stable node.js 51 | ---------------- 52 | 53 | Install the source code in `$HOME/src`: 54 | 55 | mkdir -p $HOME/src 56 | cd $HOME/src/ 57 | # NOTE: check for latest version at http://nodejs.org/#download 58 | wget http://nodejs.org/dist/v0.6.16/node-v0.6.16.tar.gz 59 | tar zxf node-v0.6.16.tar.gz 60 | cd node-v0.6.16 61 | 62 | Configure node to use edge-openssl and to install locally: 63 | 64 | ./configure --openssl-includes=$HOME/local/include --openssl-libpath=$HOME/local/lib --prefix=$HOME/local/node-v0.6.16 65 | make 66 | make install 67 | 68 | Configure Bash to Use Edge openssl and node.js 69 | ---------------------------------------------- 70 | 71 | Add the following to $HOME/.bashrc: 72 | 73 | # For locally installed binaries 74 | export LD_LIBRARY_PATH=$HOME/local/lib 75 | PATH=$HOME/local/bin:$PATH 76 | PKG_CONFIG_PATH=$HOME/local/lib/pkgconfig 77 | CPATH=$HOME/local/include 78 | export MANPATH=$HOME/local/share/man:/usr/share/man 79 | 80 | # For node.js work. For more info, see: 81 | # http://blog.nodejs.org/2011/04/04/development-environment/ 82 | for i in $HOME/local/*; do 83 | [ -d $i/bin ] && PATH="${i}/bin:${PATH}" 84 | [ -d $i/sbin ] && PATH="${i}/sbin:${PATH}" 85 | [ -d $i/include ] && CPATH="${i}/include:${CPATH}" 86 | [ -d $i/lib ] && LD_LIBRARY_PATH="${i}/lib:${LD_LIBRARY_PATH}" 87 | [ -d $i/lib/pkgconfig ] && PKG_CONFIG_PATH="${i}/lib/pkgconfig:${PKG_CONFIG_PATH}" 88 | [ -d $i/share/man ] && MANPATH="${i}/share/man:${MANPATH}" 89 | done 90 | 91 | Logout and log back in to ensure that your changes are applied correctly. 92 | 93 | Install NPM and Express-Spdy 94 | -------------------------- 95 | 96 | curl http://npmjs.org/install.sh | clean=yes sh 97 | npm install -g express-spdy 98 | 99 | Create a Sample Express-Spdy App 100 | -------------------------------- 101 | 102 | cd $HOME/repos 103 | express-spdy example-spdy 104 | cd example-spdy 105 | npm install 106 | 107 | Run the Sample App 108 | ------------------ 109 | 110 | node app.js 111 | 112 | If all has worked, you should see: 113 | 114 | Express server listening on port 3000 in development mode 115 | 116 | And you should be able to access the SPDY-ized site at: https://localhost:3000 (proceed past certificate warnings). 117 | 118 | To verify that you are seeing a SPDY session and not just a plain-old HTTP session, checkout the SPDY tab in Chrome's `about:net-internals`. 119 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | express-spdy 2 | ============ 3 | 4 | *WARNING: this is very, very alpha stuff.* 5 | 6 | [SPDY](http://www.chromium.org/spdy) is a new protocol from Google based on HTTP. It aims for 50% decrease in page load times over vanilla HTTP. 7 | 8 | Currently, Google Chrome is the only browser that supports SPDY in general release. Firefox has SPDY support in its nightlies. 9 | 10 | The express-spdy package aims to allow existing express.js sites to experiment with SPDY without making (many) changes. 11 | 12 | 13 | INSTALLATION 14 | ------------ 15 | 16 | Install the latest snapshot of openssl with NPN and shared objects. Currently, this requires obtaining a SNAP tarball from the openssl FTP server or checking out the latest trunk from the openssl CVS server. 17 | 18 | Install node.js 0.6.0 or later. 19 | 20 | With the npn-enabled node, `npm install express-spdy`. 21 | 22 | Detailed instructions in [INSTALL.md](https://github.com/eee-c/express-spdy/blob/master/INSTALL.md). 23 | 24 | CONFIGURATION 25 | ------------- 26 | 27 | An express.js app can then be SPDY-ized by changing the first few lines to: 28 | 29 | var express = require('express-spdy') 30 | , fs = require('fs'); 31 | 32 | var app = module.exports = express.createServer({ 33 | key: fs.readFileSync(__dirname + '/keys/spdy-key.pem'), 34 | cert: fs.readFileSync(__dirname + '/keys/spdy-cert.pem'), 35 | ca: fs.readFileSync(__dirname + '/keys/spdy-csr.pem'), 36 | NPNProtocols: ['spdy/2'] 37 | }); 38 | 39 | Detailed instructions in [INSTALL.md](https://github.com/eee-c/express-spdy/blob/master/INSTALL.md). 40 | 41 | 42 | TODO 43 | ---- 44 | 45 | * Tests 46 | * Server push 47 | * Documentation (of course) 48 | 49 | 50 | THANKS 51 | ------ 52 | 53 | _Huge_ thanks to [Fedor Indutny](https://github.com/indutny) for his awesome [node-spdy](https://github.com/indutny/node-spdy). Very little was required to get express-spdy working thanks to his hard work. 54 | -------------------------------------------------------------------------------- /bin/express-spdy: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var fs = require('fs') 8 | , os = require('os') 9 | , exec = require('child_process').exec 10 | , mkdirp = require('mkdirp'); 11 | 12 | /** 13 | * Framework version. 14 | */ 15 | 16 | var version = '0.1.3'; 17 | 18 | /** 19 | * Add session support. 20 | */ 21 | 22 | var sessions = false; 23 | 24 | /** 25 | * CSS engine to utilize. 26 | */ 27 | 28 | var cssEngine; 29 | 30 | /** 31 | * End-of-line code. 32 | */ 33 | 34 | var eol = os.platform 35 | ? ('win32' == os.platform() ? '\r\n' : '\n') 36 | : '\n'; 37 | 38 | /** 39 | * Template engine to utilize. 40 | */ 41 | 42 | var templateEngine = 'jade'; 43 | 44 | /** 45 | * Usage documentation. 46 | */ 47 | 48 | var usage = '' 49 | + '\n' 50 | + ' Usage: express [options] [path]\n' 51 | + '\n' 52 | + ' Options:\n' 53 | + ' -s, --sessions add session support\n' 54 | + ' -t, --template add template support (jade|ejs). default=jade\n' 55 | + ' -c, --css add stylesheet support (stylus). default=plain css\n' 56 | + ' -v, --version output framework version\n' 57 | + ' -h, --help output help information\n' 58 | ; 59 | 60 | /** 61 | * Routes index template. 62 | */ 63 | 64 | var index = [ 65 | '' 66 | , '/*' 67 | , ' * GET home page.' 68 | , ' */' 69 | , '' 70 | , 'exports.index = function(req, res){' 71 | , ' res.render(\'index\', { title: \'Express\' })' 72 | , '};' 73 | ].join(eol); 74 | 75 | /** 76 | * Jade layout template. 77 | */ 78 | 79 | var jadeLayout = [ 80 | '!!!' 81 | , 'html' 82 | , ' head' 83 | , ' title= title' 84 | , ' link(rel=\'stylesheet\', href=\'/stylesheets/style.css\')' 85 | , ' body!= body' 86 | ].join(eol); 87 | 88 | /** 89 | * Jade index template. 90 | */ 91 | 92 | var jadeIndex = [ 93 | 'h1= title' 94 | , 'p Welcome to #{title}' 95 | ].join(eol); 96 | 97 | /** 98 | * EJS layout template. 99 | */ 100 | 101 | var ejsLayout = [ 102 | '' 103 | , '' 104 | , ' ' 105 | , ' <%= title %>' 106 | , ' ' 107 | , ' ' 108 | , ' ' 109 | , ' <%- body %>' 110 | , ' ' 111 | , '' 112 | ].join(eol); 113 | 114 | /** 115 | * EJS index template. 116 | */ 117 | 118 | var ejsIndex = [ 119 | '

<%= title %>

' 120 | , '

Welcome to <%= title %>

' 121 | ].join(eol); 122 | 123 | /** 124 | * Default css template. 125 | */ 126 | 127 | var css = [ 128 | 'body {' 129 | , ' padding: 50px;' 130 | , ' font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;' 131 | , '}' 132 | , '' 133 | , 'a {' 134 | , ' color: #00B7FF;' 135 | , '}' 136 | ].join(eol); 137 | 138 | /** 139 | * Default stylus template. 140 | */ 141 | 142 | var stylus = [ 143 | 'body' 144 | , ' padding: 50px' 145 | , ' font: 14px "Lucida Grande", Helvetica, Arial, sans-serif' 146 | , 'a' 147 | , ' color: #00B7FF' 148 | ].join(eol); 149 | 150 | /** 151 | * Public key 152 | */ 153 | var cert = [ 154 | '-----BEGIN CERTIFICATE-----' 155 | , 'MIICHzCCAYgCCQCPPSUAa8QZojANBgkqhkiG9w0BAQUFADBUMQswCQYDVQQGEwJS' 156 | , 'VTETMBEGA1UECBMKU29tZS1TdGF0ZTENMAsGA1UEBxMET21zazEhMB8GA1UEChMY' 157 | , 'SW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMB4XDTExMDQwOTEwMDY0NVoXDTExMDUw' 158 | , 'OTEwMDY0NVowVDELMAkGA1UEBhMCUlUxEzARBgNVBAgTClNvbWUtU3RhdGUxDTAL' 159 | , 'BgNVBAcTBE9tc2sxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCB' 160 | , 'nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1bn25sPkv46wl70BffxradlkRd/x' 161 | , 'p5Xf8HDhPSfzNNctERYslXT2fX7Dmfd5w1XTVqqGqJ4izp5VewoVOHA8uavo3ovp' 162 | , 'gNWasil5zADWaM1T0nnV0RsFbZWzOTmm1U3D48K8rW3F5kOZ6f4yRq9QT1gF/gN7' 163 | , '5Pt494YyYyJu/a8CAwEAATANBgkqhkiG9w0BAQUFAAOBgQBuRZisIViI2G/R+w79' 164 | , 'vk21TzC/cJ+O7tKsseDqotXYTH8SuimEH5IWcXNgnWhNzczwN8s2362NixyvCipV' 165 | , 'yd4wzMpPbjIhnWGM0hluWZiK2RxfcqimIBjDParTv6CMUIuwGQ257THKY8hXGg7j' 166 | , 'Uws6Lif3P9UbsuRiYPxMgg98wg==' 167 | , '-----END CERTIFICATE-----' 168 | ].join(eol); 169 | 170 | /** 171 | * Certificate signing request 172 | */ 173 | var csr = [ 174 | '-----BEGIN CERTIFICATE REQUEST-----' 175 | , 'MIIBkzCB/QIBADBUMQswCQYDVQQGEwJSVTETMBEGA1UECBMKU29tZS1TdGF0ZTEN' 176 | , 'MAsGA1UEBxMET21zazEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRk' 177 | , 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDVufbmw+S/jrCXvQF9/Gtp2WRF' 178 | , '3/Gnld/wcOE9J/M01y0RFiyVdPZ9fsOZ93nDVdNWqoaoniLOnlV7ChU4cDy5q+je' 179 | , 'i+mA1ZqyKXnMANZozVPSedXRGwVtlbM5OabVTcPjwrytbcXmQ5np/jJGr1BPWAX+' 180 | , 'A3vk+3j3hjJjIm79rwIDAQABoAAwDQYJKoZIhvcNAQEFBQADgYEAiNWhz6EppIVa' 181 | , 'FfUaB3sLeqfamb9tg9kBHtvqj/FJni0snqms0kPWaTySEPHZF0irIb7VVdq/sVCb' 182 | , '3gseMVSyoDvPJ4lHC3PXqGQ7kM1mIPhDnR/4HDA3BhlGhTXSDIHgZnvI+HMBdsyC' 183 | , 'hC3dz5odyKqe4nmoofomALkBL9t4H8s=' 184 | , '-----END CERTIFICATE REQUEST-----' 185 | ].join(eol); 186 | 187 | /** 188 | * Private key 189 | */ 190 | var key = [ 191 | , '-----BEGIN RSA PRIVATE KEY-----' 192 | , 'MIICXAIBAAKBgQDVufbmw+S/jrCXvQF9/Gtp2WRF3/Gnld/wcOE9J/M01y0RFiyV' 193 | , 'dPZ9fsOZ93nDVdNWqoaoniLOnlV7ChU4cDy5q+jei+mA1ZqyKXnMANZozVPSedXR' 194 | , 'GwVtlbM5OabVTcPjwrytbcXmQ5np/jJGr1BPWAX+A3vk+3j3hjJjIm79rwIDAQAB' 195 | , 'AoGAAv2QI9h32epQND9TxwSCKD//dC7W/cZOFNovfKCTeZjNK6EIzKqPTGA6smvR' 196 | , 'C1enFl5adf+IcyWqAoe4lkqTvurIj+2EhtXdQ8DBlVuXKr3xvEFdYxXPautdTCF6' 197 | , 'KbXEyS/s1TZCRFjYftvCrXxc3pK45AQX/wg7z1K+YB5pyIECQQD0OJvLoxLYoXAc' 198 | , 'FZraIOZiDsEbGuSHqoCReFXH75EC3+XGYkH2bQ/nSIZ0h1buuwQ/ylKXOlTPT3Qt' 199 | , 'Xm1OQEBvAkEA4AjWsIO/rRpOm/Q2aCrynWMpoUXTZSbL2yGf8pxp/+8r2br5ier0' 200 | , 'M1LeBb/OPY1+k39NWLXxQoo64xoSFYk2wQJAd2wDCwX4HkR7HNCXw1hZL9QFK6rv' 201 | , '20NN0VSlpboJD/3KT0MW/FiCcVduoCbaJK0Au+zEjDyy4hj5N4I4Mw6KMwJAXVAx' 202 | , 'I+psTsxzS4/njXG+BgIEl/C+gRYsuMQDnAi8OebDq/et8l0Tg8ETSu++FnM18neG' 203 | , 'ntmBeMacinUUbTXuwQJBAJp/onZdsMzeVulsGrqR1uS+Lpjc5Q1gt5ttt2cxj91D' 204 | , 'rio48C/ZvWuKNE8EYj2ALtghcVKRvgaWfOxt2GPguGg=' 205 | , '-----END RSA PRIVATE KEY-----' 206 | ].join(eol); 207 | 208 | /** 209 | * App template. 210 | */ 211 | 212 | var app = [ 213 | '' 214 | , '/**' 215 | , ' * Module dependencies.' 216 | , ' */' 217 | , '' 218 | , 'var express = require(\'express-spdy\')' 219 | , ' , routes = require(\'./routes\')' 220 | , ' , fs = require(\'fs\')' 221 | , ' , host = \'https://localhost:3000/\';' 222 | , '' 223 | 224 | , 'var app = module.exports = express.createServer({' 225 | , ' key: fs.readFileSync(__dirname + \'/keys/spdy-key.pem\'),' 226 | , ' cert: fs.readFileSync(__dirname + \'/keys/spdy-cert.pem\'),' 227 | , ' ca: fs.readFileSync(__dirname + \'/keys/spdy-csr.pem\'),' 228 | , ' NPNProtocols: [\'spdy/2\', \'http/1.1\'],' 229 | , ' push: awesome_push' 230 | , '});' 231 | , '' 232 | 233 | , 'function awesome_push(pusher) {' 234 | , ' // Only push in response to the first request' 235 | , ' if (pusher.streamID > 1) return;' 236 | , '' 237 | , ' // Push resources that can be deferred until after the response is' 238 | , ' // sent' 239 | , ' pusher.pushLater([' 240 | , ' local_path_and_url("stylesheets/style.css")' 241 | , ' ]);' 242 | , '}' 243 | , '' 244 | 245 | , 'function local_path_and_url(relative_path) {' 246 | , ' return [' 247 | , ' "public/" + relative_path,' 248 | , ' host + relative_path' 249 | , ' ];' 250 | , '}' 251 | , '' 252 | 253 | , '// Configuration' 254 | , '' 255 | , 'app.configure(function(){' 256 | , ' app.set(\'views\', __dirname + \'/views\');' 257 | , ' app.set(\'view engine\', \':TEMPLATE\');' 258 | , ' app.use(express.bodyParser());' 259 | , ' app.use(express.methodOverride());{sess}{css}' 260 | , ' app.use(app.router);' 261 | , ' app.use(express.static(__dirname + \'/public\'));' 262 | , '});' 263 | , '' 264 | , 'app.configure(\'development\', function(){' 265 | , ' app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));' 266 | , '});' 267 | , '' 268 | , 'app.configure(\'production\', function(){' 269 | , ' app.use(express.errorHandler());' 270 | , '});' 271 | , '' 272 | , '// Routes' 273 | , '' 274 | , 'app.get(\'/\', routes.index);' 275 | , '' 276 | , 'app.listen(3000, function(){' 277 | , ' console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env);' 278 | , '});' 279 | , '' 280 | ].join(eol); 281 | 282 | // Parse arguments 283 | 284 | var args = process.argv.slice(2) 285 | , path = '.'; 286 | 287 | while (args.length) { 288 | var arg = args.shift(); 289 | switch (arg) { 290 | case '-h': 291 | case '--help': 292 | abort(usage); 293 | break; 294 | case '-v': 295 | case '--version': 296 | abort(version); 297 | break; 298 | case '-s': 299 | case '--session': 300 | case '--sessions': 301 | sessions = true; 302 | break; 303 | case '-c': 304 | case '--css': 305 | args.length 306 | ? (cssEngine = args.shift()) 307 | : abort('--css requires an argument'); 308 | break; 309 | case '-t': 310 | case '--template': 311 | args.length 312 | ? (templateEngine = args.shift()) 313 | : abort('--template requires an argument'); 314 | break; 315 | default: 316 | path = arg; 317 | } 318 | } 319 | 320 | // Generate application 321 | 322 | (function createApplication(path) { 323 | emptyDirectory(path, function(empty){ 324 | if (empty) { 325 | createApplicationAt(path); 326 | } else { 327 | confirm('destination is not empty, continue? ', function(ok){ 328 | if (ok) { 329 | process.stdin.destroy(); 330 | createApplicationAt(path); 331 | } else { 332 | abort('aborting'); 333 | } 334 | }); 335 | } 336 | }); 337 | })(path); 338 | 339 | /** 340 | * Create application at the given directory `path`. 341 | * 342 | * @param {String} path 343 | */ 344 | 345 | function createApplicationAt(path) { 346 | console.log(); 347 | process.on('exit', function(){ 348 | console.log(); 349 | console.log(' dont forget to install dependencies:'); 350 | console.log(' $ cd %s && npm install', path); 351 | console.log(); 352 | }); 353 | 354 | mkdir(path, function(){ 355 | mkdir(path + '/public'); 356 | mkdir(path + '/public/javascripts'); 357 | mkdir(path + '/public/images'); 358 | mkdir(path + '/public/stylesheets', function(){ 359 | switch (cssEngine) { 360 | case 'stylus': 361 | write(path + '/public/stylesheets/style.styl', stylus); 362 | break; 363 | default: 364 | write(path + '/public/stylesheets/style.css', css); 365 | } 366 | }); 367 | 368 | mkdir(path + '/routes', function(){ 369 | write(path + '/routes/index.js', index); 370 | }); 371 | 372 | mkdir(path + '/views', function(){ 373 | switch (templateEngine) { 374 | case 'ejs': 375 | write(path + '/views/layout.ejs', ejsLayout); 376 | write(path + '/views/index.ejs', ejsIndex); 377 | break; 378 | case 'jade': 379 | write(path + '/views/layout.jade', jadeLayout); 380 | write(path + '/views/index.jade', jadeIndex); 381 | break; 382 | } 383 | }); 384 | 385 | // CSS Engine support 386 | switch (cssEngine) { 387 | case 'stylus': 388 | app = app.replace('{css}', eol + ' app.use(require(\'stylus\').middleware({ src: __dirname + \'/public\' }));'); 389 | break; 390 | default: 391 | app = app.replace('{css}', ''); 392 | } 393 | 394 | // Session support 395 | app = app.replace('{sess}', sessions 396 | ? eol + ' app.use(express.cookieParser());' + eol + ' app.use(express.session({ secret: \'your secret here\' }));' 397 | : ''); 398 | 399 | // Template support 400 | app = app.replace(':TEMPLATE', templateEngine); 401 | 402 | // package.json 403 | var json = '{' + eol; 404 | json += ' "name": "application-name"' + eol; 405 | json += ' , "version": "0.0.1"' + eol; 406 | json += ' , "private": true' + eol; 407 | json += ' , "dependencies": {' + eol; 408 | json += ' "express-spdy": "' + version + '"' + eol; 409 | if (cssEngine) json += ' , "' + cssEngine + '": ">= 0.0.1"' + eol; 410 | if (templateEngine) json += ' , "' + templateEngine + '": ">= 0.0.1"' + eol; 411 | json += ' }' + eol; 412 | json += '}'; 413 | 414 | 415 | write(path + '/package.json', json); 416 | write(path + '/app.js', app); 417 | }); 418 | 419 | // SSL support 420 | mkdir(path + '/keys', function(){ 421 | write(path + '/keys/spdy-key.pem', key); 422 | write(path + '/keys/spdy-cert.pem', cert); 423 | write(path + '/keys/spdy-csr.pem', csr); 424 | }); 425 | } 426 | 427 | /** 428 | * Check if the given directory `path` is empty. 429 | * 430 | * @param {String} path 431 | * @param {Function} fn 432 | */ 433 | 434 | function emptyDirectory(path, fn) { 435 | fs.readdir(path, function(err, files){ 436 | if (err && 'ENOENT' != err.code) throw err; 437 | fn(!files || !files.length); 438 | }); 439 | } 440 | 441 | /** 442 | * echo str > path. 443 | * 444 | * @param {String} path 445 | * @param {String} str 446 | */ 447 | 448 | function write(path, str) { 449 | fs.writeFile(path, str); 450 | console.log(' \x1b[36mcreate\x1b[0m : ' + path); 451 | } 452 | 453 | /** 454 | * Prompt confirmation with the given `msg`. 455 | * 456 | * @param {String} msg 457 | * @param {Function} fn 458 | */ 459 | 460 | function confirm(msg, fn) { 461 | prompt(msg, function(val){ 462 | fn(/^ *y(es)?/i.test(val)); 463 | }); 464 | } 465 | 466 | /** 467 | * Prompt input with the given `msg` and callback `fn`. 468 | * 469 | * @param {String} msg 470 | * @param {Function} fn 471 | */ 472 | 473 | function prompt(msg, fn) { 474 | // prompt 475 | if (' ' == msg[msg.length - 1]) { 476 | process.stdout.write(msg); 477 | } else { 478 | console.log(msg); 479 | } 480 | 481 | // stdin 482 | process.stdin.setEncoding('ascii'); 483 | process.stdin.once('data', function(data){ 484 | fn(data); 485 | }).resume(); 486 | } 487 | 488 | /** 489 | * Mkdir -p. 490 | * 491 | * @param {String} path 492 | * @param {Function} fn 493 | */ 494 | 495 | function mkdir(path, fn) { 496 | mkdirp(path, 0755, function(err){ 497 | if (err) throw err; 498 | console.log(' \033[36mcreate\033[0m : ' + path); 499 | fn && fn(); 500 | }); 501 | } 502 | 503 | /** 504 | * Exit with the given `str`. 505 | * 506 | * @param {String} str 507 | */ 508 | 509 | function abort(str) { 510 | console.error(str); 511 | process.exit(1); 512 | } 513 | -------------------------------------------------------------------------------- /express.js: -------------------------------------------------------------------------------- 1 | var express = require('express') 2 | , expressCreateServer = express.createServer 3 | , SPDYServer = require('./spdy'); 4 | 5 | var exports = module.exports = express; 6 | 7 | exports.createServer = function(options){ 8 | if ('object' == typeof options) { 9 | return new SPDYServer(options, Array.prototype.slice.call(arguments, 1)); 10 | } else { 11 | return expressCreateServer(Array.prototype.slice.call(arguments)); 12 | } 13 | }; 14 | 15 | exports.SPDYServer = SPDYServer; 16 | 17 | var http = require('http') 18 | , res = http.ServerResponse.prototype 19 | , spdy = require('spdy') 20 | , spdy_res = spdy.Response.prototype; 21 | 22 | // TODO: other methods? 23 | spdy_res.send = res.send; 24 | spdy_res.header = res.header; 25 | spdy_res.contentType = res.contentType; 26 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./express.js'); 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Chris Strom (http://eeecomputes.com)", 3 | "name": "express-spdy", 4 | "description": "SPDY-ize express.js sites.", 5 | "version": "0.1.3", 6 | "homepage": "https://github.com/eee-c/express-spdy", 7 | "repository": { 8 | "type": "git", 9 | "url": "git://github.com/eee-c/express-spdy.git" 10 | }, 11 | "main": "index.js", 12 | "engines": { 13 | "node": ">= 0.6.0 < 0.7.0" 14 | }, 15 | "dependencies": { 16 | "express": ">= 2.5.0 <2.6.0", 17 | "connect-spdy": ">= 0.1.0", 18 | "spdy": ">= 0.1.0 < 1.0.0", 19 | "mkdirp": "0.3.0" 20 | }, 21 | "devDependencies": {}, 22 | "bin": { "express-spdy": "./bin/express-spdy" } 23 | } 24 | -------------------------------------------------------------------------------- /spdy.js: -------------------------------------------------------------------------------- 1 | 2 | /*! 3 | * Express - SPDYServer 4 | * Copyright(c) 2010 TJ Holowaychuk 5 | * MIT Licensed 6 | */ 7 | 8 | /** 9 | * Module dependencies. 10 | */ 11 | 12 | var connect = require('connect-spdy') 13 | , express = require('express') 14 | , HTTPServer = express.HTTPServer 15 | , spdy = require('spdy') 16 | , spdy_res = spdy.Response.prototype 17 | , http = require('http') 18 | , res = http.ServerResponse.prototype; 19 | 20 | /** 21 | * Expose `SPDYServer`. 22 | */ 23 | 24 | exports = module.exports = SPDYServer; 25 | 26 | /** 27 | * Server proto. 28 | */ 29 | 30 | var app = SPDYServer.prototype; 31 | 32 | /** 33 | * Initialize a new `SPDYServer` with the 34 | * given `options`, and optional `middleware`. 35 | * 36 | * @param {Object} options 37 | * @param {Array} middleware 38 | * @api public 39 | */ 40 | 41 | function SPDYServer(options, middleware){ 42 | connect.SPDYServer.call(this, options, []); 43 | this.init(middleware); 44 | }; 45 | 46 | /** 47 | * Inherit from `connect.SPDYServer`. 48 | */ 49 | 50 | app.__proto__ = connect.SPDYServer.prototype; 51 | 52 | // mixin HTTPServer methods 53 | 54 | Object.keys(HTTPServer.prototype).forEach(function(method){ 55 | app[method] = HTTPServer.prototype[method]; 56 | }); 57 | 58 | // TODO: don't hard-code which methods get mixed-in 59 | spdy_res.partial = res.partial; 60 | spdy_res.render = res.render; 61 | spdy_res._render = res._render; 62 | 63 | // TODO: mixin 64 | // spdy_res.send = res.send; 65 | // spdy_res.header = res.header; 66 | // spdy_res.contentType = res.contentType; 67 | // ... 68 | -------------------------------------------------------------------------------- /test/response-test.js: -------------------------------------------------------------------------------- 1 | var vows = require('vows'), 2 | assert = require('assert'), 3 | express = require('..'), 4 | spdy = require('spdy'), 5 | fs = require('fs'), 6 | tls = require('tls'); 7 | 8 | // Common options 9 | 10 | var PORT = 23432; 11 | 12 | var options = { 13 | key: fs.readFileSync(__dirname + '/../node_modules/spdy/keys/spdy-key.pem'), 14 | cert: fs.readFileSync(__dirname + '/../node_modules/spdy/keys/spdy-cert.pem'), 15 | ca: fs.readFileSync(__dirname + '/../node_modules/spdy/keys/spdy-csr.pem'), 16 | npnProtocols: ['spdy/2'], 17 | debug: true 18 | }; 19 | 20 | // Globals to pass information between batches 21 | 22 | var connection, 23 | spdy_request; 24 | 25 | vows.describe('Express SPDY response'). 26 | addBatch({ 27 | '[setup]': { 28 | 'establish a simple SSL express server': function() { 29 | var server = express.createServer(options); 30 | 31 | server.get('/', function(req, res){ 32 | res.send('wahoo'); 33 | }); 34 | 35 | server.listen(PORT); 36 | return true; 37 | } 38 | } 39 | }). 40 | addBatch({ 41 | '[setup]': { 42 | 'open an SSL connection to the server': function() { 43 | connection = tls.connect(PORT, 'localhost', options, function() {}); 44 | }, 45 | 'craft a simple SPDY request': function() { 46 | spdy_request = spdy.createControlFrame( 47 | spdy.createZLib(), 48 | { type: spdy.enums.SYN_STREAM, streamID: 1, flags: 0 }, 49 | { version: 'HTTP/1.1', url: '/', method: 'GET' } 50 | ); 51 | } 52 | } 53 | }). 54 | addBatch({ 55 | 'Sending the request': { 56 | topic: function () { 57 | var callback = this.callback; 58 | 59 | var parser = spdy.createParser(spdy.createZLib()); 60 | connection.pipe(parser); 61 | 62 | parser.on('cframe', function(cframe) { 63 | if (cframe.headers.type == spdy.enums.SYN_REPLY) { 64 | callback(null, cframe); 65 | } 66 | }); 67 | 68 | connection.write(spdy_request, function(){}); 69 | }, 70 | 'should get a response with no Keep-Alive': function(cframe) { 71 | assert.notEqual(cframe, undefined); 72 | assert.notEqual(cframe.data, undefined); 73 | assert.notEqual(cframe.data.nameValues, undefined); 74 | assert.equal(cframe.data.nameValues['connection'], undefined); 75 | } 76 | } 77 | }). 78 | export(module); 79 | --------------------------------------------------------------------------------