Created
May 23, 2013 22:54
-
-
Save ddallala/5640107 to your computer and use it in GitHub Desktop.
Simple Local Web Proxy - can replace URLs at the request
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| ** Peteris Krumins ([email protected]) | |
| ** http://www.catonmat.net -- good coders code, great reuse | |
| ** | |
| ** A simple proxy server written in node.js. | |
| ** | |
| */ | |
| var fs = require('fs'); | |
| var config = { | |
| add_proxy_header: false, //activate addition of X-Forwarded-For header for better logging on real server side | |
| allow_ip_list: './config/allow_ip_list', | |
| black_list: './config/black_list', | |
| host_filters: './config/hostfilters.js', | |
| listen: [{ | |
| ip: '127.0.0.1', | |
| port: 9090 | |
| }, //all ipv4 interfaces | |
| { | |
| ip: '::1', | |
| port: 9090 | |
| } | |
| ] //all ipv6 interfaces | |
| /*,listen_ssl:[{ | |
| ip:'0.0.0.0',//all *secure* ipv4 interfaces | |
| port:443, | |
| key:fs.readFileSync('/path/to/ssl.key'), | |
| cert:fs.readFileSync('/path/to/ssl.crt'), | |
| ca:[fs.readFileSync('/path/to/ca.pem'), | |
| fs.readFileSync('/path/to/sub-ca.pem')] | |
| },{ | |
| ip:'::',//all *secure* ipv6 interfaces | |
| port:443, | |
| key:fs.readFileSync('/path/to/ssl.key'), | |
| cert:fs.readFileSync('/path/to/ssl.crt'), | |
| ca:[fs.readFileSync('/path/to/ca.pem'), | |
| fs.readFileSync('/path/to/sub-ca.pem')] | |
| } | |
| ]*/ | |
| }; | |
| exports.config = config; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| ** Peteris Krumins ([email protected]) | |
| ** http://www.catonmat.net -- good coders code, great reuse | |
| ** | |
| ** A simple proxy server written in node.js. | |
| ** | |
| ** @modified: ddallala | |
| ** | |
| */ | |
| var http = require('http'), | |
| https = require('https'); | |
| util = require('util'); | |
| fs = require('fs'), | |
| config = require('./config').config, | |
| hostfilters = {}; | |
| var url_replacements = [ | |
| ["http://code.jquery.com/jquery-1.9.1.min.js","http://127.0.0.1/test.js"] | |
| ]; | |
| //support functions | |
| function replace_url(request){ | |
| for(var i=0;i<url_replacements.length;i++) { | |
| var tuple = url_replacements[i]; | |
| var reg_ex =new RegExp(tuple[0]); | |
| if(reg_ex.test(request.url)){ | |
| util.log("replacing: " + request.url + " ==> " + tuple[1]); | |
| request.url = tuple[1]; | |
| request.headers.host = tuple[1].replace('http://','').replace('https://','').split(/[/?#]/)[0]; | |
| return request; | |
| } | |
| } | |
| return request; | |
| } | |
| //decode host and port info from header | |
| function decode_host(host) { | |
| out = {}; | |
| host = host.split(':'); | |
| out.host = host[0]; | |
| out.port = host[1] || 80; | |
| return out; | |
| } | |
| //encode host field | |
| function encode_host(host) { | |
| return host.host + ((host.port == 80) ? "" : ":" + host.port); | |
| } | |
| //add a X-Forwarded-For header ? | |
| config.add_proxy_header = (config.add_proxy_header !== undefined && config.add_proxy_header == true); | |
| //header decoding | |
| function handle_proxy_route(host, token) { | |
| //extract target host and port | |
| action = decode_host(host); | |
| action.action = "proxyto"; //default action | |
| return action; | |
| } | |
| function prevent_loop(request, response) { | |
| if (request.headers.proxy == "node.jtlebi") { //if request is already tooted => loop | |
| util.log("Loop detected"); | |
| response.writeHead(500); | |
| response.write("Proxy loop !"); | |
| response.end(); | |
| return false; | |
| } else { //append a tattoo to it | |
| request.headers.proxy = "node.jtlebi"; | |
| return request; | |
| } | |
| } | |
| function clean_local(request){ | |
| util.log(request.url + "==>"); | |
| request.url = request.url.replace(/^http:\/\/localhost/,""); | |
| request.url = request.url.replace(/^http:\/\/127.0.0.1/,""); | |
| util.log(request.url); | |
| return request; | |
| } | |
| function action_notfound(response, msg) { | |
| response.writeHead(404); | |
| response.write(msg); | |
| response.end(); | |
| } | |
| function action_proxy(response, request, host) { | |
| util.log("Proxying to " + host); | |
| //detect HTTP version | |
| var legacy_http = request.httpVersionMajor == 1 && request.httpVersionMinor < 1 || request.httpVersionMajor < 1; | |
| //launch new request + insert proxy specific header | |
| var headers = request.headers; | |
| if (config.add_proxy_header) { | |
| if (headers['X-Forwarded-For'] !== undefined) { | |
| headers['X-Forwarded-For'] = request.connection.remoteAddress + ", " + headers['X-Forwarded-For']; | |
| } else { | |
| headers['X-Forwarded-For'] = request.connection.remoteAddress; | |
| } | |
| } | |
| var proxy = http.createClient(action.port, action.host); | |
| var proxy_request = proxy.request(request.method, request.url, request.headers); | |
| //deal with errors, timeout, con refused, ... | |
| proxy.on('error', function (err) { | |
| util.log(err.toString() + " on request to " + host); | |
| return action_notfound(response, "Requested resource (" + request.url + ") is not accessible on host \"" + host + "\""); | |
| }); | |
| //proxies to FORWARD answer to real client | |
| proxy_request.addListener('response', function (proxy_response) { | |
| if (legacy_http && proxy_response.headers['transfer-encoding'] != undefined) { | |
| console.log("legacy HTTP: " + request.httpVersion); | |
| //filter headers | |
| var headers = proxy_response.headers; | |
| delete proxy_response.headers['transfer-encoding']; | |
| var buffer = ""; | |
| //buffer answer | |
| proxy_response.addListener('data', function (chunk) { | |
| buffer += chunk; | |
| }); | |
| proxy_response.addListener('end', function () { | |
| headers['Content-length'] = buffer.length; //cancel transfer encoding "chunked" | |
| response.writeHead(proxy_response.statusCode, headers); | |
| response.write(buffer, 'binary'); | |
| response.end(); | |
| }); | |
| } else { | |
| //send headers as received | |
| response.writeHead(proxy_response.statusCode, proxy_response.headers); | |
| //easy data forward | |
| proxy_response.addListener('data', function (chunk) { | |
| response.write(chunk, 'binary'); | |
| }); | |
| proxy_response.addListener('end', function () { | |
| response.end(); | |
| }); | |
| } | |
| }); | |
| //proxies to SEND request to real server | |
| request.addListener('data', function (chunk) { | |
| proxy_request.write(chunk, 'binary'); | |
| }); | |
| request.addListener('end', function () { | |
| proxy_request.end(); | |
| }); | |
| } | |
| //special security logging function | |
| function security_log(request, response, msg) { | |
| var ip = request.connection.remoteAddress; | |
| msg = "**SECURITY VIOLATION**, " + ip + "," + (request.method || "!NO METHOD!") + " " + (request.headers.host || "!NO HOST!") + "=>" + (request.url || "!NO URL!") + "," + msg; | |
| console.log(msg); | |
| } | |
| //security filter | |
| // true if OK | |
| // false to return immediatlely | |
| function security_filter(request, response) { | |
| //HTTP 1.1 protocol violation: no host, no method, no url | |
| if (request.headers.host === undefined || | |
| request.method === undefined || | |
| request.url === undefined) { | |
| security_log(request, response, "Either host, method or url is poorly defined"); | |
| return false; | |
| } | |
| return true; | |
| } | |
| //actual server loop | |
| function server_cb(request, response) { | |
| console.log("----------------------------------------------------"); | |
| //the *very* first action here is to handle security conditions | |
| //all related actions including logging are done by specialized functions | |
| //to ensure compartimentation | |
| if (!security_filter(request, response)) return; | |
| var ip = request.connection.remoteAddress; | |
| //loop filter | |
| request = prevent_loop(request, response); | |
| if (!request) { | |
| return; | |
| } | |
| util.log(ip + ": " + request.method + " " + request.headers.host + "=>" + request.url); | |
| // replace urls before sending out | |
| request = replace_url(request); | |
| //calc new host info | |
| var action = handle_proxy_route(request.headers.host, true); | |
| host = encode_host(action); | |
| // clean request to remove 127.0.0.1 and localhost | |
| request = clean_local(request); | |
| //handle action | |
| action_proxy(response, request, host); | |
| } | |
| //last chance error handler | |
| //it catch the exception preventing the application from crashing. | |
| //I recommend to comment it in a development environment as it | |
| //"Hides" very interesting bits of debugging informations. | |
| process.on('uncaughtException', function (err) { | |
| console.log('LAST ERROR: Caught exception: ' + err); | |
| util.log(err.stack); | |
| }); | |
| //http | |
| config.listen.forEach(function (listen) { | |
| util.log("Starting reverse proxy server on port '" + listen.ip + ':' + listen.port); | |
| http.createServer(server_cb).listen(listen.port, listen.ip); | |
| }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment