Skip to content

Commit f09e175

Browse files
author
Nathan Ridley
committed
initial commit
0 parents  commit f09e175

File tree

2 files changed

+274
-0
lines changed

2 files changed

+274
-0
lines changed

readme.md

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
RelayProxy.js
2+
=============
3+
4+
RelayProxy.js implements a forward proxy server in node.js with support for:
5+
6+
- HTTP and HTTPS requests (SOCKS5 support coming in a future version)
7+
- Basic HTTP authorization
8+
- Forwarding to a remote proxy (with support for remote authorization),
9+
selectable at run-time based on incoming header and credentials
10+
11+
I wrote this server as I had a need for a private authenticated proxy server
12+
that would forward the request through a remote private proxy. The idea was to
13+
manage the amount of access a connecting client had to any given address and to
14+
manage which remote proxies (taken from a pool of rented third-party private
15+
proxies, each requiring authorization) were allocated to a given client,
16+
rotating those proxies depending on usage.
17+
18+
The server is very simple to use:
19+
20+
## Usage
21+
### Basic usage
22+
23+
At its simplest, the following will instantiate a non-authenticating transparent
24+
proxy that forwards requests to the target url without relaying the request
25+
through a third-party proxy:
26+
27+
var RelayProxy = require('./path/to/relay-proxy');
28+
var server = new RelayProxy();
29+
server.listen(3333);
30+
31+
### Local authorization
32+
33+
To authorize access to your proxy, provide a handler function:
34+
35+
var RelayProxy = require('./path/to/relay-proxy');
36+
var server = new RelayProxy();
37+
38+
server.authorize(function(req, username, password, callback) {
39+
// replace with code to verify username and password
40+
var isLoginCorrect = true;
41+
42+
// call the supplied callback function to continue the request
43+
callback(null, isLoginCorrect);
44+
});
45+
46+
server.listen(3333);
47+
48+
### Using a remote proxy
49+
50+
To forward requests via a third-party proxy, provide a handler function:
51+
52+
var RelayProxy = require('./path/to/relay-proxy');
53+
var server = new RelayProxy();
54+
55+
server.selectForwardProxy(function(req, username, callback) {
56+
57+
var proxy = {
58+
host: '1.2.3.4',
59+
port: 31337,
60+
username: 'jimbob', // only supply a username and password if
61+
password: 'mcgee' // the remote proxy requires authorization
62+
};
63+
64+
// the callback's second argument can be one of three values:
65+
// null: no remote proxy - forward directly to the requested url
66+
// false: decline to forward the request (http 407 sent back to client)
67+
// proxy: proxy options as specified in the above sample
68+
69+
callback(null, proxy);
70+
});
71+
72+
server.listen(3333);
73+
74+
## Dependencies
75+
76+
RelayProxy has a dependency on [node-http-proxy][1], by the good folks at
77+
[Nodejitsu][2]. Thankfully, it's available in the npm registry.
78+
79+
npm install http-proxy
80+
81+
[1]: https://github.com/nodejitsu/node-http-proxy/
82+
[2]: http://www.nodejitsu.com/

relay-proxy.js

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
/*
2+
relay-proxy.js: http proxy for node.js
3+
4+
Copyright (c) 2010 Nathan Ridley
5+
6+
Permission is hereby granted, free of charge, to any person obtaining
7+
a copy of this software and associated documentation files (the
8+
"Software"), to deal in the Software without restriction, including
9+
without limitation the rights to use, copy, modify, merge, publish,
10+
distribute, sublicense, and/or sell copies of the Software, and to
11+
permit persons to whom the Software is furnished to do so, subject to
12+
the following conditions:
13+
14+
The above copyright notice and this permission notice shall be
15+
included in all copies or substantial portions of the Software.
16+
17+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24+
25+
*/
26+
27+
var httpProxy = require('http-proxy'),
28+
net = require('net'),
29+
http = require('http'),
30+
util = require('util');
31+
32+
function RelayProxy() {
33+
34+
var authHandler = function defaultAuthHandler(req, username, password, callback) {
35+
// @arg 1: error object
36+
// @arg 2: true if authorized, otherwise false
37+
callback(null, true);
38+
}
39+
40+
var proxySelector = function defaultProxySelector(req, username, callback) {
41+
// @arg 1: error object
42+
// @arg 2: a proxy options object, or false to decline the request,
43+
// or null to forward directly to the remote host
44+
// options: {
45+
// host: 'x.x.x.x',
46+
// port: 9999,
47+
// username: 'username', // optional
48+
// password: 'password' // optional
49+
// }
50+
callback(null, null);
51+
}
52+
53+
function getAuth(req) {
54+
55+
var authHeader = req.headers['proxy-authorization'];
56+
if(authHeader) {
57+
var authMatch = (new Buffer(authHeader.substr(6), 'base64')).toString().match(/^([^:]*):(.*)$/);
58+
if(authMatch && authMatch[1] && authMatch[2]) {
59+
return {
60+
username: authMatch[1],
61+
password: authMatch[2]
62+
};
63+
}
64+
}
65+
return null;
66+
}
67+
68+
function createAuthHeader(username, password) {
69+
return 'Basic ' + new Buffer(username + ':' + password).toString('base64');
70+
}
71+
72+
function getRemoteProxy(req, callback) {
73+
74+
var auth = getAuth(req) || {};
75+
authHandler(req, auth.username, auth.password, function onAuthHandlerCallback(err, authorized) {
76+
77+
if(!authorized) {
78+
req.connection.end('HTTP/1.0 407 Proxy authentication required\r\nProxy-authenticate: Basic realm="remotehost"\r\n\r\n');
79+
callback(new Error('Proxy authorization not supplied (407 response sent)'))
80+
return;
81+
};
82+
83+
proxySelector(req, auth.username, function onProxySelectorCallback(err, options) {
84+
85+
if(options === false) {
86+
req.connection.end('HTTP/1.0 429 Too Many Requests\r\n\r\nNo proxy available to service request');
87+
callback(new Error('No remote proxies available (429 response sent)'));
88+
return;
89+
}
90+
91+
callback(null, options);
92+
});
93+
94+
});
95+
}
96+
97+
function onHttpRequest(req, res, proxy) {
98+
99+
var buffer = httpProxy.buffer(req);
100+
101+
console.log('HTTP --> ' + req.url);
102+
103+
getRemoteProxy(req, function(err, remoteProxy) {
104+
105+
if(err) {
106+
console.log(err.message);
107+
return;
108+
}
109+
110+
if(!remoteProxy) {
111+
var parts = req.headers.host.split(':');
112+
proxy.proxyRequest(req, res, {
113+
host: parts[0],
114+
port: parts[1] || 80
115+
});
116+
return;
117+
}
118+
119+
req.path = req.url;
120+
if(remoteProxy.username && remoteProxy.password)
121+
req.headers['proxy-authorization'] = createAuthHeader(remoteProxy.username, remoteProxy.password);
122+
var options = {
123+
host: remoteProxy.host,
124+
port: remoteProxy.port,
125+
buffer: buffer
126+
}
127+
proxy.proxyRequest(req, res, options);
128+
});
129+
}
130+
131+
function onHttpsRequest(req, socket, head) {
132+
133+
console.log('HTTPS --> ' + req.url);
134+
135+
getRemoteProxy(req, function(err, remoteProxy) {
136+
137+
if(err) {
138+
console.log(err.message);
139+
return;
140+
}
141+
142+
if(!remoteProxy) {
143+
var parts = req.url.split(':');
144+
var conn = net.connect(parts[1], parts[0], function() {
145+
socket.write('HTTP/1.1 200 OK\r\n\r\n');
146+
conn.pipe(socket);
147+
socket.pipe(conn);
148+
});
149+
return;
150+
}
151+
152+
var conn = net.connect(remoteProxy.port, remoteProxy.host, function() {
153+
154+
var headers
155+
= 'CONNECT ' + req.url + ' HTTP/1.1\r\n'
156+
+ 'Proxy-Authorization: ' + createAuthHeader(remoteProxy.username, remoteProxy.password) + '\r\n\r\n';
157+
conn.write(headers);
158+
159+
}).once('data', function(buffer) {
160+
161+
var ok = /^HTTP\/1.[01] 200 /i.test(buffer.toString('utf8'));
162+
if(!ok)
163+
socket.end('HTTP/1.1 401 Unauthorized\r\n\r\nUpstream proxy rejected the request');
164+
else {
165+
socket.write('HTTP/1.1 200 OK\r\n\r\n');
166+
socket.pipe(conn);
167+
conn.pipe(socket);
168+
}
169+
});
170+
})
171+
}
172+
173+
this.authorize = function authorize(handler) {
174+
authHandler = handler;
175+
}
176+
177+
this.selectForwardProxy = function selectForwardProxy(handler) {
178+
proxySelector = handler;
179+
}
180+
181+
this.listen = function listen(port) {
182+
port || (port = 3333);
183+
this.server = httpProxy
184+
.createServer(onHttpRequest)
185+
.on('connect', onHttpsRequest)
186+
.listen(port);
187+
188+
console.log('Proxy server now listening on port ' + port + '.');
189+
}
190+
}
191+
192+
module.exports = RelayProxy;

0 commit comments

Comments
 (0)