3 var net = require('net')
5 , http = require('http')
6 , https = require('https')
7 , events = require('events')
8 , assert = require('assert')
9 , util = require('util')
12 exports.httpOverHttp = httpOverHttp
13 exports.httpsOverHttp = httpsOverHttp
14 exports.httpOverHttps = httpOverHttps
15 exports.httpsOverHttps = httpsOverHttps
18 function httpOverHttp(options) {
19 var agent = new TunnelingAgent(options)
20 agent.request = http.request
24 function httpsOverHttp(options) {
25 var agent = new TunnelingAgent(options)
26 agent.request = http.request
27 agent.createSocket = createSecureSocket
28 agent.defaultPort = 443
32 function httpOverHttps(options) {
33 var agent = new TunnelingAgent(options)
34 agent.request = https.request
38 function httpsOverHttps(options) {
39 var agent = new TunnelingAgent(options)
40 agent.request = https.request
41 agent.createSocket = createSecureSocket
42 agent.defaultPort = 443
47 function TunnelingAgent(options) {
49 self.options = options || {}
50 self.proxyOptions = self.options.proxy || {}
51 self.maxSockets = self.options.maxSockets || http.Agent.defaultMaxSockets
55 self.on('free', function onFree(socket, host, port) {
56 for (var i = 0, len = self.requests.length; i < len; ++i) {
57 var pending = self.requests[i]
58 if (pending.host === host && pending.port === port) {
59 // Detect the request to connect same origin server,
60 // reuse the connection.
61 self.requests.splice(i, 1)
62 pending.request.onSocket(socket)
67 self.removeSocket(socket)
70 util.inherits(TunnelingAgent, events.EventEmitter)
72 TunnelingAgent.prototype.addRequest = function addRequest(req, options) {
75 // Legacy API: addRequest(req, host, port, path)
76 if (typeof options === 'string') {
84 if (self.sockets.length >= this.maxSockets) {
85 // We are over limit so we'll add it to the queue.
86 self.requests.push({host: options.host, port: options.port, request: req})
90 // If we are under maxSockets create a new one.
91 self.createConnection({host: options.host, port: options.port, request: req})
94 TunnelingAgent.prototype.createConnection = function createConnection(pending) {
97 self.createSocket(pending, function(socket) {
98 socket.on('free', onFree)
99 socket.on('close', onCloseOrRemove)
100 socket.on('agentRemove', onCloseOrRemove)
101 pending.request.onSocket(socket)
104 self.emit('free', socket, pending.host, pending.port)
107 function onCloseOrRemove(err) {
108 self.removeSocket(socket)
109 socket.removeListener('free', onFree)
110 socket.removeListener('close', onCloseOrRemove)
111 socket.removeListener('agentRemove', onCloseOrRemove)
116 TunnelingAgent.prototype.createSocket = function createSocket(options, cb) {
119 self.sockets.push(placeholder)
121 var connectOptions = mergeOptions({}, self.proxyOptions,
123 , path: options.host + ':' + options.port
127 if (connectOptions.proxyAuth) {
128 connectOptions.headers = connectOptions.headers || {}
129 connectOptions.headers['Proxy-Authorization'] = 'Basic ' +
130 new Buffer(connectOptions.proxyAuth).toString('base64')
133 debug('making CONNECT request')
134 var connectReq = self.request(connectOptions)
135 connectReq.useChunkedEncodingByDefault = false // for v0.6
136 connectReq.once('response', onResponse) // for v0.6
137 connectReq.once('upgrade', onUpgrade) // for v0.6
138 connectReq.once('connect', onConnect) // for v0.7 or later
139 connectReq.once('error', onError)
142 function onResponse(res) {
143 // Very hacky. This is necessary to avoid http-parser leaks.
147 function onUpgrade(res, socket, head) {
149 process.nextTick(function() {
150 onConnect(res, socket, head)
154 function onConnect(res, socket, head) {
155 connectReq.removeAllListeners()
156 socket.removeAllListeners()
158 if (res.statusCode === 200) {
159 assert.equal(head.length, 0)
160 debug('tunneling connection has established')
161 self.sockets[self.sockets.indexOf(placeholder)] = socket
164 debug('tunneling socket could not be established, statusCode=%d', res.statusCode)
165 var error = new Error('tunneling socket could not be established, ' + 'statusCode=' + res.statusCode)
166 error.code = 'ECONNRESET'
167 options.request.emit('error', error)
168 self.removeSocket(placeholder)
172 function onError(cause) {
173 connectReq.removeAllListeners()
175 debug('tunneling socket could not be established, cause=%s\n', cause.message, cause.stack)
176 var error = new Error('tunneling socket could not be established, ' + 'cause=' + cause.message)
177 error.code = 'ECONNRESET'
178 options.request.emit('error', error)
179 self.removeSocket(placeholder)
183 TunnelingAgent.prototype.removeSocket = function removeSocket(socket) {
184 var pos = this.sockets.indexOf(socket)
185 if (pos === -1) return
187 this.sockets.splice(pos, 1)
189 var pending = this.requests.shift()
191 // If we have pending requests and a socket gets closed a new one
192 // needs to be created to take over in the pool for the one that closed.
193 this.createConnection(pending)
197 function createSecureSocket(options, cb) {
199 TunnelingAgent.prototype.createSocket.call(self, options, function(socket) {
200 // 0 is dummy port for v0.6
201 var secureSocket = tls.connect(0, mergeOptions({}, self.options,
202 { servername: options.host
206 self.sockets[self.sockets.indexOf(socket)] = secureSocket
212 function mergeOptions(target) {
213 for (var i = 1, len = arguments.length; i < len; ++i) {
214 var overrides = arguments[i]
215 if (typeof overrides === 'object') {
216 var keys = Object.keys(overrides)
217 for (var j = 0, keyLen = keys.length; j < keyLen; ++j) {
219 if (overrides[k] !== undefined) {
220 target[k] = overrides[k]
230 if (process.env.NODE_DEBUG && /\btunnel\b/.test(process.env.NODE_DEBUG)) {
232 var args = Array.prototype.slice.call(arguments)
233 if (typeof args[0] === 'string') {
234 args[0] = 'TUNNEL: ' + args[0]
236 args.unshift('TUNNEL:')
238 console.error.apply(console, args)
241 debug = function() {}
243 exports.debug = debug // for test