UNPKG

4.81 kBJavaScriptView Raw
1'use strict';
2
3/**
4 * Module dependencies.
5 */
6
7var url = require('url');
8var LRU = require('lru-cache');
9var Agent = require('agent-base');
10var inherits = require('util').inherits;
11var debug = require('debug')('proxy-agent');
12var getProxyForUrl = require('proxy-from-env').getProxyForUrl;
13
14var http = require('http');
15var https = require('https');
16var PacProxyAgent = require('pac-proxy-agent');
17var HttpProxyAgent = require('http-proxy-agent');
18var HttpsProxyAgent = require('https-proxy-agent');
19var SocksProxyAgent = require('socks-proxy-agent');
20
21/**
22 * Module exports.
23 */
24
25exports = module.exports = ProxyAgent;
26
27/**
28 * Number of `http.Agent` instances to cache.
29 *
30 * This value was arbitrarily chosen... a better
31 * value could be conceived with some benchmarks.
32 */
33
34var cacheSize = 20;
35
36/**
37 * Cache for `http.Agent` instances.
38 */
39
40exports.cache = new LRU(cacheSize);
41
42/**
43 * Built-in proxy types.
44 */
45
46exports.proxies = Object.create(null);
47exports.proxies.http = httpOrHttpsProxy;
48exports.proxies.https = httpOrHttpsProxy;
49exports.proxies.socks = SocksProxyAgent;
50exports.proxies.socks4 = SocksProxyAgent;
51exports.proxies.socks4a = SocksProxyAgent;
52exports.proxies.socks5 = SocksProxyAgent;
53exports.proxies.socks5h = SocksProxyAgent;
54
55PacProxyAgent.protocols.forEach(function (protocol) {
56 exports.proxies['pac+' + protocol] = PacProxyAgent;
57});
58
59function httpOrHttps(opts, secureEndpoint) {
60 if (secureEndpoint) {
61 // HTTPS
62 return https.globalAgent;
63 } else {
64 // HTTP
65 return http.globalAgent;
66 }
67}
68
69function httpOrHttpsProxy(opts, secureEndpoint) {
70 if (secureEndpoint) {
71 // HTTPS
72 return new HttpsProxyAgent(opts);
73 } else {
74 // HTTP
75 return new HttpProxyAgent(opts);
76 }
77}
78
79function mapOptsToProxy(opts) {
80 // NO_PROXY case
81 if (!opts) {
82 return {
83 uri: 'no proxy',
84 fn: httpOrHttps
85 };
86 }
87
88 if ('string' == typeof opts) opts = url.parse(opts);
89
90 var proxies;
91 if (opts.proxies) {
92 proxies = Object.assign({}, exports.proxies, opts.proxies);
93 } else {
94 proxies = exports.proxies;
95 }
96
97 // get the requested proxy "protocol"
98 var protocol = opts.protocol;
99 if (!protocol) {
100 throw new TypeError('You must specify a "protocol" for the ' +
101 'proxy type (' + Object.keys(proxies).join(', ') + ')');
102 }
103
104 // strip the trailing ":" if present
105 if (':' == protocol[protocol.length - 1]) {
106 protocol = protocol.substring(0, protocol.length - 1);
107 }
108
109 // get the proxy `http.Agent` creation function
110 var proxyFn = proxies[protocol];
111 if ('function' != typeof proxyFn) {
112 throw new TypeError('unsupported proxy protocol: "' + protocol + '"');
113 }
114
115 // format the proxy info back into a URI, since an opts object
116 // could have been passed in originally. This generated URI is used
117 // as part of the "key" for the LRU cache
118 return {
119 opts: opts,
120 uri: url.format({
121 protocol: protocol + ':',
122 slashes: true,
123 auth: opts.auth,
124 hostname: opts.hostname || opts.host,
125 port: opts.port
126 }),
127 fn: proxyFn,
128 }
129}
130
131/**
132 * Attempts to get an `http.Agent` instance based off of the given proxy URI
133 * information, and the `secure` flag.
134 *
135 * An LRU cache is used, to prevent unnecessary creation of proxy
136 * `http.Agent` instances.
137 *
138 * @param {String} uri proxy url
139 * @param {Boolean} secure true if this is for an HTTPS request, false for HTTP
140 * @return {http.Agent}
141 * @api public
142 */
143
144function ProxyAgent (opts) {
145 if (!(this instanceof ProxyAgent)) return new ProxyAgent(opts);
146 debug('creating new ProxyAgent instance: %o', opts);
147 Agent.call(this, connect);
148
149 if (opts) {
150 var proxy = mapOptsToProxy(opts);
151 this.proxy = proxy.opts;
152 this.proxyUri = proxy.uri;
153 this.proxyFn = proxy.fn;
154 }
155}
156inherits(ProxyAgent, Agent);
157
158/**
159 *
160 */
161
162function connect (req, opts, fn) {
163 var proxyOpts = this.proxy;
164 var proxyUri = this.proxyUri;
165 var proxyFn = this.proxyFn;
166
167 // if we did not instantiate with a proxy, set one per request
168 if (!proxyOpts) {
169 var urlOpts = getProxyForUrl(opts);
170 var proxy = mapOptsToProxy(urlOpts, opts);
171 proxyOpts = proxy.opts;
172 proxyUri = proxy.uri;
173 proxyFn = proxy.fn;
174 }
175
176 // create the "key" for the LRU cache
177 var key = proxyUri;
178 if (opts.secureEndpoint) key += ' secure';
179
180 // attempt to get a cached `http.Agent` instance first
181 var agent = exports.cache.get(key);
182 if (!agent) {
183 // get an `http.Agent` instance from protocol-specific agent function
184 agent = proxyFn(proxyOpts, opts.secureEndpoint);
185 if (agent) {
186 exports.cache.set(key, agent);
187 }
188 } else {
189 debug('cache hit with key: %o', key);
190 }
191
192 if (!proxyOpts) {
193 agent.addRequest(req, opts);
194 } else {
195 // XXX: agent.callback() is an agent-base-ism
196 agent.callback(req, opts, fn);
197 }
198}