3 var net
= require('net');
4 var tls
= require('tls');
5 var http
= require('http');
6 var https
= require('https');
7 var events
= require('events');
8 var assert
= require('assert');
9 var 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
;
31 function httpOverHttps(options
) {
32 var agent
= new TunnelingAgent(options
);
33 agent
.request
= https
.request
;
37 function httpsOverHttps(options
) {
38 var agent
= new TunnelingAgent(options
);
39 agent
.request
= https
.request
;
40 agent
.createSocket
= createSecureSocket
;
45 function TunnelingAgent(options
) {
47 self
.options
= options
|| {};
48 self
.proxyOptions
= self
.options
.proxy
|| {};
49 self
.maxSockets
= self
.options
.maxSockets
|| http
.Agent
.defaultMaxSockets
;
53 self
.on('free', function onFree(socket
, host
, port
) {
54 for (var i
= 0, len
= self
.requests
.length
; i
< len
; ++i
) {
55 var pending
= self
.requests
[i
];
56 if (pending
.host
=== host
&& pending
.port
=== port
) {
57 // Detect the request to connect same origin server,
58 // reuse the connection.
59 self
.requests
.splice(i
, 1);
60 pending
.request
.onSocket(socket
);
65 self
.removeSocket(socket
);
68 util
.inherits(TunnelingAgent
, events
.EventEmitter
);
70 TunnelingAgent
.prototype.addRequest
= function addRequest(req
, host
, port
) {
73 if (self
.sockets
.length
>= this.maxSockets
) {
74 // We are over limit so we'll add it to the queue.
75 self
.requests
.push({host
: host
, port
: port
, request
: req
});
79 // If we are under maxSockets create a new one.
80 self
.createSocket({host
: host
, port
: port
, request
: req
}, function(socket
) {
81 socket
.on('free', onFree
);
82 socket
.on('close', onCloseOrRemove
);
83 socket
.on('agentRemove', onCloseOrRemove
);
87 self
.emit('free', socket
, host
, port
);
90 function onCloseOrRemove(err
) {
92 socket
.removeListener('free', onFree
);
93 socket
.removeListener('close', onCloseOrRemove
);
94 socket
.removeListener('agentRemove', onCloseOrRemove
);
99 TunnelingAgent
.prototype.createSocket
= function createSocket(options
, cb
) {
101 var placeholder
= {};
102 self
.sockets
.push(placeholder
);
104 var connectOptions
= mergeOptions({}, self
.proxyOptions
, {
106 path
: options
.host
+ ':' + options
.port
,
109 if (connectOptions
.proxyAuth
) {
110 connectOptions
.headers
= connectOptions
.headers
|| {};
111 connectOptions
.headers
['Proxy-Authorization'] = 'Basic ' +
112 new Buffer(connectOptions
.proxyAuth
).toString('base64');
115 debug('making CONNECT request');
116 var connectReq
= self
.request(connectOptions
);
117 connectReq
.useChunkedEncodingByDefault
= false; // for v0.6
118 connectReq
.once('response', onResponse
); // for v0.6
119 connectReq
.once('upgrade', onUpgrade
); // for v0.6
120 connectReq
.once('connect', onConnect
); // for v0.7 or later
121 connectReq
.once('error', onError
);
124 function onResponse(res
) {
125 // Very hacky. This is necessary to avoid http-parser leaks.
129 function onUpgrade(res
, socket
, head
) {
131 process
.nextTick(function() {
132 onConnect(res
, socket
, head
);
136 function onConnect(res
, socket
, head
) {
137 connectReq
.removeAllListeners();
138 socket
.removeAllListeners();
140 if (res
.statusCode
=== 200) {
141 assert
.equal(head
.length
, 0);
142 debug('tunneling connection has established');
143 self
.sockets
[self
.sockets
.indexOf(placeholder
)] = socket
;
146 debug('tunneling socket could not be established, statusCode=%d',
148 var error
= new Error('tunneling socket could not be established, ' +
149 'sutatusCode=' + res
.statusCode
);
150 error
.code
= 'ECONNRESET';
151 options
.request
.emit('error', error
);
152 self
.removeSocket(placeholder
);
156 function onError(cause
) {
157 connectReq
.removeAllListeners();
159 debug('tunneling socket could not be established, cause=%s\n',
160 cause
.message
, cause
.stack
);
161 var error
= new Error('tunneling socket could not be established, ' +
162 'cause=' + cause
.message
);
163 error
.code
= 'ECONNRESET';
164 options
.request
.emit('error', error
);
165 self
.removeSocket(placeholder
);
169 TunnelingAgent
.prototype.removeSocket
= function removeSocket(socket
) {
170 var pos
= this.sockets
.indexOf(socket
)
174 this.sockets
.splice(pos
, 1);
176 var pending
= this.requests
.shift();
178 // If we have pending requests and a socket gets closed a new one
179 // needs to be created to take over in the pool for the one that closed.
180 this.createSocket(pending
, function(socket
) {
181 pending
.request
.onSocket(socket
);
186 function createSecureSocket(options
, cb
) {
188 TunnelingAgent
.prototype.createSocket
.call(self
, options
, function(socket
) {
189 // 0 is dummy port for v0.6
190 var secureSocket
= tls
.connect(0, mergeOptions({}, self
.options
, {
191 servername
: options
.host
,
199 function mergeOptions(target
) {
200 for (var i
= 1, len
= arguments
.length
; i
< len
; ++i
) {
201 var overrides
= arguments
[i
];
202 if (typeof overrides
=== 'object') {
203 var keys
= Object
.keys(overrides
);
204 for (var j
= 0, keyLen
= keys
.length
; j
< keyLen
; ++j
) {
206 if (overrides
[k
] !== undefined) {
207 target
[k
] = overrides
[k
];
217 if (process
.env
.NODE_DEBUG
&& /\btunnel\b/.test(process
.env
.NODE_DEBUG
)) {
219 var args
= Array
.prototype.slice
.call(arguments
);
220 if (typeof args
[0] === 'string') {
221 args
[0] = 'TUNNEL: ' + args
[0];
223 args
.unshift('TUNNEL:');
225 console
.error
.apply(console
, args
);
228 debug = function() {};
230 exports
.debug
= debug
; // for test