index.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. 'use strict'
  2. var 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. ;
  10. exports.httpOverHttp = httpOverHttp
  11. exports.httpsOverHttp = httpsOverHttp
  12. exports.httpOverHttps = httpOverHttps
  13. exports.httpsOverHttps = httpsOverHttps
  14. function httpOverHttp(options) {
  15. var agent = new TunnelingAgent(options)
  16. agent.request = http.request
  17. return agent
  18. }
  19. function httpsOverHttp(options) {
  20. var agent = new TunnelingAgent(options)
  21. agent.request = http.request
  22. agent.createSocket = createSecureSocket
  23. agent.defaultPort = 443
  24. return agent
  25. }
  26. function httpOverHttps(options) {
  27. var agent = new TunnelingAgent(options)
  28. agent.request = https.request
  29. return agent
  30. }
  31. function httpsOverHttps(options) {
  32. var agent = new TunnelingAgent(options)
  33. agent.request = https.request
  34. agent.createSocket = createSecureSocket
  35. agent.defaultPort = 443
  36. return agent
  37. }
  38. function TunnelingAgent(options) {
  39. var self = this
  40. self.options = options || {}
  41. self.proxyOptions = self.options.proxy || {}
  42. self.maxSockets = self.options.maxSockets || http.Agent.defaultMaxSockets
  43. self.requests = []
  44. self.sockets = []
  45. self.on('free', function onFree(socket, host, port) {
  46. for (var i = 0, len = self.requests.length; i < len; ++i) {
  47. var pending = self.requests[i]
  48. if (pending.host === host && pending.port === port) {
  49. // Detect the request to connect same origin server,
  50. // reuse the connection.
  51. self.requests.splice(i, 1)
  52. pending.request.onSocket(socket)
  53. return
  54. }
  55. }
  56. socket.destroy()
  57. self.removeSocket(socket)
  58. })
  59. }
  60. util.inherits(TunnelingAgent, events.EventEmitter)
  61. TunnelingAgent.prototype.addRequest = function addRequest(req, options) {
  62. var self = this
  63. // Legacy API: addRequest(req, host, port, path)
  64. if (typeof options === 'string') {
  65. options = {
  66. host: options,
  67. port: arguments[2],
  68. path: arguments[3]
  69. };
  70. }
  71. if (self.sockets.length >= this.maxSockets) {
  72. // We are over limit so we'll add it to the queue.
  73. self.requests.push({host: options.host, port: options.port, request: req})
  74. return
  75. }
  76. // If we are under maxSockets create a new one.
  77. self.createConnection({host: options.host, port: options.port, request: req})
  78. }
  79. TunnelingAgent.prototype.createConnection = function createConnection(pending) {
  80. var self = this
  81. self.createSocket(pending, function(socket) {
  82. socket.on('free', onFree)
  83. socket.on('close', onCloseOrRemove)
  84. socket.on('agentRemove', onCloseOrRemove)
  85. pending.request.onSocket(socket)
  86. function onFree() {
  87. self.emit('free', socket, pending.host, pending.port)
  88. }
  89. function onCloseOrRemove(err) {
  90. self.removeSocket(socket)
  91. socket.removeListener('free', onFree)
  92. socket.removeListener('close', onCloseOrRemove)
  93. socket.removeListener('agentRemove', onCloseOrRemove)
  94. }
  95. })
  96. }
  97. TunnelingAgent.prototype.createSocket = function createSocket(options, cb) {
  98. var self = this
  99. var placeholder = {}
  100. self.sockets.push(placeholder)
  101. var connectOptions = mergeOptions({}, self.proxyOptions,
  102. { method: 'CONNECT'
  103. , path: options.host + ':' + options.port
  104. , agent: false
  105. }
  106. )
  107. if (connectOptions.proxyAuth) {
  108. connectOptions.headers = connectOptions.headers || {}
  109. connectOptions.headers['Proxy-Authorization'] = 'Basic ' +
  110. new Buffer(connectOptions.proxyAuth).toString('base64')
  111. }
  112. debug('making CONNECT request')
  113. var connectReq = self.request(connectOptions)
  114. connectReq.useChunkedEncodingByDefault = false // for v0.6
  115. connectReq.once('response', onResponse) // for v0.6
  116. connectReq.once('upgrade', onUpgrade) // for v0.6
  117. connectReq.once('connect', onConnect) // for v0.7 or later
  118. connectReq.once('error', onError)
  119. connectReq.end()
  120. function onResponse(res) {
  121. // Very hacky. This is necessary to avoid http-parser leaks.
  122. res.upgrade = true
  123. }
  124. function onUpgrade(res, socket, head) {
  125. // Hacky.
  126. process.nextTick(function() {
  127. onConnect(res, socket, head)
  128. })
  129. }
  130. function onConnect(res, socket, head) {
  131. connectReq.removeAllListeners()
  132. socket.removeAllListeners()
  133. if (res.statusCode === 200) {
  134. assert.equal(head.length, 0)
  135. debug('tunneling connection has established')
  136. self.sockets[self.sockets.indexOf(placeholder)] = socket
  137. cb(socket)
  138. } else {
  139. debug('tunneling socket could not be established, statusCode=%d', res.statusCode)
  140. var error = new Error('tunneling socket could not be established, ' + 'statusCode=' + res.statusCode)
  141. error.code = 'ECONNRESET'
  142. options.request.emit('error', error)
  143. self.removeSocket(placeholder)
  144. }
  145. }
  146. function onError(cause) {
  147. connectReq.removeAllListeners()
  148. debug('tunneling socket could not be established, cause=%s\n', cause.message, cause.stack)
  149. var error = new Error('tunneling socket could not be established, ' + 'cause=' + cause.message)
  150. error.code = 'ECONNRESET'
  151. options.request.emit('error', error)
  152. self.removeSocket(placeholder)
  153. }
  154. }
  155. TunnelingAgent.prototype.removeSocket = function removeSocket(socket) {
  156. var pos = this.sockets.indexOf(socket)
  157. if (pos === -1) return
  158. this.sockets.splice(pos, 1)
  159. var pending = this.requests.shift()
  160. if (pending) {
  161. // If we have pending requests and a socket gets closed a new one
  162. // needs to be created to take over in the pool for the one that closed.
  163. this.createConnection(pending)
  164. }
  165. }
  166. function createSecureSocket(options, cb) {
  167. var self = this
  168. TunnelingAgent.prototype.createSocket.call(self, options, function(socket) {
  169. // 0 is dummy port for v0.6
  170. var secureSocket = tls.connect(0, mergeOptions({}, self.options,
  171. { servername: options.host
  172. , socket: socket
  173. }
  174. ))
  175. self.sockets[self.sockets.indexOf(socket)] = secureSocket
  176. cb(secureSocket)
  177. })
  178. }
  179. function mergeOptions(target) {
  180. for (var i = 1, len = arguments.length; i < len; ++i) {
  181. var overrides = arguments[i]
  182. if (typeof overrides === 'object') {
  183. var keys = Object.keys(overrides)
  184. for (var j = 0, keyLen = keys.length; j < keyLen; ++j) {
  185. var k = keys[j]
  186. if (overrides[k] !== undefined) {
  187. target[k] = overrides[k]
  188. }
  189. }
  190. }
  191. }
  192. return target
  193. }
  194. var debug
  195. if (process.env.NODE_DEBUG && /\btunnel\b/.test(process.env.NODE_DEBUG)) {
  196. debug = function() {
  197. var args = Array.prototype.slice.call(arguments)
  198. if (typeof args[0] === 'string') {
  199. args[0] = 'TUNNEL: ' + args[0]
  200. } else {
  201. args.unshift('TUNNEL:')
  202. }
  203. console.error.apply(console, args)
  204. }
  205. } else {
  206. debug = function() {}
  207. }
  208. exports.debug = debug // for test