forked from wzrdtales/global-tunnel
-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
220 lines (192 loc) · 5.43 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
/*jshint node:true */
'use strict';
/**
* @fileOverview
* Global proxy settings.
*/
var globalTunnel = exports;
exports.constructor = function globalTunnel(){};
var http = require('http');
var https = require('https');
var urlParse = require('url').parse;
var _ = require('lodash');
var tunnel = require('tunnel');
var agents = require('./lib/agents');
exports.agents = agents;
// save the original globalAgents for restoration later.
var ORIGINALS = {
http: _.pick(http, 'globalAgent', 'request'),
https: _.pick(https, 'globalAgent', 'request')
};
function resetGlobals() {
_.assign(http, ORIGINALS.http);
_.assign(https, ORIGINALS.https);
}
/**
* Parses the de facto `http_proxy` environment.
*/
function tryParse(url) {
if (!url) {
return null;
}
var conf = {};
var parsed = urlParse(url);
conf.protocol = parsed.protocol;
conf.host = parsed.hostname;
conf.port = parseInt(parsed.port,10);
return conf;
}
globalTunnel.isProxying = false;
/**
* Overrides the node http/https `globalAgent`s to use the configured proxy.
*
* If the config is empty, the `http_proxy` environment variable is checked. If
* that's not present, no proxying will be enabled.
*
* @param {object} conf
* @param {string} conf.host
* @param {int} conf.port
* @param {int} [conf.sockets] maximum number of sockets to pool (falsy uses
* node's default).
*/
globalTunnel.initialize = function(conf) {
if (globalTunnel.isProxying) {
return;
}
conf = conf || {};
if (typeof conf === 'string') {
conf = tryParse(conf);
}
if (_.isEmpty(conf)) {
conf = tryParse(process.env['http_proxy']);
if (!conf) {
globalTunnel.isProxying = false;
return;
}
}
conf = _.clone(conf);
if (!conf.host) {
throw new Error('upstream proxy host is required');
}
if (!conf.port) {
throw new Error('upstream proxy port is required');
}
if (conf.protocol === undefined) {
conf.protocol = 'http:'; // default to proxy speaking http
}
if (!/:$/.test(conf.protocol)) {
conf.protocol = conf.protocol + ':';
}
if (!conf.connect) {
conf.connect = 'https'; // just HTTPS by default
}
switch(conf.connect) {
case 'both':
conf.connectHttp = true;
conf.connectHttps = true;
break;
case 'neither':
conf.connectHttp = false;
conf.connectHttps = false;
break;
case 'https':
conf.connectHttp = false;
conf.connectHttps = true;
break;
default:
throw new Error('valid connect options are "neither", "https", or "both"');
}
if (conf.httpsOptions) {
conf.outerHttpsOpts = conf.innerHttpsOpts = conf.httpsOptions;
}
try {
http.globalAgent = globalTunnel._makeAgent(conf, 'http', conf.connectHttp);
https.globalAgent = globalTunnel._makeAgent(conf, 'https', conf.connectHttps);
http.request = globalTunnel._defaultedAgentRequest.bind(http, 'http');
https.request = globalTunnel._defaultedAgentRequest.bind(https, 'https');
globalTunnel.isProxying = true;
} catch (e) {
resetGlobals();
throw e;
}
};
/**
* Construct an agent based on:
* - is the connection to the proxy secure?
* - is the connection to the origin secure?
* - the address of the proxy
*/
globalTunnel._makeAgent = function(conf, innerProtocol, useCONNECT) {
var outerProtocol = conf.protocol;
innerProtocol = innerProtocol + ':';
var opts = {
proxy: _.pick(conf, 'host','port','protocol','localAddress'),
maxSockets: conf.sockets
};
opts.proxy.innerProtocol = innerProtocol;
if (useCONNECT) {
if (conf.proxyHttpsOptions) {
_.assign(opts.proxy, conf.proxyHttpsOptions);
}
if (conf.originHttpsOptions) {
_.assign(opts, conf.originHttpsOptions);
}
if (outerProtocol === 'https:') {
if (innerProtocol === 'https:') {
return tunnel.httpsOverHttps(opts);
} else {
return tunnel.httpOverHttps(opts);
}
} else {
if (innerProtocol === 'https:') {
return tunnel.httpsOverHttp(opts);
} else {
return tunnel.httpOverHttp(opts);
}
}
} else {
if (conf.originHttpsOptions) {
throw new Error('originHttpsOptions must be combined with a tunnel:true option');
}
if (conf.proxyHttpsOptions) {
// NB: not opts.
_.assign(opts, conf.proxyHttpsOptions);
}
if (outerProtocol === 'https:') {
return new agents.OuterHttpsAgent(opts);
} else {
return new agents.OuterHttpAgent(opts);
}
}
};
/**
* Override for http.request and https.request, makes sure to default the agent
* to the global agent. Due to how node implements it in lib/http.js, the
* globalAgent we define won't get used (node uses a module-scoped variable,
* not the exports field).
* @param {string} protocol bound during initialization
* @param {string|object} options http/https request url or options
* @param {function} [cb]
* @private
*/
globalTunnel._defaultedAgentRequest = function(protocol, options, callback) {
var httpOrHttps = this;
if (typeof options === 'string') {
options = urlParse(options);
} else {
options = _.clone(options);
}
// A literal `false` means "no agent at all", other falsey values should use
// our global agent.
if (!options.agent && options.agent !== false) {
options.agent = httpOrHttps.globalAgent;
}
return ORIGINALS[protocol].request.call(httpOrHttps, options, callback);
};
/**
* Restores global http/https agents.
*/
globalTunnel.end = function() {
resetGlobals();
globalTunnel.isProxying = false;
};