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

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