You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							244 lines
						
					
					
						
							6.7 KiB
						
					
					
				
			
		
		
	
	
							244 lines
						
					
					
						
							6.7 KiB
						
					
					
				'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
 | 
						|
 |