UNPKG

whistle

Version:

HTTP, HTTPS, Websocket debugging proxy

186 lines (157 loc) 5.31 kB
var tls = require('tls'); var http = require('http'); var events = require('events'); var util = require('util'); var Buffer = require('safe-buffer').Buffer; exports.httpOverHttp = httpOverHttp; exports.httpsOverHttp = httpsOverHttp; function httpOverHttp(options) { var agent = new TunnelingAgent(options); agent.request = http.request; return agent; } function httpsOverHttp(options) { var agent = new TunnelingAgent(options); agent.request = http.request; agent.createSocket = createSecureSocket; agent.defaultPort = 443; return agent; } function TunnelingAgent(options) { var self = this; self.options = options || {}; self.proxyOptions = self.options.proxy || {}; self.maxSockets = self.options.maxSockets || http.Agent.defaultMaxSockets; self.requests = []; self.sockets = []; self.on('free', function onFree(socket, host, port) { for (var i = 0, len = self.requests.length; i < len; ++i) { var pending = self.requests[i]; if (pending.host === host && pending.port === port) { // Detect the request to connect same origin server, // reuse the connection. self.requests.splice(i, 1); pending.request.onSocket(socket); return; } } socket.destroy(); self.removeSocket(socket); }); } util.inherits(TunnelingAgent, events.EventEmitter); TunnelingAgent.prototype.addRequest = function addRequest(req, options) { var self = this; // Legacy API: addRequest(req, host, port, path) if (typeof options === 'string') { options = { host: options, port: arguments[2], path: arguments[3] }; } if (self.sockets.length >= this.maxSockets) { // We are over limit so we'll add it to the queue. self.requests.push({host: options.host, port: options.port, request: req}); return; } // If we are under maxSockets create a new one. self.createConnection({host: options.host, port: options.port, request: req}); }; TunnelingAgent.prototype.createConnection = function createConnection(pending) { var self = this; self.createSocket(pending, function(socket) { socket.on('free', onFree); socket.on('close', onCloseOrRemove); socket.on('agentRemove', onCloseOrRemove); pending.request.onSocket(socket); function onFree() { self.emit('free', socket, pending.host, pending.port); } function onCloseOrRemove(err) { self.removeSocket(socket); socket.removeListener('free', onFree); socket.removeListener('close', onCloseOrRemove); socket.removeListener('agentRemove', onCloseOrRemove); } }); }; TunnelingAgent.prototype.createSocket = function createSocket(options, cb) { var self = this; var placeholder = {}; self.sockets.push(placeholder); var connectOptions = mergeOptions({}, self.proxyOptions, { method: 'CONNECT' , path: options.host + ':' + options.port , agent: false } ); if (connectOptions.proxyAuth) { connectOptions.headers = connectOptions.headers || {}; connectOptions.headers['Proxy-Authorization'] = 'Basic ' + Buffer.from(connectOptions.proxyAuth).toString('base64'); } var connectReq = self.request(connectOptions); connectReq.once('connect', onConnect); connectReq.once('error', onError); connectReq.end(); function onConnect(res, socket, head) { connectReq.removeAllListeners(); socket.removeAllListeners(); if (res.statusCode === 200) { self.sockets[self.sockets.indexOf(placeholder)] = socket; cb(socket); } else { var error = new Error('tunneling socket could not be established, ' + 'statusCode=' + res.statusCode); error.code = 'ECONNRESET'; options.request.emit('error', error); self.removeSocket(placeholder); } } function onError(cause) { connectReq.removeAllListeners(); var error = new Error('tunneling socket could not be established, ' + 'cause=' + cause.message); error.code = 'ECONNRESET'; options.request.emit('error', error); self.removeSocket(placeholder); } }; TunnelingAgent.prototype.removeSocket = function removeSocket(socket) { var pos = this.sockets.indexOf(socket); if (pos === -1) return; this.sockets.splice(pos, 1); var pending = this.requests.shift(); if (pending) { // If we have pending requests and a socket gets closed a new one // needs to be created to take over in the pool for the one that closed. this.createConnection(pending); } }; function createSecureSocket(options, cb) { var self = this; TunnelingAgent.prototype.createSocket.call(self, options, function(socket) { // 0 is dummy port for v0.6 var secureSocket = tls.connect(0, mergeOptions({}, self.options, { servername: options.host , socket: socket } )); self.sockets[self.sockets.indexOf(socket)] = secureSocket; cb(secureSocket); }); } function mergeOptions(target) { for (var i = 1, len = arguments.length; i < len; ++i) { var overrides = arguments[i]; if (typeof overrides === 'object') { var keys = Object.keys(overrides); for (var j = 0, keyLen = keys.length; j < keyLen; ++j) { var k = keys[j]; if (overrides[k] !== undefined) { target[k] = overrides[k]; } } } } return target; }