initial commit
[JIRC.git] / node_modules / request / tunnel.js
1 'use strict';
2
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');
10
11
12 exports.httpOverHttp = httpOverHttp;
13 exports.httpsOverHttp = httpsOverHttp;
14 exports.httpOverHttps = httpOverHttps;
15 exports.httpsOverHttps = httpsOverHttps;
16
17
18 function httpOverHttp(options) {
19 var agent = new TunnelingAgent(options);
20 agent.request = http.request;
21 return agent;
22 }
23
24 function httpsOverHttp(options) {
25 var agent = new TunnelingAgent(options);
26 agent.request = http.request;
27 agent.createSocket = createSecureSocket;
28 return agent;
29 }
30
31 function httpOverHttps(options) {
32 var agent = new TunnelingAgent(options);
33 agent.request = https.request;
34 return agent;
35 }
36
37 function httpsOverHttps(options) {
38 var agent = new TunnelingAgent(options);
39 agent.request = https.request;
40 agent.createSocket = createSecureSocket;
41 return agent;
42 }
43
44
45 function TunnelingAgent(options) {
46 var self = this;
47 self.options = options || {};
48 self.proxyOptions = self.options.proxy || {};
49 self.maxSockets = self.options.maxSockets || http.Agent.defaultMaxSockets;
50 self.requests = [];
51 self.sockets = [];
52
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);
61 return;
62 }
63 }
64 socket.destroy();
65 self.removeSocket(socket);
66 });
67 }
68 util.inherits(TunnelingAgent, events.EventEmitter);
69
70 TunnelingAgent.prototype.addRequest = function addRequest(req, host, port) {
71 var self = this;
72
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});
76 return;
77 }
78
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);
84 req.onSocket(socket);
85
86 function onFree() {
87 self.emit('free', socket, host, port);
88 }
89
90 function onCloseOrRemove(err) {
91 self.removeSocket();
92 socket.removeListener('free', onFree);
93 socket.removeListener('close', onCloseOrRemove);
94 socket.removeListener('agentRemove', onCloseOrRemove);
95 }
96 });
97 };
98
99 TunnelingAgent.prototype.createSocket = function createSocket(options, cb) {
100 var self = this;
101 var placeholder = {};
102 self.sockets.push(placeholder);
103
104 var connectOptions = mergeOptions({}, self.proxyOptions, {
105 method: 'CONNECT',
106 path: options.host + ':' + options.port,
107 agent: false
108 });
109 if (connectOptions.proxyAuth) {
110 connectOptions.headers = connectOptions.headers || {};
111 connectOptions.headers['Proxy-Authorization'] = 'Basic ' +
112 new Buffer(connectOptions.proxyAuth).toString('base64');
113 }
114
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);
122 connectReq.end();
123
124 function onResponse(res) {
125 // Very hacky. This is necessary to avoid http-parser leaks.
126 res.upgrade = true;
127 }
128
129 function onUpgrade(res, socket, head) {
130 // Hacky.
131 process.nextTick(function() {
132 onConnect(res, socket, head);
133 });
134 }
135
136 function onConnect(res, socket, head) {
137 connectReq.removeAllListeners();
138 socket.removeAllListeners();
139
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;
144 cb(socket);
145 } else {
146 debug('tunneling socket could not be established, statusCode=%d',
147 res.statusCode);
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);
153 }
154 }
155
156 function onError(cause) {
157 connectReq.removeAllListeners();
158
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);
166 }
167 };
168
169 TunnelingAgent.prototype.removeSocket = function removeSocket(socket) {
170 var pos = this.sockets.indexOf(socket)
171 if (pos === -1) {
172 return;
173 }
174 this.sockets.splice(pos, 1);
175
176 var pending = this.requests.shift();
177 if (pending) {
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);
182 });
183 }
184 };
185
186 function createSecureSocket(options, cb) {
187 var self = this;
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,
192 socket: socket
193 }));
194 cb(secureSocket);
195 });
196 }
197
198
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) {
205 var k = keys[j];
206 if (overrides[k] !== undefined) {
207 target[k] = overrides[k];
208 }
209 }
210 }
211 }
212 return target;
213 }
214
215
216 var debug;
217 if (process.env.NODE_DEBUG && /\btunnel\b/.test(process.env.NODE_DEBUG)) {
218 debug = function() {
219 var args = Array.prototype.slice.call(arguments);
220 if (typeof args[0] === 'string') {
221 args[0] = 'TUNNEL: ' + args[0];
222 } else {
223 args.unshift('TUNNEL:');
224 }
225 console.error.apply(console, args);
226 }
227 } else {
228 debug = function() {};
229 }
230 exports.debug = debug; // for test