react / wstein / node_modules / jest-cli / node_modules / jsdom / node_modules / request / node_modules / tunnel-agent / index.js
81145 views'use strict'12var net = require('net')3, tls = require('tls')4, http = require('http')5, https = require('https')6, events = require('events')7, assert = require('assert')8, util = require('util')9;1011exports.httpOverHttp = httpOverHttp12exports.httpsOverHttp = httpsOverHttp13exports.httpOverHttps = httpOverHttps14exports.httpsOverHttps = httpsOverHttps151617function httpOverHttp(options) {18var agent = new TunnelingAgent(options)19agent.request = http.request20return agent21}2223function httpsOverHttp(options) {24var agent = new TunnelingAgent(options)25agent.request = http.request26agent.createSocket = createSecureSocket27return agent28}2930function httpOverHttps(options) {31var agent = new TunnelingAgent(options)32agent.request = https.request33return agent34}3536function httpsOverHttps(options) {37var agent = new TunnelingAgent(options)38agent.request = https.request39agent.createSocket = createSecureSocket40return agent41}424344function TunnelingAgent(options) {45var self = this46self.options = options || {}47self.proxyOptions = self.options.proxy || {}48self.maxSockets = self.options.maxSockets || http.Agent.defaultMaxSockets49self.requests = []50self.sockets = []5152self.on('free', function onFree(socket, host, port) {53for (var i = 0, len = self.requests.length; i < len; ++i) {54var pending = self.requests[i]55if (pending.host === host && pending.port === port) {56// Detect the request to connect same origin server,57// reuse the connection.58self.requests.splice(i, 1)59pending.request.onSocket(socket)60return61}62}63socket.destroy()64self.removeSocket(socket)65})66}67util.inherits(TunnelingAgent, events.EventEmitter)6869TunnelingAgent.prototype.addRequest = function addRequest(req, options) {70var self = this7172// Legacy API: addRequest(req, host, port, path)73if (typeof options === 'string') {74options = {75host: options,76port: arguments[2],77path: arguments[3]78};79}8081if (self.sockets.length >= this.maxSockets) {82// We are over limit so we'll add it to the queue.83self.requests.push({host: host, port: port, request: req})84return85}8687// If we are under maxSockets create a new one.88self.createSocket({host: options.host, port: options.port, request: req}, function(socket) {89socket.on('free', onFree)90socket.on('close', onCloseOrRemove)91socket.on('agentRemove', onCloseOrRemove)92req.onSocket(socket)9394function onFree() {95self.emit('free', socket, options.host, options.port)96}9798function onCloseOrRemove(err) {99self.removeSocket()100socket.removeListener('free', onFree)101socket.removeListener('close', onCloseOrRemove)102socket.removeListener('agentRemove', onCloseOrRemove)103}104})105}106107TunnelingAgent.prototype.createSocket = function createSocket(options, cb) {108var self = this109var placeholder = {}110self.sockets.push(placeholder)111112var connectOptions = mergeOptions({}, self.proxyOptions,113{ method: 'CONNECT'114, path: options.host + ':' + options.port115, agent: false116}117)118if (connectOptions.proxyAuth) {119connectOptions.headers = connectOptions.headers || {}120connectOptions.headers['Proxy-Authorization'] = 'Basic ' +121new Buffer(connectOptions.proxyAuth).toString('base64')122}123124debug('making CONNECT request')125var connectReq = self.request(connectOptions)126connectReq.useChunkedEncodingByDefault = false // for v0.6127connectReq.once('response', onResponse) // for v0.6128connectReq.once('upgrade', onUpgrade) // for v0.6129connectReq.once('connect', onConnect) // for v0.7 or later130connectReq.once('error', onError)131connectReq.end()132133function onResponse(res) {134// Very hacky. This is necessary to avoid http-parser leaks.135res.upgrade = true136}137138function onUpgrade(res, socket, head) {139// Hacky.140process.nextTick(function() {141onConnect(res, socket, head)142})143}144145function onConnect(res, socket, head) {146connectReq.removeAllListeners()147socket.removeAllListeners()148149if (res.statusCode === 200) {150assert.equal(head.length, 0)151debug('tunneling connection has established')152self.sockets[self.sockets.indexOf(placeholder)] = socket153cb(socket)154} else {155debug('tunneling socket could not be established, statusCode=%d', res.statusCode)156var error = new Error('tunneling socket could not be established, ' + 'statusCode=' + res.statusCode)157error.code = 'ECONNRESET'158options.request.emit('error', error)159self.removeSocket(placeholder)160}161}162163function onError(cause) {164connectReq.removeAllListeners()165166debug('tunneling socket could not be established, cause=%s\n', cause.message, cause.stack)167var error = new Error('tunneling socket could not be established, ' + 'cause=' + cause.message)168error.code = 'ECONNRESET'169options.request.emit('error', error)170self.removeSocket(placeholder)171}172}173174TunnelingAgent.prototype.removeSocket = function removeSocket(socket) {175var pos = this.sockets.indexOf(socket)176if (pos === -1) return177178this.sockets.splice(pos, 1)179180var pending = this.requests.shift()181if (pending) {182// If we have pending requests and a socket gets closed a new one183// needs to be created to take over in the pool for the one that closed.184this.createSocket(pending, function(socket) {185pending.request.onSocket(socket)186})187}188}189190function createSecureSocket(options, cb) {191var self = this192TunnelingAgent.prototype.createSocket.call(self, options, function(socket) {193// 0 is dummy port for v0.6194var secureSocket = tls.connect(0, mergeOptions({}, self.options,195{ servername: options.host196, socket: socket197}198))199cb(secureSocket)200})201}202203204function mergeOptions(target) {205for (var i = 1, len = arguments.length; i < len; ++i) {206var overrides = arguments[i]207if (typeof overrides === 'object') {208var keys = Object.keys(overrides)209for (var j = 0, keyLen = keys.length; j < keyLen; ++j) {210var k = keys[j]211if (overrides[k] !== undefined) {212target[k] = overrides[k]213}214}215}216}217return target218}219220221var debug222if (process.env.NODE_DEBUG && /\btunnel\b/.test(process.env.NODE_DEBUG)) {223debug = function() {224var args = Array.prototype.slice.call(arguments)225if (typeof args[0] === 'string') {226args[0] = 'TUNNEL: ' + args[0]227} else {228args.unshift('TUNNEL:')229}230console.error.apply(console, args)231}232} else {233debug = function() {}234}235exports.debug = debug // for test236237238