|
|
'use strict'
var net = require('net') , tls = require('tls') , http = require('http') , https = require('https') , events = require('events') , assert = require('assert') , util = require('util') , Buffer = require('safe-buffer').Buffer ;
exports.httpOverHttp = httpOverHttp exports.httpsOverHttp = httpsOverHttp exports.httpOverHttps = httpOverHttps exports.httpsOverHttps = httpsOverHttps
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 httpOverHttps(options) { var agent = new TunnelingAgent(options) agent.request = https.request return agent }
function httpsOverHttps(options) { var agent = new TunnelingAgent(options) agent.request = https.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') }
debug('making CONNECT request') var connectReq = self.request(connectOptions) connectReq.useChunkedEncodingByDefault = false // for v0.6
connectReq.once('response', onResponse) // for v0.6
connectReq.once('upgrade', onUpgrade) // for v0.6
connectReq.once('connect', onConnect) // for v0.7 or later
connectReq.once('error', onError) connectReq.end()
function onResponse(res) { // Very hacky. This is necessary to avoid http-parser leaks.
res.upgrade = true }
function onUpgrade(res, socket, head) { // Hacky.
process.nextTick(function() { onConnect(res, socket, head) }) }
function onConnect(res, socket, head) { connectReq.removeAllListeners() socket.removeAllListeners()
if (res.statusCode === 200) { assert.equal(head.length, 0) debug('tunneling connection has established') self.sockets[self.sockets.indexOf(placeholder)] = socket cb(socket) } else { debug('tunneling socket could not be established, statusCode=%d', res.statusCode) 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()
debug('tunneling socket could not be established, cause=%s\n', cause.message, cause.stack) 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 }
var debug if (process.env.NODE_DEBUG && /\btunnel\b/.test(process.env.NODE_DEBUG)) { debug = function() { var args = Array.prototype.slice.call(arguments) if (typeof args[0] === 'string') { args[0] = 'TUNNEL: ' + args[0] } else { args.unshift('TUNNEL:') } console.error.apply(console, args) } } else { debug = function() {} } exports.debug = debug // for test
|