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.httpOverHttps = httpOverHttps;
|
14 | exports.httpsOverHttp = httpsOverHttp;
|
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 httpOverHttps(options) {
|
25 | var agent = new TunnelingAgent(options);
|
26 | agent.request = https.request;
|
27 | return agent;
|
28 | }
|
29 |
|
30 | function httpsOverHttp(options) {
|
31 | var agent = new TunnelingAgent(options);
|
32 | agent.request = http.request;
|
33 | agent.createSocket = createSecureSocket;
|
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 |
|
58 |
|
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 |
|
75 | self.requests.push({host: host, port: port, request: req});
|
76 | return;
|
77 | }
|
78 |
|
79 |
|
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 | 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 |
|
149 | TunnelingAgent.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 |
|
159 |
|
160 | this.createSocket(pending, function(socket) {
|
161 | pending.request.onSocket(socket);
|
162 | });
|
163 | }
|
164 | };
|
165 |
|
166 | function 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 |
|
177 | function 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 |
|
194 | var debug;
|
195 | if (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 | }
|