8889841cPK[wtest/client-test.jsnuW+A/* eslint-env mocha */ var assert = require('assert') var https = require('https') var http = require('http') var util = require('util') var fixtures = require('./fixtures') var spdy = require('../') // Node.js 0.10 and 0.12 support Object.assign = process.versions.modules >= 46 ? Object.assign // eslint-disable-next-line : util._extend describe('SPDY Client', function () { describe('regular', function () { fixtures.everyConfig(function (protocol, alpn, version, plain) { var server var agent var hmodule beforeEach(function (done) { hmodule = plain ? http : https var options = Object.assign({ spdy: { plain: plain } }, fixtures.keys) server = spdy.createServer(options, function (req, res) { var body = '' req.on('data', function (chunk) { body += chunk }) req.on('end', function () { res.writeHead(200, req.headers) res.addTrailers({ trai: 'ler' }) var push = res.push('/push', { request: { push: 'yes' } }, function (err) { assert(!err) push.end('push') push.on('error', function () { }) res.end(body || 'okay') }) }) }) server.listen(fixtures.port, function () { agent = spdy.createAgent({ rejectUnauthorized: false, port: fixtures.port, spdy: { plain: plain, protocol: plain ? alpn : null, protocols: [alpn] } }) done() }) }) afterEach(function (done) { var waiting = 2 agent.close(next) server.close(next) function next () { if (--waiting === 0) { done() } } }) it('should send GET request', function (done) { var req = hmodule.request({ agent: agent, method: 'GET', path: '/get', headers: { a: 'b' } }, function (res) { assert.strictEqual(res.statusCode, 200) assert.strictEqual(res.headers.a, 'b') fixtures.expectData(res, 'okay', done) }) req.end() }) it('should send POST request', function (done) { var req = hmodule.request({ agent: agent, method: 'POST', path: '/post', headers: { post: 'headers' } }, function (res) { assert.strictEqual(res.statusCode, 200) assert.strictEqual(res.headers.post, 'headers') fixtures.expectData(res, 'post body', done) }) agent._spdyState.socket.once(plain ? 'connect' : 'secureConnect', function () { req.end('post body') }) }) it('should receive PUSH_PROMISE', function (done) { var req = hmodule.request({ agent: agent, method: 'GET', path: '/get' }, function (res) { assert.strictEqual(res.statusCode, 200) res.resume() }) req.on('push', function (push) { assert.strictEqual(push.path, '/push') assert.strictEqual(push.headers.push, 'yes') push.resume() push.once('end', done) }) req.end() }) it('should receive trailing headers', function (done) { var req = hmodule.request({ agent: agent, method: 'GET', path: '/get' }, function (res) { assert.strictEqual(res.statusCode, 200) res.on('trailers', function (headers) { assert.strictEqual(headers.trai, 'ler') fixtures.expectData(res, 'okay', done) }) }) req.end() }) }) }) describe('x-forwarded-for', function () { fixtures.everyConfig(function (protocol, alpn, version, plain) { var server var agent var hmodule // The underlying spdy Connection created by the agent. var connection beforeEach(function (done) { hmodule = plain ? http : https var options = Object.assign({ spdy: { plain: plain, 'x-forwarded-for': true } }, fixtures.keys) server = spdy.createServer(options, function (req, res) { res.writeHead(200, req.headers) res.end() }) server.listen(fixtures.port, function () { agent = spdy.createAgent({ rejectUnauthorized: false, port: fixtures.port, spdy: { 'x-forwarded-for': '1.2.3.4', plain: plain, protocol: plain ? alpn : null, protocols: [alpn] } }) // Once aagent has connection, keep a copy for testing. agent.once('_connect', function () { connection = agent._spdyState.connection done() }) }) }) afterEach(function (done) { var waiting = 2 agent.close(next) server.close(next) function next () { if (--waiting === 0) { done() } } }) it('should send x-forwarded-for', function (done) { var req = hmodule.request({ agent: agent, method: 'GET', path: '/get' }, function (res) { assert.strictEqual(res.statusCode, 200) assert.strictEqual(res.headers['x-forwarded-for'], '1.2.3.4') res.resume() res.once('end', done) }) req.end() }) it('agent should emit connection level errors', function (done) { agent.once('error', function (err) { assert.strictEqual(err.message, 'mock error') done() }) connection.emit('error', new Error('mock error')) }) }) }) }) PK[6RRtest/fixtures.jsnuW+A/* eslint-env mocha */ 'use strict' var assert = require('assert') exports.port = 23433 exports.keys = { key: '-----BEGIN RSA PRIVATE KEY-----\n' + 'MIIEogIBAAKCAQEA1ARXSoyizYSnHDYickxX4x2UG/8uNWnQWKlWR97NAwRsspN6\n' + 'aFF1+LnyN9bvLNnhxIowcYy68+LpZ7pYAQgBZSyAhnF1S4qz2w/rxH4CNn96B/je\n' + 'vQGo3e8vIQ8ChhfuYvGAtTEYJzW8aRoxWSPcukZZdxPQ1Wgbhd9DSXhgkUnkEEET\n' + 'owyn8ufQFRnQHfc9Fn5DrJilI7vD+ZyRU3gZoBj2GVMQuxJLqQEHy2XsJ6ZWTea/\n' + 'EfK93XfDyY7ZxyeK0ZdWCVoTqw9QNJSkGjesCBkcY4Rjxi9LbLJwW3Es4wgW4N4Y\n' + 'cltfygjltSis+RVKJyGeDqTWAxceih3mlkdGIQIDAQABAoIBAB6akc8dBdMMtuKH\n' + 'nelJw9XwyxRPfWgQYhaqOt4c9xLcbKRKTX0JZTIGBUSyLcwXl1M7b0q0ubfCpVZn\n' + 'u5RKh4kHJ3ZAomHJH7UbUzkFx2P+eqrz7ZLyzmFayT7IX+DjS3HU0nNVJttiElRJ\n' + 'h54KYy4wQXHC1n43jOGCHMBaM/ZEpO3xA7PLfVD/BpYLzL+FAYoFBb/x2buLv8Mg\n' + 'D6QAWkS70mu8ER13NapKjg6PUsYPxHYU30BjGMTXw95Iz5PSAK8+/xQ6YaW7MEVM\n' + 'twxxfJfZ+9u9nJMfJANqxCi6iZ6ft/e5cbhvNhV/X97XeoPWxqSpx98M6BC/vvBc\n' + 'UjSmaRECgYEA4NH8Y20zC8zF4ALcBgqgrx+U9upov2UGa+kjS1/So4r/dpG4T8pT\n' + 'T2tMW6zR5qe7g11kgJm0oI/I6x9P2qwFJONO3MdLYVKd2mSxG2fniCktLg2j6BAX\n' + 'QTt5zjIEWvhRP2vkrS8gAaJbVMLTMg4s374bE/IdKT+c59tYpcVaXXMCgYEA8WvJ\n' + 'dfPXoagEgaHRd++R2COMG19euOTFRle0MSq+S9ZeeSe9ejb9CIpWYZ3WVviKvf+E\n' + 'zksmKTZJnig5pGEgU+2ka1C9PthCGlTlQagD6Ey4hblQgi+pOFgBjE9Yn3FxfppH\n' + '25ICXNY89EF6klEqKV67E/5O+nBZo+Y2TM4YKRsCgYAaEV8RbEUB9kFvYwV+Eddl\n' + '1uSf6LgykRU4h/TWtYqn+eL7LZRQdCZKzCczbgt8kjBU4AxaOPhPsbxbPus0cMO7\n' + '7jtjsBwWcczp2MkMY3TePeAGOgCqVMtNfgb2mKgWoDpTf0ApsJAmgFvUrS5t3GTp\n' + 'oJJlMqqc8MpRvAZAWmzK7wKBgEVBFlmvyXumJyTItr4hC0VlbRutEA8aET1Mi3RP\n' + 'Pqeipxc6PzB/9bYtePonvQTV53b5ha9n/1pzKEsmXuK4uf1ZfoEKeD8+6jeDgwCC\n' + 'ohxRZd12e5Hc+j4fgNIvMM0MTfJzb4mdKPBYxMOMxQyUG/QiKKhjm2RcNlq9/3Wo\n' + '6WVhAoGAG4QPWoE4ccFECp8eyGw8rjE45y5uqUI/f/RssX7bnKbCRY0otDsPlJd6\n' + 'Kf0XFssLnYsCXO+ua03gw2N+2mrcsuA5FXHmQMrbfnuojHIVY05nt4Wa5iqV/gqH\n' + 'PJXWyOgD+Kd6eR/cih/SCoKl4tSGCSJG5TDEpMt+r8EJkCXJ7Fw=\n' + '-----END RSA PRIVATE KEY-----', cert: '-----BEGIN CERTIFICATE-----\n' + 'MIICuTCCAaOgAwIBAgIDAQABMAsGCSqGSIb3DQEBCzAUMRIwEAYDVQQDFglub2Rl\n' + 'LnNwZHkwHhcNNjkwMTAxMDAwMDAwWhcNMjUwNzA2MDUwMzQzWjAUMRIwEAYDVQQD\n' + 'Fglub2RlLnNwZHkwggEgMAsGCSqGSIb3DQEBAQOCAQ8AMIIBCgKCAQEA1ARXSoyi\n' + 'zYSnHDYickxX4x2UG/8uNWnQWKlWR97NAwRsspN6aFF1+LnyN9bvLNnhxIowcYy6\n' + '8+LpZ7pYAQgBZSyAhnF1S4qz2w/rxH4CNn96B/jevQGo3e8vIQ8ChhfuYvGAtTEY\n' + 'JzW8aRoxWSPcukZZdxPQ1Wgbhd9DSXhgkUnkEEETowyn8ufQFRnQHfc9Fn5DrJil\n' + 'I7vD+ZyRU3gZoBj2GVMQuxJLqQEHy2XsJ6ZWTea/EfK93XfDyY7ZxyeK0ZdWCVoT\n' + 'qw9QNJSkGjesCBkcY4Rjxi9LbLJwW3Es4wgW4N4YcltfygjltSis+RVKJyGeDqTW\n' + 'Axceih3mlkdGIQIDAQABoxowGDAWBgNVHREEDzANggsqLm5vZGUuc3BkeTALBgkq\n' + 'hkiG9w0BAQsDggEBALn2FQSDMsyu+oqUnJgTVdGpnzKmfXoBPlQuznRdibri8ABO\n' + 'kOo8FC72Iy6leVSsB26KtAdhpURZ3mv1Oyt4cGeeyQln2Olzp5flIos+GqYSztAq\n' + '5ZnrzTLLlip7KHkmastYRXhEwTLmo2JCU8RkRP1X/m1xONF/YkURxmqj6cQTahPY\n' + 'FzzLP1clW3arJwPlUcKKby6WpxO5MihYEliheBr7fL2TDUA96eG+B/SKxvwaGF2v\n' + 'gWF8rg5prjPaLW8HH3Efq59AimFqUVQ4HtcJApjLJDYUKlvsMNMvBqh/pQRRPafj\n' + '0Cp8dyS45sbZ2RgXdyfl6gNEj+DiPbaFliIuFmM=\n' + '-----END CERTIFICATE-----' } function expectData (stream, expected, callback) { var actual = '' stream.on('data', function (chunk) { actual += chunk }) stream.on('end', function () { assert.strictEqual(actual, expected) callback() }) } exports.expectData = expectData exports.everyProtocol = function everyProtocol (body) { var protocols = [ { protocol: 'http2', alpn: 'h2', version: 4 }, { protocol: 'spdy', alpn: 'spdy/3.1', version: 3.1 }, { protocol: 'spdy', alpn: 'spdy/3', version: 3 }, { protocol: 'spdy', alpn: 'spdy/2', version: 2 } ] protocols.forEach(function (protocol) { describe(protocol.alpn, function () { body(protocol.protocol, protocol.alpn, protocol.version) }) }) } exports.everyConfig = function everyConfig (body) { exports.everyProtocol(function (protocol, alpn, version) { if (alpn === 'spdy/2') { return } [false, true].forEach(function (plain) { describe(plain ? 'plain mode' : 'ssl mode', function () { body(protocol, alpn, version, plain) }) }) }) } PK[zO..test/server-test.jsnuW+A/* eslint-env mocha */ var assert = require('assert') var tls = require('tls') var net = require('net') var https = require('https') var transport = require('spdy-transport') var util = require('util') var fixtures = require('./fixtures') var spdy = require('../') describe('SPDY Server', function () { fixtures.everyConfig(function (protocol, alpn, version, plain) { var server var client beforeEach(function (done) { server = spdy.createServer(Object.assign({ spdy: { 'x-forwarded-for': true, plain: plain } }, fixtures.keys)) server.listen(fixtures.port, function () { var socket = (plain ? net : tls).connect({ rejectUnauthorized: false, port: fixtures.port, ALPNProtocols: [alpn] }, function () { client = transport.connection.create(socket, { protocol: protocol, isServer: false }) client.start(version) done() }) }) }) afterEach(function (done) { client.socket.destroy() server.close(done) }) it('should process GET request', function (done) { var stream = client.request({ method: 'GET', path: '/get', headers: { a: 'b' } }, function (err) { assert(!err) stream.on('error', (err) => { done(err) }) stream.on('response', function (status, headers) { assert.strictEqual(status, 200) assert.strictEqual(headers.ok, 'yes') fixtures.expectData(stream, 'response', done) }) stream.end() }) server.on('request', function (req, res) { assert.strictEqual(req.isSpdy, res.isSpdy) assert.strictEqual(req.spdyVersion, res.spdyVersion) assert(req.isSpdy) if (!plain) { assert(req.socket.encrypted) assert(req.socket.getPeerCertificate()) } // Auto-detection if (version === 3.1) { assert(req.spdyVersion >= 3 && req.spdyVersion <= 3.1) } else { assert.strictEqual(req.spdyVersion, version) } assert(req.spdyStream) assert(res.spdyStream) assert.strictEqual(req.method, 'GET') assert.strictEqual(req.url, '/get') assert.deepStrictEqual(req.headers, { a: 'b', host: 'localhost' }) req.on('end', function () { res.writeHead(200, { ok: 'yes' }) res.end('response') assert(res.finished, 'res.finished should be set') }) req.resume() }) }) it('should process POST request', function (done) { var stream = client.request({ method: 'POST', path: '/post' }, function (err) { assert(!err) stream.on('response', function (status, headers) { assert.strictEqual(status, 200) assert.strictEqual(headers.ok, 'yes') fixtures.expectData(stream, 'response', next) }) stream.end('request') }) server.on('request', function (req, res) { assert.strictEqual(req.method, 'POST') assert.strictEqual(req.url, '/post') res.writeHead(200, { ok: 'yes' }) res.end('response') fixtures.expectData(req, 'request', next) }) var waiting = 2 function next () { if (--waiting === 0) { return done() } } }) it('should process expect-continue request', function (done) { var stream = client.request({ method: 'GET', path: '/get', headers: { Expect: '100-continue' } }, function (err) { assert(!err) stream.on('response', function (status, headers) { assert.strictEqual(status, 100) fixtures.expectData(stream, 'response', done) }) stream.end() }) server.on('request', function (req, res) { req.on('end', function () { res.end('response') }) req.resume() }) }) it('should emit `checkContinue` request', function (done) { var stream = client.request({ method: 'GET', path: '/get', headers: { Expect: '100-continue' } }, function (err) { assert(!err) stream.on('response', function (status, headers) { assert.strictEqual(status, 100) fixtures.expectData(stream, 'response', done) }) stream.end() }) server.on('checkContinue', function (req, res) { req.on('end', function () { res.writeContinue() res.end('response') }) req.resume() }) }) it('should send PUSH_PROMISE', function (done) { var stream = client.request({ method: 'POST', path: '/page' }, function (err) { assert(!err) stream.on('pushPromise', function (push) { assert.strictEqual(push.path, '/push') assert.strictEqual(push.headers.yes, 'push') fixtures.expectData(push, 'push', next) fixtures.expectData(stream, 'response', next) }) stream.end('request') }) server.on('request', function (req, res) { assert.strictEqual(req.method, 'POST') assert.strictEqual(req.url, '/page') res.writeHead(200, { ok: 'yes' }) var push = res.push('/push', { request: { yes: 'push' } }) push.end('push') res.end('response') fixtures.expectData(req, 'request', next) }) var waiting = 3 function next () { if (--waiting === 0) { return done() } } }) it('should receive trailing headers', function (done) { var stream = client.request({ method: 'POST', path: '/post' }, function (err) { assert(!err) stream.sendHeaders({ trai: 'ler' }) stream.end() stream.on('response', function (status, headers) { assert.strictEqual(status, 200) assert.strictEqual(headers.ok, 'yes') fixtures.expectData(stream, 'response', done) }) }) server.on('request', function (req, res) { var gotHeaders = false req.on('trailers', function (headers) { gotHeaders = true assert.strictEqual(headers.trai, 'ler') }) req.on('end', function () { assert(gotHeaders) res.writeHead(200, { ok: 'yes' }) res.end('response') }) req.resume() }) }) it('should call .writeHead() automatically', function (done) { var stream = client.request({ method: 'POST', path: '/post' }, function (err) { assert(!err) stream.on('response', function (status, headers) { assert.strictEqual(status, 300) fixtures.expectData(stream, 'response', done) }) stream.end() }) server.on('request', function (req, res) { req.on('end', function () { res.statusCode = 300 res.end('response') }) req.resume() }) }) it('should not crash on .writeHead() after socket close', function (done) { var stream = client.request({ method: 'POST', path: '/post' }, function (err) { assert(!err) setTimeout(function () { client.socket.destroy() }, 50) stream.on('error', function () {}) stream.end() }) server.on('request', function (req, res) { req.connection.on('close', function () { assert.doesNotThrow(function () { res.writeHead(200) res.end('response') }) done() }) }) }) it('should not crash on .push() after socket close', function (done) { var stream = client.request({ method: 'POST', path: '/post' }, function (err) { assert(!err) setTimeout(function () { client.socket.destroy() }, 50) stream.on('error', function () {}) stream.end() }) server.on('request', function (req, res) { req.connection.on('close', function () { assert.doesNotThrow(function () { assert.strictEqual(res.push('/push', {}), undefined) res.end('response') }) done() }) }) }) it('should end response after writing everything down', function (done) { var stream = client.request({ method: 'GET', path: '/post' }, function (err) { assert(!err) stream.on('response', function (status, headers) { assert.strictEqual(status, 200) fixtures.expectData(stream, 'hello world, what\'s up?', done) }) stream.end() }) server.on('request', function (req, res) { req.resume() res.writeHead(200) res.write('hello ') res.write('world') res.write(', what\'s') res.write(' up?') res.end() }) }) it('should handle x-forwarded-for', function (done) { client.sendXForwardedFor('1.2.3.4') var stream = client.request({ method: 'GET', path: '/post' }, function (err) { assert(!err) stream.resume() stream.on('end', done) stream.end() }) server.on('request', function (req, res) { assert.strictEqual(req.headers['x-forwarded-for'], '1.2.3.4') req.resume() res.end() }) }) it('should destroy request after end', function (done) { var stream = client.request({ method: 'POST', path: '/post' }, function (err) { assert(!err) }) stream.end() stream.on('error', function () {}) server.on('request', function (req, res) { res.end() res.destroy() res.socket.on('close', function () { done() }) }) }) }) it('should respond to http/1.1', function (done) { var server = spdy.createServer(fixtures.keys, function (req, res) { assert.strictEqual(req.isSpdy, res.isSpdy) assert.strictEqual(req.spdyVersion, res.spdyVersion) assert(!req.isSpdy) assert.strictEqual(req.spdyVersion, 1) res.writeHead(200) res.end() }) server.listen(fixtures.port, function () { var req = https.request({ agent: false, rejectUnauthorized: false, NPNProtocols: ['http/1.1'], port: fixtures.port, method: 'GET', path: '/' }, function (res) { assert.strictEqual(res.statusCode, 200) res.resume() res.on('end', function () { server.close(done) }) }) req.end() }) }) it('should support custom base', function (done) { function Pseuver (options, listener) { https.Server.call(this, options, listener) } util.inherits(Pseuver, https.Server) var server = spdy.createServer(Pseuver, fixtures.keys, function (req, res) { assert.strictEqual(req.isSpdy, res.isSpdy) assert.strictEqual(req.spdyVersion, res.spdyVersion) assert(!req.isSpdy) assert.strictEqual(req.spdyVersion, 1) res.writeHead(200) res.end() }) server.listen(fixtures.port, function () { var req = https.request({ agent: false, rejectUnauthorized: false, NPNProtocols: ['http/1.1'], port: fixtures.port, method: 'GET', path: '/' }, function (res) { assert.strictEqual(res.statusCode, 200) res.resume() res.on('end', function () { server.close(done) }) }) req.end() }) }) }) PK[E!E! README.mdnuW+A# SPDY Server for node.js [![Build Status](https://travis-ci.org/spdy-http2/node-spdy.svg?branch=master)](http://travis-ci.org/spdy-http2/node-spdy) [![NPM version](https://badge.fury.io/js/spdy.svg)](http://badge.fury.io/js/spdy) [![dependencies Status](https://david-dm.org/spdy-http2/node-spdy/status.svg?style=flat-square)](https://david-dm.org/spdy-http2/node-spdy) [![Standard - JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg?style=flat-square)](http://standardjs.com/) [![Waffle](https://img.shields.io/badge/track-waffle-blue.svg?style=flat-square)](https://waffle.io/spdy-http2/node-spdy) With this module you can create [HTTP2][0] / [SPDY][1] servers in node.js with natural http module interface and fallback to regular https (for browsers that don't support neither HTTP2, nor SPDY yet). This module named `spdy` but it [provides](https://github.com/indutny/node-spdy/issues/269#issuecomment-239014184) support for both http/2 (h2) and spdy (2,3,3.1). Also, `spdy` is compatible with Express. ## Usage ### Examples Server: ```javascript var spdy = require('spdy'), fs = require('fs'); var options = { // Private key key: fs.readFileSync(__dirname + '/keys/spdy-key.pem'), // Fullchain file or cert file (prefer the former) cert: fs.readFileSync(__dirname + '/keys/spdy-fullchain.pem'), // **optional** SPDY-specific options spdy: { protocols: [ 'h2', 'spdy/3.1', ..., 'http/1.1' ], plain: false, // **optional** // Parse first incoming X_FORWARDED_FOR frame and put it to the // headers of every request. // NOTE: Use with care! This should not be used without some proxy that // will *always* send X_FORWARDED_FOR 'x-forwarded-for': true, connection: { windowSize: 1024 * 1024, // Server's window size // **optional** if true - server will send 3.1 frames on 3.0 *plain* spdy autoSpdy31: false } } }; var server = spdy.createServer(options, function(req, res) { res.writeHead(200); res.end('hello world!'); }); server.listen(3000); ``` Client: ```javascript var spdy = require('spdy'); var https = require('https'); var agent = spdy.createAgent({ host: 'www.google.com', port: 443, // Optional SPDY options spdy: { plain: false, ssl: true, // **optional** send X_FORWARDED_FOR 'x-forwarded-for': '127.0.0.1' } }); https.get({ host: 'www.google.com', agent: agent }, function(response) { console.log('yikes'); // Here it goes like with any other node.js HTTP request // ... // And once we're done - we may close TCP connection to server // NOTE: All non-closed requests will die! agent.close(); }).end(); ``` Please note that if you use a custom agent, by default all connection-level errors will result in an uncaught exception. To handle these errors subscribe to the `error` event and re-emit the captured error: ```javascript var agent = spdy.createAgent({ host: 'www.google.com', port: 443 }).once('error', function (err) { this.emit(err); }); ``` #### Push streams It is possible to initiate [PUSH_PROMISE][5] to send content to clients _before_ the client requests it. ```javascript spdy.createServer(options, function(req, res) { var stream = res.push('/main.js', { status: 200, // optional method: 'GET', // optional request: { accept: '*/*' }, response: { 'content-type': 'application/javascript' } }); stream.on('error', function() { }); stream.end('alert("hello from push stream!");'); res.end(''); }).listen(3000); ``` [PUSH_PROMISE][5] may be sent using the `push()` method on the current response object. The signature of the `push()` method is: `.push('/some/relative/url', { request: {...}, response: {...} }, callback)` Second argument contains headers for both PUSH_PROMISE and emulated response. `callback` will receive two arguments: `err` (if any error is happened) and a [Duplex][4] stream as the second argument. Client usage: ```javascript var agent = spdy.createAgent({ /* ... */ }); var req = http.get({ host: 'www.google.com', agent: agent }, function(response) { }); req.on('push', function(stream) { stream.on('error', function(err) { // Handle error }); // Read data from stream }); ``` NOTE: You're responsible for the `stream` object once given it in `.push()` callback or `push` event. Hence ignoring `error` event on it will result in uncaught exception and crash your program. #### Trailing headers Server usage: ```javascript function (req, res) { // Send trailing headers to client res.addTrailers({ header1: 'value1', header2: 'value2' }); // On client's trailing headers req.on('trailers', function(headers) { // ... }); } ``` Client usage: ```javascript var req = http.request({ agent: spdyAgent, /* ... */ }).function (res) { // On server's trailing headers res.on('trailers', function(headers) { // ... }); }); req.write('stuff'); req.addTrailers({ /* ... */ }); req.end(); ``` #### Options All options supported by [tls][2] work with node-spdy. Additional options may be passed via `spdy` sub-object: * `plain` - if defined, server will ignore NPN and ALPN data and choose whether to use spdy or plain http by looking at first data packet. * `ssl` - if `false` and `options.plain` is `true`, `http.Server` will be used as a `base` class for created server. * `maxChunk` - if set and non-falsy, limits number of bytes sent in one DATA chunk. Setting it to non-zero value is recommended if you care about interleaving of outgoing data from multiple different streams. (defaults to 8192) * `protocols` - list of NPN/ALPN protocols to use (default is: `['h2','spdy/3.1', 'spdy/3', 'spdy/2','http/1.1', 'http/1.0']`) * `protocol` - use specific protocol if no NPN/ALPN ex In addition, * `maxStreams` - set "[maximum concurrent streams][3]" protocol option ### API API is compatible with `http` and `https` module, but you can use another function as base class for SPDYServer. ```javascript spdy.createServer( [base class constructor, i.e. https.Server], { /* keys and options */ }, // <- the only one required argument [request listener] ).listen([port], [host], [callback]); ``` Request listener will receive two arguments: `request` and `response`. They're both instances of `http`'s `IncomingMessage` and `OutgoingMessage`. But three custom properties are added to both of them: `isSpdy`, `spdyVersion`. `isSpdy` is `true` when the request was processed using HTTP2/SPDY protocols, it is `false` in case of HTTP/1.1 fallback. `spdyVersion` is either of: `2`, `3`, `3.1`, or `4` (for HTTP2). #### Contributors * [Fedor Indutny](https://github.com/indutny) * [Chris Strom](https://github.com/eee-c) * [François de Metz](https://github.com/francois2metz) * [Ilya Grigorik](https://github.com/igrigorik) * [Roberto Peon](https://github.com/grmocg) * [Tatsuhiro Tsujikawa](https://github.com/tatsuhiro-t) * [Jesse Cravens](https://github.com/jessecravens) #### LICENSE This software is licensed under the MIT License. Copyright Fedor Indutny, 2015. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. [0]: https://http2.github.io/ [1]: http://www.chromium.org/spdy [2]: http://nodejs.org/docs/latest/api/tls.html#tls.createServer [3]: https://httpwg.github.io/specs/rfc7540.html#SETTINGS_MAX_CONCURRENT_STREAMS [4]: https://iojs.org/api/stream.html#stream_class_stream_duplex [5]: https://httpwg.github.io/specs/rfc7540.html#PUSH_PROMISE PK[q~~ .travis.ymlnuW+Asudo: false language: node_js node_js: - "6" - "8" - "10" script: - npm run lint - npm test - npm run coverage PK[Y k k lib/spdy/response.jsnuW+A'use strict' // NOTE: Mostly copy paste from node exports.writeHead = function writeHead (statusCode, reason, obj) { var headers if (typeof reason === 'string') { // writeHead(statusCode, reasonPhrase[, headers]) this.statusMessage = reason } else { // writeHead(statusCode[, headers]) this.statusMessage = this.statusMessage || 'unknown' obj = reason } this.statusCode = statusCode if (this._headers) { // Slow-case: when progressive API and header fields are passed. if (obj) { var keys = Object.keys(obj) for (var i = 0; i < keys.length; i++) { var k = keys[i] if (k) this.setHeader(k, obj[k]) } } // only progressive api is used headers = this._renderHeaders() } else { // only writeHead() called headers = obj } if (statusCode === 204 || statusCode === 304 || (statusCode >= 100 && statusCode <= 199)) { // RFC 2616, 10.2.5: // The 204 response MUST NOT include a message-body, and thus is always // terminated by the first empty line after the header fields. // RFC 2616, 10.3.5: // The 304 response MUST NOT contain a message-body, and thus is always // terminated by the first empty line after the header fields. // RFC 2616, 10.1 Informational 1xx: // This class of status code indicates a provisional response, // consisting only of the Status-Line and optional headers, and is // terminated by an empty line. this._hasBody = false } // don't keep alive connections where the client expects 100 Continue // but we sent a final status; they may put extra bytes on the wire. if (this._expect_continue && !this._sent100) { this.shouldKeepAlive = false } // Implicit headers sent! this._header = true this._headerSent = true if (this.socket._handle) { this.socket._handle._spdyState.stream.respond(this.statusCode, headers) } } exports.end = function end (data, encoding, callback) { if (!this._headerSent) { this.writeHead(this.statusCode) } if (!this.socket._handle) { return } // Compatibility with Node.js core this.finished = true var self = this var handle = this.socket._handle handle._spdyState.ending = true this.socket.end(data, encoding, function () { self.constructor.prototype.end.call(self, '', 'utf8', callback) }) } exports.push = function push (path, headers, callback) { var frame = { path: path, method: headers.method ? headers.method.toString() : 'GET', status: headers.status ? parseInt(headers.status, 10) : 200, host: this._req.headers.host, headers: headers.request, response: headers.response } var stream = this.spdyStream return stream.pushPromise(frame, callback) } exports.writeContinue = function writeContinue (callback) { if (this.socket._handle) { this.socket._handle._spdyState.stream.respond(100, {}, callback) } } PK[T9lib/spdy/agent.jsnuW+A'use strict' var assert = require('assert') var http = require('http') var https = require('https') var net = require('net') var util = require('util') var transport = require('spdy-transport') var debug = require('debug')('spdy:client') // Node.js 0.10 and 0.12 support Object.assign = process.versions.modules >= 46 ? Object.assign // eslint-disable-next-line : util._extend var EventEmitter = require('events').EventEmitter var spdy = require('../spdy') var mode = /^v0\.8\./.test(process.version) ? 'rusty' : /^v0\.(9|10)\./.test(process.version) ? 'old' : /^v0\.12\./.test(process.version) ? 'normal' : 'modern' var proto = {} function instantiate (base) { function Agent (options) { this._init(base, options) } util.inherits(Agent, base) Agent.create = function create (options) { return new Agent(options) } Object.keys(proto).forEach(function (key) { Agent.prototype[key] = proto[key] }) return Agent } proto._init = function _init (base, options) { base.call(this, options) var state = {} this._spdyState = state state.host = options.host state.options = options.spdy || {} state.secure = this instanceof https.Agent state.fallback = false state.createSocket = this._getCreateSocket() state.socket = null state.connection = null // No chunked encoding this.keepAlive = false var self = this this._connect(options, function (err, connection) { if (err) { return self.emit('error', err) } state.connection = connection self.emit('_connect') }) } proto._getCreateSocket = function _getCreateSocket () { // Find super's `createSocket` method var createSocket var cons = this.constructor.super_ do { createSocket = cons.prototype.createSocket if (cons.super_ === EventEmitter || !cons.super_) { break } cons = cons.super_ } while (!createSocket) if (!createSocket) { createSocket = http.Agent.prototype.createSocket } assert(createSocket, '.createSocket() method not found') return createSocket } proto._connect = function _connect (options, callback) { var self = this var state = this._spdyState var protocols = state.options.protocols || [ 'h2', 'spdy/3.1', 'spdy/3', 'spdy/2', 'http/1.1', 'http/1.0' ] // TODO(indutny): reconnect automatically? var socket = this.createConnection(Object.assign({ NPNProtocols: protocols, ALPNProtocols: protocols, servername: options.servername || options.host }, options)) state.socket = socket socket.setNoDelay(true) function onError (err) { return callback(err) } socket.on('error', onError) socket.on(state.secure ? 'secureConnect' : 'connect', function () { socket.removeListener('error', onError) var protocol if (state.secure) { protocol = socket.npnProtocol || socket.alpnProtocol || state.options.protocol } else { protocol = state.options.protocol } // HTTP server - kill socket and switch to the fallback mode if (!protocol || protocol === 'http/1.1' || protocol === 'http/1.0') { debug('activating fallback') socket.destroy() state.fallback = true return } debug('connected protocol=%j', protocol) var connection = transport.connection.create(socket, Object.assign({ protocol: /spdy/.test(protocol) ? 'spdy' : 'http2', isServer: false }, state.options.connection || {})) // Pass connection level errors are passed to the agent. connection.on('error', function (err) { self.emit('error', err) }) // Set version when we are certain if (protocol === 'h2') { connection.start(4) } else if (protocol === 'spdy/3.1') { connection.start(3.1) } else if (protocol === 'spdy/3') { connection.start(3) } else if (protocol === 'spdy/2') { connection.start(2) } else { socket.destroy() callback(new Error('Unexpected protocol: ' + protocol)) return } if (state.options['x-forwarded-for'] !== undefined) { connection.sendXForwardedFor(state.options['x-forwarded-for']) } callback(null, connection) }) } proto._createSocket = function _createSocket (req, options, callback) { var state = this._spdyState if (state.fallback) { return state.createSocket(req, options) } var handle = spdy.handle.create(null, null, state.socket) var socketOptions = { handle: handle, allowHalfOpen: true } var socket if (state.secure) { socket = new spdy.Socket(state.socket, socketOptions) } else { socket = new net.Socket(socketOptions) } handle.assignSocket(socket) handle.assignClientRequest(req) // Create stream only once `req.end()` is called var self = this handle.once('needStream', function () { if (state.connection === null) { self.once('_connect', function () { handle.setStream(self._createStream(req, handle)) }) } else { handle.setStream(self._createStream(req, handle)) } }) // Yes, it is in reverse req.on('response', function (res) { handle.assignRequest(res) }) handle.assignResponse(req) // Handle PUSH req.addListener('newListener', spdy.request.onNewListener) // For v0.8 socket.readable = true socket.writable = true if (callback) { return callback(null, socket) } return socket } if (mode === 'modern' || mode === 'normal') { proto.createSocket = proto._createSocket } else { proto.createSocket = function createSocket (name, host, port, addr, req) { var state = this._spdyState if (state.fallback) { return state.createSocket(name, host, port, addr, req) } return this._createSocket(req, { host: host, port: port }) } } proto._createStream = function _createStream (req, handle) { var state = this._spdyState var self = this return state.connection.reserveStream({ method: req.method, path: req.path, headers: req._headers, host: state.host }, function (err, stream) { if (err) { return self.emit('error', err) } stream.on('response', function (status, headers) { handle.emitResponse(status, headers) }) }) } // Public APIs proto.close = function close (callback) { var state = this._spdyState if (state.connection === null) { this.once('_connect', function () { this.close(callback) }) return } state.connection.end(callback) } exports.Agent = instantiate(https.Agent) exports.PlainAgent = instantiate(http.Agent) exports.create = function create (base, options) { if (typeof base === 'object') { options = base base = null } if (base) { return instantiate(base).create(options) } if (options.spdy && options.spdy.plain) { return exports.PlainAgent.create(options) } else { return exports.Agent.create(options) } } PK[lib/spdy/request.jsnuW+A'use strict' function attachPush (req) { var handle = req.socket._handle handle.getStream(function (stream) { stream.on('pushPromise', function (push) { req.emit('push', push) }) }) } exports.onNewListener = function onNewListener (type) { var req = this if (type !== 'push') { return } // Not first listener if (req.listeners('push').length !== 0) { return } if (!req.socket) { req.on('socket', function () { attachPush(req) }) return } attachPush(req) } PK[Xlib/spdy/server.jsnuW+A'use strict' var assert = require('assert') var https = require('https') var http = require('http') var tls = require('tls') var net = require('net') var util = require('util') var selectHose = require('select-hose') var transport = require('spdy-transport') var debug = require('debug')('spdy:server') var EventEmitter = require('events').EventEmitter // Node.js 0.8, 0.10 and 0.12 support Object.assign = process.versions.modules >= 46 ? Object.assign // eslint-disable-next-line : util._extend var spdy = require('../spdy') var proto = {} function instantiate (base) { function Server (options, handler) { this._init(base, options, handler) } util.inherits(Server, base) Server.create = function create (options, handler) { return new Server(options, handler) } Object.keys(proto).forEach(function (key) { Server.prototype[key] = proto[key] }) return Server } proto._init = function _init (base, options, handler) { var state = {} this._spdyState = state state.options = options.spdy || {} var protocols = state.options.protocols || [ 'h2', 'spdy/3.1', 'spdy/3', 'spdy/2', 'http/1.1', 'http/1.0' ] var actualOptions = Object.assign({ NPNProtocols: protocols, // Future-proof ALPNProtocols: protocols }, options) state.secure = this instanceof tls.Server if (state.secure) { base.call(this, actualOptions) } else { base.call(this) } // Support HEADERS+FIN this.httpAllowHalfOpen = true var event = state.secure ? 'secureConnection' : 'connection' state.listeners = this.listeners(event).slice() assert(state.listeners.length > 0, 'Server does not have default listeners') this.removeAllListeners(event) if (state.options.plain) { this.on(event, this._onPlainConnection) } else { this.on(event, this._onConnection) } if (handler) { this.on('request', handler) } debug('server init secure=%d', state.secure) } proto._onConnection = function _onConnection (socket) { var state = this._spdyState var protocol if (state.secure) { protocol = socket.npnProtocol || socket.alpnProtocol } this._handleConnection(socket, protocol) } proto._handleConnection = function _handleConnection (socket, protocol) { var state = this._spdyState if (!protocol) { protocol = state.options.protocol } debug('incoming socket protocol=%j', protocol) // No way we can do anything with the socket if (!protocol || protocol === 'http/1.1' || protocol === 'http/1.0') { debug('to default handler it goes') return this._invokeDefault(socket) } socket.setNoDelay(true) var connection = transport.connection.create(socket, Object.assign({ protocol: /spdy/.test(protocol) ? 'spdy' : 'http2', isServer: true }, state.options.connection || {})) // Set version when we are certain if (protocol === 'http2') { connection.start(4) } else if (protocol === 'spdy/3.1') { connection.start(3.1) } else if (protocol === 'spdy/3') { connection.start(3) } else if (protocol === 'spdy/2') { connection.start(2) } connection.on('error', function () { socket.destroy() }) var self = this connection.on('stream', function (stream) { self._onStream(stream) }) } // HTTP2 preface var PREFACE = 'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n' var PREFACE_BUFFER = Buffer.from(PREFACE) function hoseFilter (data, callback) { if (data.length < 1) { return callback(null, null) } // SPDY! if (data[0] === 0x80) { return callback(null, 'spdy') } var avail = Math.min(data.length, PREFACE_BUFFER.length) for (var i = 0; i < avail; i++) { if (data[i] !== PREFACE_BUFFER[i]) { return callback(null, 'http/1.1') } } // Not enough bytes to be sure about HTTP2 if (avail !== PREFACE_BUFFER.length) { return callback(null, null) } return callback(null, 'h2') } proto._onPlainConnection = function _onPlainConnection (socket) { var hose = selectHose.create(socket, {}, hoseFilter) var self = this hose.on('select', function (protocol, socket) { self._handleConnection(socket, protocol) }) hose.on('error', function (err) { debug('hose error %j', err.message) socket.destroy() }) } proto._invokeDefault = function _invokeDefault (socket) { var state = this._spdyState for (var i = 0; i < state.listeners.length; i++) { state.listeners[i].call(this, socket) } } proto._onStream = function _onStream (stream) { var state = this._spdyState var handle = spdy.handle.create(this._spdyState.options, stream) var socketOptions = { handle: handle, allowHalfOpen: true } var socket if (state.secure) { socket = new spdy.Socket(stream.connection.socket, socketOptions) } else { socket = new net.Socket(socketOptions) } // This is needed because the `error` listener, added by the default // `connection` listener, no longer has bound arguments. It relies instead // on the `server` property of the socket. See https://github.com/nodejs/node/pull/11926 // for more details. // This is only done for Node.js >= 4 in order to not break compatibility // with older versions of the platform. if (process.versions.modules >= 46) { socket.server = this } handle.assignSocket(socket) // For v0.8 socket.readable = true socket.writable = true this._invokeDefault(socket) // For v0.8, 0.10 and 0.12 if (process.versions.modules < 46) { // eslint-disable-next-line this.listenerCount = EventEmitter.listenerCount.bind(this) } // Add lazy `checkContinue` listener, otherwise `res.writeContinue` will be // called before the response object was patched by us. if (stream.headers.expect !== undefined && /100-continue/i.test(stream.headers.expect) && this.listenerCount('checkContinue') === 0) { this.once('checkContinue', function (req, res) { res.writeContinue() this.emit('request', req, res) }) } handle.emitRequest() } proto.emit = function emit (event, req, res) { if (event !== 'request' && event !== 'checkContinue') { return EventEmitter.prototype.emit.apply(this, arguments) } if (!(req.socket._handle instanceof spdy.handle)) { debug('not spdy req/res') req.isSpdy = false req.spdyVersion = 1 res.isSpdy = false res.spdyVersion = 1 return EventEmitter.prototype.emit.apply(this, arguments) } var handle = req.connection._handle req.isSpdy = true req.spdyVersion = handle.getStream().connection.getVersion() res.isSpdy = true res.spdyVersion = req.spdyVersion req.spdyStream = handle.getStream() debug('override req/res') res.writeHead = spdy.response.writeHead res.end = spdy.response.end res.push = spdy.response.push res.writeContinue = spdy.response.writeContinue res.spdyStream = handle.getStream() res._req = req handle.assignRequest(req) handle.assignResponse(res) return EventEmitter.prototype.emit.apply(this, arguments) } exports.Server = instantiate(https.Server) exports.PlainServer = instantiate(http.Server) exports.create = function create (base, options, handler) { if (typeof base === 'object') { handler = options options = base base = null } if (base) { return instantiate(base).create(options, handler) } if (options.spdy && options.spdy.plain) { return exports.PlainServer.create(options, handler) } else { return exports.Server.create(options, handler) } } PK[jlib/spdy/handle.jsnuW+A'use strict' var assert = require('assert') var thing = require('handle-thing') var httpDeceiver = require('http-deceiver') var util = require('util') function Handle (options, stream, socket) { var state = {} this._spdyState = state state.options = options || {} state.stream = stream state.socket = null state.rawSocket = socket || stream.connection.socket state.deceiver = null state.ending = false var self = this thing.call(this, stream, { getPeerName: function () { return self._getPeerName() }, close: function (callback) { return self._closeCallback(callback) } }) if (!state.stream) { this.on('stream', function (stream) { state.stream = stream }) } } util.inherits(Handle, thing) module.exports = Handle Handle.create = function create (options, stream, socket) { return new Handle(options, stream, socket) } Handle.prototype._getPeerName = function _getPeerName () { var state = this._spdyState if (state.rawSocket._getpeername) { return state.rawSocket._getpeername() } return null } Handle.prototype._closeCallback = function _closeCallback (callback) { var state = this._spdyState var stream = state.stream if (state.ending) { // The .end() method of the stream may be called by us or by the // .shutdown() method in our super-class. If the latter has already been // called, then calling the .end() method below will have no effect, with // the result that the callback will never get executed, leading to an ever // so subtle memory leak. if (stream._writableState.finished) { // NOTE: it is important to call `setImmediate` instead of `nextTick`, // since this is how regular `handle.close()` works in node.js core. // // Using `nextTick` will lead to `net.Socket` emitting `close` before // `end` on UV_EOF. This results in aborted request without `end` event. setImmediate(callback) } else if (stream._writableState.ending) { stream.once('finish', function () { callback(null) }) } else { stream.end(callback) } } else { stream.abort(callback) } // Only a single end is allowed state.ending = false } Handle.prototype.getStream = function getStream (callback) { var state = this._spdyState if (!callback) { assert(state.stream) return state.stream } if (state.stream) { process.nextTick(function () { callback(state.stream) }) return } this.on('stream', callback) } Handle.prototype.assignSocket = function assignSocket (socket, options) { var state = this._spdyState state.socket = socket state.deceiver = httpDeceiver.create(socket, options) function onStreamError (err) { state.socket.emit('error', err) } this.getStream(function (stream) { stream.on('error', onStreamError) }) } Handle.prototype.assignClientRequest = function assignClientRequest (req) { var state = this._spdyState var oldEnd = req.end var oldSend = req._send // Catch the headers before request will be sent var self = this // For old nodes if (thing.mode !== 'modern') { req.end = function end () { this.end = oldEnd this._send('') return this.end.apply(this, arguments) } } req._send = function send (data) { this._headerSent = true // for v0.10 and below, otherwise it will set `hot = false` and include // headers in first write this._header = 'ignore me' // To prevent exception this.connection = state.socket // It is very important to leave this here, otherwise it will be executed // on a next tick, after `_send` will perform write self.getStream(function (stream) { if (!stream.connection._isGoaway(stream.id)) { stream.send() } }) // We are ready to create stream self.emit('needStream') // Ensure that the connection is still ok to use if (state.stream && state.stream.connection._isGoaway(state.stream.id)) { return } req._send = oldSend // Ignore empty writes if (req.method === 'GET' && data.length === 0) { return } return req._send.apply(this, arguments) } // No chunked encoding req.useChunkedEncodingByDefault = false req.on('finish', function () { req.socket.end() }) } Handle.prototype.assignRequest = function assignRequest (req) { // Emit trailing headers this.getStream(function (stream) { stream.on('headers', function (headers) { req.emit('trailers', headers) }) }) } Handle.prototype.assignResponse = function assignResponse (res) { var self = this res.addTrailers = function addTrailers (headers) { self.getStream(function (stream) { stream.sendHeaders(headers) }) } } Handle.prototype._transformHeaders = function _transformHeaders (kind, headers) { var state = this._spdyState var res = {} var keys = Object.keys(headers) if (kind === 'request' && state.options['x-forwarded-for']) { var xforwarded = state.stream.connection.getXForwardedFor() if (xforwarded !== null) { res['x-forwarded-for'] = xforwarded } } for (var i = 0; i < keys.length; i++) { var key = keys[i] var value = headers[key] if (key === ':authority') { res.host = value } if (/^:/.test(key)) { continue } res[key] = value } return res } Handle.prototype.emitRequest = function emitRequest () { var state = this._spdyState var stream = state.stream state.deceiver.emitRequest({ method: stream.method, path: stream.path, headers: this._transformHeaders('request', stream.headers) }) } Handle.prototype.emitResponse = function emitResponse (status, headers) { var state = this._spdyState state.deceiver.emitResponse({ status: status, headers: this._transformHeaders('response', headers) }) } PK[aٙlib/spdy/socket.jsnuW+A'use strict' var util = require('util') var net = require('net') function Socket (parent, options) { net.Socket.call(this, options) var state = {} this._spdyState = state state.parent = parent this.servername = parent.servername this.npnProtocol = parent.npnProtocol this.alpnProtocol = parent.alpnProtocol this.authorized = parent.authorized this.authorizationError = parent.authorizationError this.encrypted = true this.allowHalfOpen = true } util.inherits(Socket, net.Socket) module.exports = Socket var methods = [ 'renegotiate', 'setMaxSendFragment', 'getTLSTicket', 'setServername', 'setSession', 'getPeerCertificate', 'getSession', 'isSessionReused', 'getCipher', 'getEphemeralKeyInfo' ] methods.forEach(function (method) { Socket.prototype[method] = function methodWrap () { var parent = this._spdyState.parent return parent[method].apply(parent, arguments) } }) PK[R_z lib/spdy.jsnuW+A'use strict' var spdy = exports // Export tools spdy.handle = require('./spdy/handle') spdy.request = require('./spdy/request') spdy.response = require('./spdy/response') spdy.Socket = require('./spdy/socket') // Export client spdy.agent = require('./spdy/agent') spdy.Agent = spdy.agent.Agent spdy.createAgent = spdy.agent.create // Export server spdy.server = require('./spdy/server') spdy.Server = spdy.server.Server spdy.PlainServer = spdy.server.PlainServer spdy.createServer = spdy.server.create PK[wtTRR package.jsonnuW+A{ "name": "spdy", "version": "4.0.2", "description": "Implementation of the SPDY protocol on node.js.", "license": "MIT", "scripts": { "lint": "standard", "test": "mocha --reporter=spec test/*-test.js", "coverage": "istanbul cover node_modules/.bin/_mocha -- --reporter=spec test/**/*-test.js" }, "pre-commit": [ "lint", "test" ], "keywords": [ "spdy" ], "repository": { "type": "git", "url": "git://github.com/indutny/node-spdy.git" }, "homepage": "https://github.com/indutny/node-spdy", "bugs": { "email": "node-spdy+bugs@indutny.com", "url": "https://github.com/spdy-http2/node-spdy/issues" }, "author": "Fedor Indutny ", "contributors": [ "Chris Storm ", "François de Metz ", "Ilya Grigorik ", "Roberto Peon", "Tatsuhiro Tsujikawa", "Jesse Cravens " ], "dependencies": { "debug": "^4.1.0", "handle-thing": "^2.0.0", "http-deceiver": "^1.2.7", "select-hose": "^2.0.0", "spdy-transport": "^3.0.0" }, "devDependencies": { "istanbul": "^0.4.5", "mocha": "^6.2.3", "pre-commit": "^1.2.2", "standard": "^13.1.0" }, "engines": { "node": ">=6.0.0" }, "main": "./lib/spdy", "optionalDependencies": {} } PK[wtest/client-test.jsnuW+APK[6RRtest/fixtures.jsnuW+APK[zO..)test/server-test.jsnuW+APK[E!E! XREADME.mdnuW+APK[q~~ 6z.travis.ymlnuW+APK[Y k k zlib/spdy/response.jsnuW+APK[T9lib/spdy/agent.jsnuW+APK[lib/spdy/request.jsnuW+APK[X?lib/spdy/server.jsnuW+APK[jlib/spdy/handle.jsnuW+APK[aٙlib/spdy/socket.jsnuW+APK[R_z lib/spdy.jsnuW+APK[wtTRR package.jsonnuW+APK x