Files
tubestation/testing/xpcshell/node-spdy/lib/spdy/client.js

232 lines
6.0 KiB
JavaScript

var spdy = require('../spdy');
var assert = require('assert');
var util = require('util');
var net = require('net');
var https = require('https');
var EventEmitter = require('events').EventEmitter;
var proto = {};
function instantiate(base) {
function Agent(options) {
base.call(this, options);
if (!this.options.spdy)
this.options.spdy = {};
this._init();
// Hack for node.js v0.10
this.createConnection = proto.createConnection;
// No chunked encoding
this.keepAlive = false;
};
util.inherits(Agent, base);
// Copy prototype methods
Object.keys(proto).forEach(function(key) {
this[key] = proto[key];
}, Agent.prototype);
return Agent;
};
proto._init = function init() {
var self = this;
var state = {};
// Find super's `createConnection` method
var createConnection;
var cons = this.constructor.super_;
do {
createConnection = cons.prototype.createConnection;
if (cons.super_ === EventEmitter || !cons.super_)
break;
cons = cons.super_;
} while (!createConnection);
if (!createConnection)
createConnection = this.createConnection || net.createConnection;
// TODO(indutny): Think about falling back to http/https
var socket = createConnection.call(this, util._extend({
NPNProtocols: ['spdy/3.1', 'spdy/3', 'spdy/2'],
ALPNProtocols: ['spdy/3.1', 'spdy/3', 'spdy/2']
}, this.options));
state.socket = socket;
// Header compression is disabled by default in clients to prevent CRIME
// attacks. Don't enable it unless you know what you're doing.
if (this.options.spdy.headerCompression !== true)
this.options.spdy.headerCompression = false;
// Create SPDY connection using newly created socket
var connection = new spdy.Connection(socket,
util._extend(this.options.spdy, {
isServer: false
}));
state.connection = connection;
connection.on('error', function(err) {
self.emit('error', err);
});
// Select SPDY version
if (this.options.spdy.ssl !== false && !this.options.spdy.version) {
// SSL, wait for NPN or ALPN to happen
socket.once('secureConnect', function() {
var selectedProtocol = socket.npnProtocol || socket.alpnProtocol;
if (selectedProtocol === 'spdy/2')
connection._setVersion(2);
else if (selectedProtocol === 'spdy/3')
connection._setVersion(3);
else if (selectedProtocol === 'spdy/3.1')
connection._setVersion(3.1);
else
socket.emit('error', new Error('No supported SPDY version'));
});
} else {
// No SSL, use fixed version
connection._setVersion(this.options.spdy.version || 3);
}
// Destroy PUSH streams until the listener will be added
connection.on('stream', function(stream) {
stream.on('error', function() {});
stream.destroy();
});
state.pushServer = null;
this.on('newListener', this._onNewListener.bind(this));
this.on('removeListener', this._onRemoveListener.bind(this));
// Client has odd-numbered stream ids
state.id = 1;
this._spdyState = state;
};
//
// ### function onNewListener (type, listener)
// #### @type {String} Event type
// #### @listener {Function} Listener callback
//
proto._onNewListener = function onNewListener(type, listener) {
if (type !== 'push')
return;
var state = this._spdyState;
if (state.pushServer)
return state.pushServer.on('translated-request', listener);
state.pushServer = require('http').createServer(listener);
state.connection.removeAllListeners('stream');
state.connection.on('stream', function(stream) {
state.pushServer.emit('connection', stream);
});
state.pushServer.on('request', function(req) {
// Populate trailing headers
req.connection.on('headers', function(headers) {
Object.keys(headers).forEach(function(key) {
req.trailers[key] = headers[key];
});
req.emit('trailers', headers);
});
this.emit('translated-request', req);
});
};
//
// ### function onRemoveListener (type, listener)
// #### @type {String} Event type
// #### @listener {Function} Listener callback
//
proto._onRemoveListener = function onRemoveListener(type, listener) {
if (type !== 'push')
return;
var state = this._spdyState;
if (!state.pushServer)
return;
state.pushServer.removeListener('translated-request', listener);
};
//
// ### function createConnection (options)
//
proto.createConnection = function createConnection(options) {
if (!options)
options = {};
var state = this._spdyState;
var stream = new spdy.Stream(state.connection, {
id: state.id,
priority: options.priority || 7,
client: true,
decompress: options.spdy.decompress == undefined ? true :
options.spdy.decompress
});
state.id += 2;
state.connection._addStream(stream);
return stream;
};
//
// ### function close (callback)
// #### @callback {Function}
// Close underlying socket and terminate all streams
//
proto.close = function close(callback) {
this._spdyState.socket.destroySoon();
if (callback)
this._spdyState.socket.once('close', callback);
};
//
// ### function ping (callback)
// #### @callback {Function}
// Send PING frame and invoke callback once received it back
//
proto.ping = function ping(callback) {
return this._spdyState.connection.ping(callback);
};
//
// Default Agent
//
exports.Agent = instantiate(https.Agent);
//
// ### function create (base, options)
// #### @base {Function} (optional) base server class (https.Server)
// #### @options {Object} tls server options
// @constructor wrapper
//
exports.create = function create(base, options) {
var agent;
if (typeof base === 'function') {
agent = instantiate(base);
} else {
agent = exports.Agent;
options = base;
base = null;
}
// Instantiate http server if `ssl: false`
if (!base &&
options &&
options.spdy &&
options.spdy.plain &&
options.spdy.ssl === false) {
return exports.create(require('http').Agent, options);
}
return new agent(options);
};