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, {
198 function mergeOptions(target) {
199 for (var i = 1, len = arguments.length; i < len; ++i) {
200 var overrides = arguments[i];
201 if (typeof overrides === 'object') {
202 var keys = Object.keys(overrides);
203 for (var j = 0, keyLen = keys.length; j < keyLen; ++j) {
205 if (overrides[k] !== undefined) {
206 target[k] = overrides[k];
216 if (process.env.NODE_DEBUG && /\btunnel\b/.test(process.env.NODE_DEBUG)) {
218 var args = Array.prototype.slice.call(arguments);
219 if (typeof args[0] === 'string') {
220 args[0] = 'TUNNEL: ' + args[0];
222 args.unshift('TUNNEL:');
224 console.error.apply(console, args);
227 debug = function() {};
229 exports.debug = debug; // for test