UNPKG

5.92 kBJavaScriptView Raw
1'use strict';
2
3var net = require('net');
4var tls = require('tls');
5var http = require('http');
6var https = require('https');
7var events = require('events');
8var assert = require('assert');
9var util = require('util');
10
11
12exports.httpOverHttp = httpOverHttp;
13exports.httpOverHttps = httpOverHttps;
14exports.httpsOverHttp = httpsOverHttp;
15exports.httpsOverHttps = httpsOverHttps;
16
17
18function httpOverHttp(options) {
19 var agent = new TunnelingAgent(options);
20 agent.request = http.request;
21 return agent;
22}
23
24function httpOverHttps(options) {
25 var agent = new TunnelingAgent(options);
26 agent.request = https.request;
27 return agent;
28}
29
30function httpsOverHttp(options) {
31 var agent = new TunnelingAgent(options);
32 agent.request = http.request;
33 agent.createSocket = createSecureSocket;
34 return agent;
35}
36
37function httpsOverHttps(options) {
38 var agent = new TunnelingAgent(options);
39 agent.request = https.request;
40 agent.createSocket = createSecureSocket;
41 return agent;
42}
43
44
45function 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}
68util.inherits(TunnelingAgent, events.EventEmitter);
69
70TunnelingAgent.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
99TunnelingAgent.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 var connectReq = self.request(connectOptions);
115 connectReq.once('connect', onConnect);
116 connectReq.once('error', onError);
117 connectReq.end();
118
119 function onConnect(res, socket, head) {
120 connectReq.removeListener('error', onError);
121
122 if (res.statusCode === 200) {
123 assert.equal(head.length, 0);
124 self.sockets[self.sockets.indexOf(placeholder)] = socket;
125 cb(socket);
126 } else {
127 debug('tunneling socket could not be established, statusCode=%d',
128 res.statusCode);
129 var error = new Error('tunneling socket could not be established, ' +
130 'sutatusCode=' + res.statusCode);
131 error.code = 'ECONNRESET';
132 options.request.emit('error', error);
133 self.removeSocket(placeholder);
134 }
135 }
136
137 function onError(cause) {
138 connectReq.removeListener('connect', onConnect);
139 debug('tunneling socket could not be established, cause=%s\n',
140 cause.message, cause.stack);
141 var error = new Error('tunneling socket could not be established, ' +
142 'cause=' + cause.message);
143 error.code = 'ECONNRESET';
144 options.request.emit('error', error);
145 self.removeSocket(placeholder);
146 }
147};
148
149TunnelingAgent.prototype.removeSocket = function removeSocket(socket) {
150 var pos = this.sockets.indexOf(socket)
151 if (pos === -1) {
152 return;
153 }
154 this.sockets.splice(pos, 1);
155
156 var pending = this.requests.shift();
157 if (pending) {
158 // If we have pending requests and a socket gets closed a new one
159 // needs to be created to take over in the pool for the one that closed.
160 this.createSocket(pending, function(socket) {
161 pending.request.onSocket(socket);
162 });
163 }
164};
165
166function createSecureSocket(options, cb) {
167 var self = this;
168 TunnelingAgent.prototype.createSocket.call(self, options, function(socket) {
169 var secureSocket = tls.connect(mergeOptions({}, self.options, {
170 socket: socket
171 }));
172 cb(secureSocket);
173 });
174}
175
176
177function mergeOptions(target) {
178 for (var i = 1, len = arguments.length; i < len; ++i) {
179 var overrides = arguments[i];
180 if (typeof overrides === 'object') {
181 var keys = Object.keys(overrides);
182 for (var j = 0, keyLen = keys.length; j < keyLen; ++j) {
183 var k = keys[j];
184 if (overrides[k] !== undefined) {
185 target[k] = overrides[k];
186 }
187 }
188 }
189 }
190 return target;
191}
192
193
194var debug;
195if (process.env.NODE_DEBUG && /\btunnel\b/.test(process.env.NODE_DEBUG)) {
196 debug = function() {
197 var args = Array.prototype.slice.call(arguments);
198 if (typeof args[0] === 'string') {
199 args[0] = 'TUNNEL: ' + args[0];
200 } else {
201 args.unshift('TUNNEL:');
202 }
203 console.error.apply(console, args);
204 }
205} else {
206 debug = function() {};
207}