Skip to content

Commit 46bf283

Browse files
committed
Initial commit
0 parents  commit 46bf283

File tree

7 files changed

+538
-0
lines changed

7 files changed

+538
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
*.swp
2+
node_modules/

.jscsrc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"preset": "node-style-guide"
3+
}

.jshintrc

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"camelcase": true,
3+
"curly": true,
4+
"eqeqeq": true,
5+
"freeze": true,
6+
"indent": 2,
7+
"newcap": true,
8+
"quotmark": "single",
9+
"maxdepth": 3,
10+
"maxlen": 80,
11+
"eqnull": true,
12+
"funcscope": true,
13+
"node": true,
14+
"undef": true,
15+
"unused": "vars"
16+
}

README.md

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# proxy-from-env
2+
3+
`proxy-from-env` is a Node.js package that exports a function (`getProxyForUrl`)
4+
that takes an input URL (a string) and returns the desired proxy URL (also a
5+
string) based on standard proxy environment variables. If no proxy is set, an
6+
empty string is returned.
7+
8+
It is your responsibility to actually proxy the request using the given URL.
9+
10+
Installation:
11+
12+
```sh
13+
npm install proxy-from-env
14+
```
15+
16+
## Example
17+
This example shows how the data for a URL can be fetched via the
18+
[`http` module](https://nodejs.org/api/http.html), in a proxy-aware way.
19+
20+
```javascript
21+
var http = require('http');
22+
var parseUrl = require('url').parse;
23+
var getProxyForUrl = require('proxy-from-env').getProxyForUrl;
24+
25+
var some_url = 'https://example.com/something';
26+
27+
var proxy_url = getProxyForUrl(some_url); // <-- Our magic.
28+
if (proxy_url) {
29+
// Should be proxied through proxy_url.
30+
var parsed_some_url = parseUrl(some_url);
31+
var parsed_proxy_url = parseUrl(proxy_url);
32+
// A HTTP proxy is quite simple. It is similar to a normal request, except the
33+
// path is an absolute URL, and the proxied URL's host is put in the header
34+
// instead of the server's actual host.
35+
httpOptions = {
36+
protocol: parsed_proxy_url.protocol,
37+
hostname: parsed_proxy_url.hostname,
38+
port: parsed_proxy_url.port,
39+
path: parsed_some_url.href,
40+
headers: {
41+
Host: parsed_some_url.host, // = host name + optional port.
42+
},
43+
};
44+
} else {
45+
// Direct request.
46+
httpOptions = some_url;
47+
}
48+
http.get(httpOptions, function(res) {
49+
var responses = [];
50+
res.on('data', function() { responses.push(chunk); });
51+
res.on('end', function() { console.log(responses.join('')); });
52+
});
53+
54+
```
55+
56+
## Environment variables
57+
The environment variables can be specified in lowercase or uppercase, with the
58+
lowercase name having precedence over the uppercase variant. A variable that is
59+
not set has the same meaning as a variable that is set but has no value.
60+
61+
### NO\_PROXY
62+
63+
`NO_PROXY` is a list of host names (optionally with a port). If the input URL
64+
matches any of the entries in `NO_PROXY`, then the input URL should be fetched
65+
by a direct request (i.e. without a proxy).
66+
67+
Matching follows the following rules:
68+
69+
- `NO_PROXY=*` disables all proxies.
70+
- Space and commas may be used to separate the entries in the `NO_PROXY` list.
71+
- If `NO_PROXY` does not contain any entries, then proxies are never disabled.
72+
- If a port is added after the host name, then the ports must match. If the URL
73+
does not have an explicit port name, the protocol's default port is used.
74+
- Generally, the proxy is only disabled if the host name is an exact match for
75+
an entry in the `NO_PROXY` list. The only exceptions are entries that start
76+
with a dot or with a wildcard; then the proxy is disabled if the host name
77+
ends with the entry.
78+
79+
See `test.js` for examples of what should match and what does not.
80+
81+
### \*\_PROXY
82+
83+
The environment variable used for the proxy depends on the protocol of the URL.
84+
For example, `https://example,com` uses the "https" protocol, and therefore the
85+
proxy to be used is `HTTPS_PROXY` (_NOT_ `HTTP_PROXY`, which is _only_ used for
86+
http:-URLs).
87+
88+
The library is not limited to http(s), other schemes such as
89+
`FTP_PROXY` (ftp:),
90+
`WSS_PROXY` (wss:),
91+
`WS_PROXY` (ws:)
92+
are also supported.
93+
94+
If present, `ALL_PROXY` is used as fallback if there is no other match.
95+
96+
97+
## External resources
98+
The exact way of parsing the environment variables is not codified in any
99+
standard. This library is designed to be compatible with formats as expected by
100+
existing software.
101+
The following resources were used to determine the desired behavior:
102+
103+
- cURL:
104+
https://curl.haxx.se/docs/manpage.html#ENVIRONMENT
105+
https://github.com/curl/curl/blob/4af40b3646d3b09f68e419f7ca866ff395d1f897/lib/url.c#L4446-L4514
106+
107+
- wget:
108+
https://www.gnu.org/software/wget/manual/wget.html#Proxies
109+
http://git.savannah.gnu.org/cgit/wget.git/tree/src/init.c?id=636a5f9a1c508aa39e35a3a8e9e54520a284d93d#n383
110+
http://git.savannah.gnu.org/cgit/wget.git/tree/src/retr.c?id=93c1517c4071c4288ba5a4b038e7634e4c6b5482#n1278
111+
112+
- W3:
113+
https://www.w3.org/Daemon/User/Proxies/ProxyClients.html
114+
115+
- Python's urllib:
116+
https://github.com/python/cpython/blob/936135bb97fe04223aa30ca6e98eac8f3ed6b349/Lib/urllib/request.py#L2444-L2479

index.js

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
'use strict';
2+
3+
var parseUrl = require('url').parse;
4+
5+
var DEFAULT_PORTS = {
6+
'ftp:': 21,
7+
'gopher:': 70,
8+
'http:': 80,
9+
'https:': 443,
10+
'ws:': 80,
11+
'wss:': 443,
12+
};
13+
14+
var stringEndsWith = String.prototype.endsWith || function(s) {
15+
return s.length <= this.length &&
16+
this.indexOf(s, this.length - s.length) !== -1;
17+
};
18+
19+
/**
20+
* @param {string} url - The URL
21+
* @return {string} The URL of the proxy that should handle the request to the
22+
* given URL. If no proxy is set, this will be an empty string.
23+
*/
24+
function getProxyForUrl(url) {
25+
var parsedUrl = parseUrl(url);
26+
if (!parsedUrl.host || !shouldProxy(parsedUrl)) {
27+
return ''; // Don't proxy invalid URLs or URLs that match NO_PROXY.
28+
}
29+
30+
var proto = url.split(':', 1)[0];
31+
return getEnv(proto + '_proxy') || getEnv('all_proxy');
32+
}
33+
34+
/**
35+
* Determines whether a given URL should be proxied.
36+
*
37+
* @param {object} parsedUrl - The result of url.parse
38+
* @returns {boolean} Whether the given URL should be proxied.
39+
* @private
40+
*/
41+
function shouldProxy(parsedUrl) {
42+
var NO_PROXY = getEnv('no_proxy').toLowerCase();
43+
if (!NO_PROXY) {
44+
return true; // Always proxy if NO_PROXY is not set.
45+
}
46+
if (NO_PROXY === '*') {
47+
return false; // Never proxy if wildcard is set.
48+
}
49+
50+
var port = parseInt(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || 0;
51+
// Stripping ports in this way instead of using parsedUrl.hostname to make
52+
// sure that the brackets around IPv6 addresses are kept.
53+
var hostname = parsedUrl.host.replace(/:\d*$/, '');
54+
55+
return NO_PROXY.split(/[,\s]/).every(function(proxy) {
56+
if (!proxy) {
57+
return true; // Skip zero-length hosts.
58+
}
59+
var parsedProxy = proxy.match(/^(.+):(\d+)$/);
60+
var parsedProxyHostname = parsedProxy ? parsedProxy[1] : proxy;
61+
var parsedProxyPort = parsedProxy ? parseInt(parsedProxy[2]) : 0;
62+
if (parsedProxyPort && parsedProxyPort !== port) {
63+
return true; // Skip if ports don't match.
64+
}
65+
66+
if (!/^[.*]/.test(parsedProxyHostname)) {
67+
// No wildcards, so stop proxying if there is an exact match.
68+
return hostname !== parsedProxyHostname;
69+
}
70+
71+
if (parsedProxyHostname.charAt(0) === '*') {
72+
// Remove leading wildcard.
73+
parsedProxyHostname = parsedProxyHostname.slice(1);
74+
}
75+
// Stop proxying if the hostname ends with the no_proxy host.
76+
return !stringEndsWith.call(hostname, parsedProxyHostname);
77+
});
78+
}
79+
80+
/**
81+
* Get the value for an environment variable.
82+
*
83+
* @param {string} key - The name of the environment variable.
84+
* @return {string} The value of the environment variable.
85+
* @private
86+
*/
87+
function getEnv(key) {
88+
return process.env[key.toLowerCase()] || process.env[key.toUpperCase()] || '';
89+
}
90+
91+
exports.getProxyForUrl = getProxyForUrl;

package.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"name": "proxy-from-env",
3+
"version": "0.0.1",
4+
"description": "Offers getProxyForUrl to get the proxy URL for a URL, respecting the *_PROXY (e.g. HTTP_PROXY) and NO_PROXY environment variables.",
5+
"main": "index.js",
6+
"scripts": {
7+
"lint": "jscs index.js && jshint index.js ;:",
8+
"test": "./node_modules/.bin/mocha ./test.js --reporter spec"
9+
},
10+
"repository": {
11+
"type": "git",
12+
"url": "https://github.com/Rob--W/proxy-from-env.git"
13+
},
14+
"keywords": [
15+
"proxy",
16+
"http_proxy",
17+
"https_proxy",
18+
"no_proxy",
19+
"environment"
20+
],
21+
"author": "Rob Wu <[email protected]> (https://robwu.nl/)",
22+
"license": "MIT",
23+
"bugs": {
24+
"url": "https://github.com/Rob--W/proxy-from-env/issues"
25+
},
26+
"homepage": "https://github.com/Rob--W/proxy-from-env#readme",
27+
"devDependencies": {
28+
"mocha": "^2.4.5"
29+
}
30+
}

0 commit comments

Comments
 (0)