diff --git a/README.md b/README.md index 8ff8c6253..e270ff773 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ There are several ways to use node-http-proxy; the library is designed to be fle 3. In conjunction with a Proxy Routing Table 4. As a forward-proxy with a reverse proxy 5. From the command-line as a long running process +6. customized with 3rd party middleware. In each of these scenarios node-http-proxy can handle any of these types of requests: @@ -312,6 +313,16 @@ https.createServer(options.https, function (req, res) { res.end(); }).listen(8000); ``` +## Middleware + +`node-http-proxy` now supports connect middleware. Add middleware functions to your createServer call: + +``` js +httpProxy.createServer( + require('connect-gzip').gzip(), + 9000, 'localhost' +).listen(8000); +``` ## Proxying WebSockets Websockets are handled automatically when using the `httpProxy.createServer()`, but if you want to use it in conjunction with a stand-alone HTTP + WebSocket (such as [socket.io][5]) server here's how: diff --git a/examples/body-decoder.js b/examples/body-decoder.js new file mode 100644 index 000000000..23ae96d51 --- /dev/null +++ b/examples/body-decoder.js @@ -0,0 +1,72 @@ +/* + body-decoder.js: Example of body-decoder middleware with node-http-proxy + + Copyright (c) 2010 Charlie Robbins, Mikeal Rogers, Fedor Indutny, & Marak Squires. + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ + +var httpProxy = require('http-proxy'), + http = require('http'), + util = require('util'), + colors = require('colors'); + +exports.bodyMod = function () { + console.log('middleware has been started.'.green); + return function (req, res, next) { + var proxy = next, + total = ''; + + req.on('data', function (data) { + console.log('ON DATA') + total += data; + }); + + req.on('end', function () { + console.log('ON END') + console.log(total); + // + // This line, uncommented, hangs forever. + // proxy.proxyRequest(req, res, { port: 9000, host: 'localhost' }); + // The following also hangs forever. + // next.proxyRequest(req, res, { port: 9000, host: 'localhost' }); + // + }); + + // + // The following fires just fine. + //proxy.proxyRequest(req, res, { port: 9000, host: 'localhost' }); + // + console.log('request proxied...'.blue); + } +} + +var proxyServer = httpProxy.createServer( + // Your middleware stack goes here. + exports.bodyMod() +).listen(8000); + + +var httpServer = http.createServer(function (req, res) { + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.write('request successfully proxied to: ' + req.url + '\n' + JSON.stringify(req.headers, true, 2)); + res.end(); +}).listen(9000); \ No newline at end of file diff --git a/examples/bodyDecoder-middleware.js b/examples/bodyDecoder-middleware.js new file mode 100644 index 000000000..180372f44 --- /dev/null +++ b/examples/bodyDecoder-middleware.js @@ -0,0 +1,87 @@ + +var Store = require('./lib/store') + , http = require('http') + +http.createServer(new Store().handler()).listen(7531, function () { +//try these commands: +// get index: +// curl localhost:7531 +// [] +// +// get a doc: +// curl localhost:7531/foo +// {"error":"not_found"} +// +// post an doc: +// curl -X POST localhost:7531/foo -d '{"content": "hello", "type": "greeting"}' +// {"ok":true} +// +// get index (now, not empty) +// curl localhost:7531 +// ["/foo"] +// +// get doc +// curl localhost:7531/foo +// {"content": "hello", "type": "greeting"} + +// +// now, suppose we wanted to direct all objects where type == "greeting" to a different store +// than where type == "insult" +// +// we can use connect connect-bodyDecoder and some custom logic to send insults to another Store. + +//insult server: + + http.createServer(new Store().handler()).listen(2600, function () { + + //greetings -> 7531, insults-> 2600 + + // now, start a proxy server. + + var bodyParser = require('connect/lib/middleware/bodyParser') + //don't worry about incoming contont type + //bodyParser.parse[''] = JSON.parse + + require('http-proxy').createServer( + //refactor the body parser and re-streamer into a separate package + bodyParser(), + //body parser absorbs the data and end events before passing control to the next + // middleware. if we want to proxy it, we'll need to re-emit these events after + //passing control to the middleware. + require('connect-restreamer')(), + function (req, res, proxy) { + //if your posting an obect which contains type: "insult" + //it will get redirected to port 2600. + //normal get requests will go to 7531 nad will not return insults. + var port = (req.body && req.body.type === 'insult' ? 2600 : 7531) + proxy.proxyRequest(req, res, {host: 'localhost', port: port}) + } + ).listen(1337, function () { + var request = require('request') + //bodyParser needs content-type set to application/json + //if we use request, it will set automatically if we use the 'json:' field. + function post (greeting, type) { + request.post({ + url: 'http://localhost:1337/' + greeting, + json: {content: greeting, type: type || "greeting"} + }) + } + post("hello") + post("g'day") + post("kiora") + post("houdy") + post("java", "insult") + + //now, the insult should have been proxied to 2600 + + //curl localhost:2600 + //["/java"] + + //but the greetings will be sent to 7531 + + //curl localhost:7531 + //["/hello","/g%27day","/kiora","/houdy"] + + }) + }) +}) diff --git a/examples/concurrent-proxy.js b/examples/concurrent-proxy.js new file mode 100644 index 000000000..4bf5673fe --- /dev/null +++ b/examples/concurrent-proxy.js @@ -0,0 +1,65 @@ +/* + concurrent-proxy.js: check levelof concurrency through proxy. + + Copyright (c) 2010 Charlie Robbins, Mikeal Rogers, Fedor Indutny, & Marak Squires. + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ + +var util = require('util'), + colors = require('colors'), + http = require('http'), + httpProxy = require('./../lib/node-http-proxy'); + +// +// Basic Http Proxy Server +// +httpProxy.createServer(9000, 'localhost').listen(8000); + +// +// Target Http Server +// +// to check apparent problems with concurrent connections +// make a server which only responds when there is a given nubmer on connections +// + + +var connections = [] + , go + +http.createServer(function (req, res) { + + connections.push (function (){ + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.write('request successfully proxied to: ' + req.url + '\n' + JSON.stringify(req.headers, true, 2)); + res.end(); + }) + process.stdout.write(connections.length + ', ') + if (connections.length > 110 || go) { + go = true + while(connections.length) + connections.shift()() + } + +}).listen(9000); + +util.puts('http proxy server'.blue + ' started '.green.bold + 'on port '.blue + '8000'.yellow); +util.puts('http server '.blue + 'started '.green.bold + 'on port '.blue + '9000 '.yellow); diff --git a/examples/gzip-middleware.js b/examples/gzip-middleware.js new file mode 100644 index 000000000..856e2f6e8 --- /dev/null +++ b/examples/gzip-middleware.js @@ -0,0 +1,50 @@ +/* + gzip-middleware.js: Basic example of `connect-gzip` middleware in node-http-proxy + + Copyright (c) 2010 Charlie Robbins, Mikeal Rogers, Fedor Indutny, Marak Squires, & Dominic Tarr. + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ + +var util = require('util'), + colors = require('colors'), + http = require('http'), + httpProxy = require('./../lib/node-http-proxy'); + +// +// Basic Http Proxy Server +// +httpProxy.createServer( + require('connect-gzip').gzip({ matchType: /.?/ }), + 9000, 'localhost' +).listen(8000); + +// +// Target Http Server +// +http.createServer(function (req, res) { + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.write('request successfully proxied to: ' + req.url + '\n' + JSON.stringify(req.headers, true, 2)); + res.end(); +}).listen(9000); + +util.puts('http proxy server'.blue + ' started '.green.bold + 'on port '.blue + '8000'.yellow); +util.puts('http server '.blue + 'started '.green.bold + 'on port '.blue + '9000 '.yellow); diff --git a/examples/jsonp-middleware.js b/examples/jsonp-middleware.js new file mode 100644 index 000000000..11cc65877 --- /dev/null +++ b/examples/jsonp-middleware.js @@ -0,0 +1,30 @@ +var Store = require('./lib/store') + , http = require('http') + +// +// jsonp is a handy technique for getting around the limitations of the same-origin policy. +// (http://en.wikipedia.org/wiki/Same_origin_policy) +// +// normally, to dynamically update a page you use an XmlHttpRequest. this has flakey support +// is some browsers and is restricted by the same origin policy. you cannot perform XHR requests to +// someone else's server. one way around this would be to proxy requests to all the servers you want +// to xhr to, and your core server - so that everything has the same port and host. +// +// another way, is to turn json into javascript. (which is exempt from the same origin policy) +// this is done by wrapping the json object in a function call, and then including a script tag. +// +// here we're proxing our own JSON returning server, but we could proxy any server on the internet, +// and our client side app would be slurping down JSONP from anywhere. +// +// curl localhost:1337/whatever?callback=alert +// alert([]) //which is valid javascript! +// +// also see http://en.wikipedia.org/wiki/JSONP#JSONP +// + +http.createServer(new Store().handler()).listen(7531) + +require('http-proxy').createServer( + require('connect-jsonp')(true), + 'localhost', 7531 +).listen(1337) diff --git a/examples/lib/store.js b/examples/lib/store.js new file mode 100644 index 000000000..8144b13b6 --- /dev/null +++ b/examples/lib/store.js @@ -0,0 +1,67 @@ + +module.exports = Store +// +// just to make these example a little bit interesting, +// make a little key value store with an http interface +// (see couchbd for a grown-up version of this) +// +// API: +// GET / +// retrive list of keys +// +// GET /[url] +// retrive object stored at [url] +// will respond with 404 if there is nothing stored at [url] +// +// POST /[url] +// +// JSON.parse the body and store it under [url] +// will respond 400 (bad request) if body is not valid json. +// +// TODO: cached map-reduce views and auto-magic sharding. +// + + + +function Store () { + this.store = {} +} +Store.prototype = { + get: function (key) { + return this.store[key] + }, + set: function (key, value) { + return this.store[key] = value + }, + handler:function () { + var store = this + return function (req, res) { + function send (obj, status) { + res.writeHead(200 || status,{'Content-Type': 'application/json'}) + res.write(JSON.stringify(obj) + '\n') + res.end() + } + var url = req.url.split('?').shift() + if(url === '/') { + console.log('get index') + return send(Object.keys(store.store)) + } else if(req.method == 'GET') { + var obj = store.get (url) + send(obj || {error: 'not_found', url: url}, obj ? 200 : 404) + } else { + //post: buffer body, and parse. + var body = '', obj + req.on('data', function (c) { body += c}) + req.on('end', function (c) { + try { + obj = JSON.parse(body) + } catch (err) { + return send (err, 400) + } + store.set(url, obj) + send({ok: true}) + }) + } + } + } +} diff --git a/examples/package.json b/examples/package.json new file mode 100644 index 000000000..16af680fe --- /dev/null +++ b/examples/package.json @@ -0,0 +1,12 @@ +{ + "name": "http-proxy-examples" +, "description": "packages required to run the examples" +, "version": "0.0.0" +, "dependencies": { + "connect": "1.6" + , "connect-gzip": "0.1" + , "connect-jsonp": "0.0.5" + , "connect-restreamer": "1" + , "proxy-by-url": "0.0.0" + } +} \ No newline at end of file diff --git a/examples/url-middleware.js b/examples/url-middleware.js new file mode 100644 index 000000000..669a73e12 --- /dev/null +++ b/examples/url-middleware.js @@ -0,0 +1,58 @@ +/* + url-middleware.js: Example of a simple url routing middleware for node-http-proxy + + Copyright (c) 2010 Charlie Robbins, Mikeal Rogers, Fedor Indutny, & Marak Squires. + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ + +var util = require('util'), + colors = require('colors'), + http = require('http'), + httpProxy = require('http-proxy'); + +// +// Now we set up our proxy. +// +httpProxy.createServer( + // + // This is where our middlewares go, with any options desired - in this case, + // the list of routes/URLs and their destinations. + // + require('proxy-by-url')({ + '/hello': { port: 9000, host: 'localhost' }, + '/charlie': { port: 80, host: 'charlieistheman.com' }, + '/google': { port: 80, host: 'google.com' } + }); +).listen(8000); + +// +// Target Http Server (to listen for requests on 'localhost') +// +http.createServer(function (req, res) { + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.write('request successfully proxied to: ' + req.url + '\n' + JSON.stringify(req.headers, true, 2)); + res.end(); +}).listen(9000); + +// And finally, some colored startup output. +util.puts('http proxy server'.blue + ' started '.green.bold + 'on port '.blue + '8000'.yellow); +util.puts('http server '.blue + 'started '.green.bold + 'on port '.blue + '9000 '.yellow); \ No newline at end of file diff --git a/examples/url-middleware2.js b/examples/url-middleware2.js new file mode 100644 index 000000000..95dfef46b --- /dev/null +++ b/examples/url-middleware2.js @@ -0,0 +1,31 @@ +var util = require('util'), + colors = require('colors'), + http = require('http'), + httpProxy = require('http-proxy'), + Store = require('./lib/store') + +http.createServer(new Store().handler()).listen(7531) + +// Now we set up our proxy. +httpProxy.createServer( + // This is where our middlewares go, with any options desired - in this case, + // the list of routes/URLs and their destinations. + require('proxy-by-url')({ + '/store': { port: 7531, host: 'localhost' }, + '/': { port: 9000, host: 'localhost' } + }) +).listen(8000); + +// +// Target Http Server (to listen for requests on 'localhost') +// +http.createServer( + function (req, res) { + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.write('request successfully proxied to: ' + req.url + '\n' + JSON.stringify(req.headers, true, 2)); + res.end(); + }).listen(9000); + +// And finally, some colored startup output. +util.puts('http proxy server'.blue + ' started '.green.bold + 'on port '.blue + '8000'.yellow); +util.puts('http server '.blue + 'started '.green.bold + 'on port '.blue + '9000 '.yellow); \ No newline at end of file diff --git a/examples/web-socket-proxy.js b/examples/web-socket-proxy.js index d94898883..d7a8efa71 100644 --- a/examples/web-socket-proxy.js +++ b/examples/web-socket-proxy.js @@ -47,6 +47,7 @@ var server = http.createServer(function (req, res) { res.writeHead(200); res.end(); }); + server.listen(8080); // diff --git a/lib/node-http-proxy.js b/lib/node-http-proxy.js index 2e41b4236..ff22317bc 100644 --- a/lib/node-http-proxy.js +++ b/lib/node-http-proxy.js @@ -1,7 +1,7 @@ /* node-http-proxy.js: http proxy for node.js - Copyright (c) 2010 Charlie Robbins, Mikeal Rogers, Marak Squires, Fedor Indutny + Copyright (c) 2010 Charlie Robbins, Mikeal Rogers, Marak Squires, Fedor Indutny Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -51,16 +51,16 @@ var _agents = {}; // function _getAgent (host, port, secure) { var Agent, id = [host, port].join(':'); - + if (!port) { port = secure ? 443 : 80; } - + if (!_agents[id]) { Agent = secure ? https.Agent : http.Agent; - _agents[id] = new Agent({ - host: host, + _agents[id] = new Agent({ + host: host, port: port, maxSockets: maxSockets }); @@ -70,17 +70,17 @@ function _getAgent (host, port, secure) { } // -// ### function _getProtocol (secure, outgoing) +// ### function _getProtocol (secure, outgoing) // #### @secure {Object|boolean} Settings for `https` // #### @outgoing {Object} Outgoing request options -// Returns the appropriate protocol based on the settings in +// Returns the appropriate protocol based on the settings in // `secure`. If the protocol is `https` this function will update // the options in `outgoing` as appropriate by adding `ca`, `key`, // and `cert` if they exist in `secure`. // function _getProtocol (secure, outgoing) { var protocol = secure ? https : http; - + if (typeof secure === 'object') { outgoing = outgoing || {}; ['ca', 'cert', 'key'].forEach(function (prop) { @@ -89,7 +89,7 @@ function _getProtocol (secure, outgoing) { } }) } - + return protocol; } @@ -137,6 +137,34 @@ function stack (middlewares, proxy) { return handle; } +// +// stack +// adapted from https://github.com/creationix/stack +// +function stack (middlewares, proxy) { + var handle; + middlewares.reverse().forEach(function (layer) { + var child = handle; + handle = function (req, res) { + var next = function (err) { + if (err) { + throw err; + // + // TODO: figure out where to send errors. + // return error(req, res, err); + // + } + child(req, res); + } + + next.__proto__ = proxy; + layer(req, res, next); + }; + }); + + return handle; +} + // // ### function createServer ([port, host, options, handler]) // #### @port {number} **Optional** Port to use on the proxy target host. @@ -150,87 +178,101 @@ function stack (middlewares, proxy) { // * `httpPRoxy.createServer(function (req, res, proxy) { ... })` // exports.createServer = function () { - var args = Array.prototype.slice.call(arguments), - callback, - options = {}, port, host, forward, silent, proxy, server, middleware = []; - + var args = Array.prototype.slice.call(arguments), + callback, forward, + port, host, + proxy, server, + options = {}, + middleware = [], + silent; + args.forEach(function (arg) { - switch (typeof arg) { case 'string': host = arg; break; case 'number': port = arg; break; - case 'function': middleware.push(arg); break; + case 'function': middleware.push(handler = callback = arg); break; case 'object': options = arg; break; }; - }); var proxy = new HttpProxy(options); - if(middleware.length) - //handler = callback = middleware.shift() - //else if (middleware.length) - handler = callback = stack(middleware, proxy); + if (port && host) { + // + // If we have a target host and port for the request + // then proxy to the specified location. + // + handler = function (req, res) { + proxy.proxyRequest(req, res, { + port: port, + host: host + }); + } - if (port && host) { - // - // If we have a target host and port for the request - // then proxy to the specified location. - // - handler = function (req, res) { - proxy.proxyRequest(req, res, { - port: port, - host: host - }); - } + if (middleware.length) { + middleware.push(handler); } - else if (proxy.proxyTable) { - // - // If the proxy is configured with a ProxyTable - // instance then use that before failing. - // - handler = function (req, res) { - proxy.proxyRequest(req, res); - } + } + else if (proxy.proxyTable) { + // + // If the proxy is configured with a ProxyTable + // instance then use that before failing. + // + handler = function (req, res) { + proxy.proxyRequest(req, res); } - else if (!handler) { - // - // Otherwise this server is improperly configured. - // - throw new Error('Cannot proxy without port, host, or router.') + + if (middleware.length) { + middleware.push(handler); } + } + + if (middleware.length > 1) { + handler = callback = stack(middleware, proxy); + } + else if (middleware.length) { + //do not use middleware code if it's not needed. + var h = middleware[0]; + handler = callback = function (req,res) { h(req,res,proxy) }; + } - server = options.https + if (!handler) { + // + // Otherwise this server is improperly configured. + // + throw new Error('Cannot proxy without port, host, or router.') + } + + server = options.https ? https.createServer(options.https, handler) : http.createServer(handler); server.on('close', function () { proxy.close(); }); - + proxy.on('routes', function (routes) { server.emit('routes', routes); }); if (!callback) { - // WebSocket support: if callback is empty tunnel + // WebSocket support: if callback is empty tunnel // websocket request automatically server.on('upgrade', function (req, socket, head) { // Tunnel websocket requests too - proxy.proxyWebSocketRequest(req, socket, head, { port: port, host: host }); }); } - + // // Set the proxy on the server so it is available // to the consumer of the server // server.proxy = proxy; - + return server; }; @@ -251,23 +293,23 @@ exports.createServer = function () { // host: 'localhost', // port: 9001 // } -// } +// } // var HttpProxy = exports.HttpProxy = function (options) { events.EventEmitter.call(this); - + var self = this; options = options || {}; - + // // Setup basic proxying options // this.https = options.https; this.forward = options.forward; this.target = options.target || {}; - + // - // Setup additional options for WebSocket proxying. When forcing + // Setup additional options for WebSocket proxying. When forcing // the WebSocket handshake to change the `sec-websocket-location` // and `sec-websocket-origin` headers `options.source` **MUST** // be provided or the operation will fail with an `origin mismatch` @@ -275,7 +317,7 @@ var HttpProxy = exports.HttpProxy = function (options) { // this.source = options.source || { host: 'localhost', port: 8000 }; this.changeOrigin = options.changeOrigin || false; - + if (options.router) { this.proxyTable = new ProxyTable(options.router, options.silent, options.hostnameOnly); this.proxyTable.on('routes', function (routes) { @@ -288,10 +330,10 @@ var HttpProxy = exports.HttpProxy = function (options) { util.inherits(HttpProxy, events.EventEmitter); // -// ### function buffer (obj) +// ### function buffer (obj) // #### @obj {Object} Object to pause events from // Buffer `data` and `end` events from the given `obj`. -// Consumers of HttpProxy performing async tasks +// Consumers of HttpProxy performing async tasks // __must__ utilize this utility, to re-emit data once // the async operation has completed, otherwise these // __events will be lost.__ @@ -301,9 +343,9 @@ util.inherits(HttpProxy, events.EventEmitter); // httpProxy.proxyRequest(req, res, host, port, buffer); // }); // -// __Attribution:__ This approach is based heavily on +// __Attribution:__ This approach is based heavily on // [Connect](https://github.com/senchalabs/connect/blob/master/lib/utils.js#L157). -// However, this is not a big leap from the implementation in node-http-proxy < 0.4.0. +// However, this is not a big leap from the implementation in node-http-proxy < 0.4.0. // This simply chooses to manage the scope of the events on a new Object literal as opposed to // [on the HttpProxy instance](https://github.com/nodejitsu/node-http-proxy/blob/v0.3.1/lib/node-http-proxy.js#L154). // @@ -335,10 +377,12 @@ HttpProxy.prototype.buffer = function (obj) { // // ### function close () // Frees the resources associated with this instance, -// if they exist. +// if they exist. // HttpProxy.prototype.close = function () { - if (this.proxyTable) this.proxyTable.close(); + if (this.proxyTable) { + this.proxyTable.close(); + } }; // @@ -355,18 +399,18 @@ HttpProxy.prototype.close = function () { // HttpProxy.prototype.proxyRequest = function (req, res, options) { var self = this, errState = false, location, outgoing, protocol, reverseProxy; - + // // Create an empty options hash if none is passed. - // If default options have been passed to the constructor + // If default options have been passed to the constructor // of this instance, use them by default. // options = options || {}; options.host = options.host || this.target.host; options.port = options.port || this.target.port; - options.enableXForwarded = + options.enableXForwarded = (undefined === options.enableXForwarded ? true : options.enableXForwarded); - + // // Check the proxy table for this instance to see if we need // to get the proxy location for the request supplied. We will @@ -375,7 +419,7 @@ HttpProxy.prototype.proxyRequest = function (req, res, options) { // if (this.proxyTable && !options.host) { location = this.proxyTable.getProxyLocation(req); - + // // If no location is returned from the ProxyTable instance // then respond with `404` since we do not have a valid proxy target. @@ -384,38 +428,38 @@ HttpProxy.prototype.proxyRequest = function (req, res, options) { res.writeHead(404); return res.end(); } - + // // When using the ProxyTable in conjunction with an HttpProxy instance // only the following arguments are valid: - // + // // * `proxy.proxyRequest(req, res, { host: 'localhost' })`: This will be skipped // * `proxy.proxyRequest(req, res, { buffer: buffer })`: Buffer will get updated appropriately - // * `proxy.proxyRequest(req, res)`: Options will be assigned appropriately. + // * `proxy.proxyRequest(req, res)`: Options will be assigned appropriately. // options.port = location.port; options.host = location.host; } - + // - // Add common proxy headers to the request so that they can + // Add common proxy headers to the request so that they can // be availible to the proxy target server: - // + // // * `x-forwarded-for`: IP Address of the original request // * `x-forwarded-proto`: Protocol of the original request - // * `x-forwarded-port`: Port of the original request. + // * `x-forwarded-port`: Port of the original request. // - if (options.enableXForwarded == true) { + if (options.enableXForwarded === true) { req.headers['x-forwarded-for'] = req.connection.remoteAddress || req.connection.socket.remoteAddress; req.headers['x-forwarded-port'] = req.connection.remotePort || req.connection.socket.remotePort; req.headers['x-forwarded-proto'] = res.connection.pair ? 'https' : 'http'; } - + // // Emit the `start` event indicating that we have begun the proxy operation. // this.emit('start', req, res, options); - + // // If forwarding is enabled for this instance, foward proxy the // specified request to the address provided in `this.forward` @@ -424,16 +468,16 @@ HttpProxy.prototype.proxyRequest = function (req, res, options) { this.emit('forward', req, res, this.forward); this._forwardRequest(req); } - + // // #### function proxyError (err) // #### @err {Error} Error contacting the proxy target - // Short-circuits `res` in the event of any error when + // Short-circuits `res` in the event of any error when // contacting the proxy target at `host` / `port`. // function proxyError(err) { errState = true; - + // // Emit an `error` event, allowing the application to use custom // error handling. The error handler should end the response. @@ -456,10 +500,10 @@ HttpProxy.prototype.proxyRequest = function (req, res, options) { res.write('An error has occurred: ' + JSON.stringify(err)); } } - + res.end(); } - + outgoing = { host: options.host, port: options.port, @@ -468,12 +512,12 @@ HttpProxy.prototype.proxyRequest = function (req, res, options) { path: req.url, headers: req.headers }; - + protocol = _getProtocol(options.https || this.target.https, outgoing); - + // Open new HTTP request to internal resource with will act as a reverse proxy pass reverseProxy = protocol.request(outgoing, function (response) { - + // Process the `reverseProxy` `response` when it's received. if (response.headers.connection) { if (req.headers.connection) response.headers.connection = req.headers.connection; @@ -505,17 +549,17 @@ HttpProxy.prototype.proxyRequest = function (req, res, options) { if (!errState) { reverseProxy.removeListener('error', proxyError); res.end(); - + // Emit the `end` event now that we have completed proxying self.emit('end', req, res); } }); }); - + // Handle 'error' events from the `reverseProxy`. reverseProxy.once('error', proxyError); - - // For each data `chunk` received from the incoming + + // For each data `chunk` received from the incoming // `req` write it to the `reverseProxy` request. req.on('data', function (chunk) { if (!errState) { @@ -524,8 +568,8 @@ HttpProxy.prototype.proxyRequest = function (req, res, options) { }); // - // When the incoming `req` ends, end the corresponding `reverseProxy` - // request unless we have entered an error state. + // When the incoming `req` ends, end the corresponding `reverseProxy` + // request unless we have entered an error state. // req.on('end', function () { if (!errState) { @@ -538,7 +582,7 @@ HttpProxy.prototype.proxyRequest = function (req, res, options) { options.buffer.resume(); } }; - + // // ### @private function _forwardRequest (req) // #### @req {ServerRequest} Incoming HTTP Request to proxy. @@ -550,7 +594,7 @@ HttpProxy.prototype._forwardRequest = function (req) { port = this.forward.port; host = this.forward.host; - + outgoing = { host: host, port: port, @@ -559,13 +603,13 @@ HttpProxy.prototype._forwardRequest = function (req) { path: req.url, headers: req.headers }; - + // Force the `connection` header to be 'close' until // node.js core re-implements 'keep-alive'. outgoing.headers['connection'] = 'close'; - + protocol = _getProtocol(this.forward.https, outgoing); - + // Open new HTTP request to internal resource with will act as a reverse proxy pass forwardProxy = protocol.request(outgoing, function (response) { // @@ -573,10 +617,10 @@ HttpProxy.prototype._forwardRequest = function (req) { // Remark (indexzero): We will eventually emit a 'forward' event here for performance tuning. // }); - + // Add a listener for the connection timeout event. // - // Remark: Ignoring this error in the event + // Remark: Ignoring this error in the event // forward target doesn't exist. // forwardProxy.once('error', function (err) { }); @@ -594,7 +638,7 @@ HttpProxy.prototype._forwardRequest = function (req) { // // ### function proxyWebSocketRequest (req, socket, head, options) -// #### @req {ServerRequest} Websocket request to proxy. +// #### @req {ServerRequest} Websocket request to proxy. // #### @socket {net.Socket} Socket for the underlying HTTP request // #### @head {string} Headers for the Websocket request. // #### @options {Object} Options to use when proxying this request. @@ -605,28 +649,29 @@ HttpProxy.prototype._forwardRequest = function (req) { // options.https {Object|boolean} Settings for https. // HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, options) { - var self = this, + var self = this, listeners = {}, - errState = false, + errState = false, CRLF = '\r\n', outgoing; options = options || {}; options.host = options.host || this.target.host; options.port = options.port || this.target.port; - + if (this.proxyTable && !options.host) { location = this.proxyTable.getProxyLocation(req); - + if (!location) { - res.writeHead(404); - return res.end(); + return socket.destroy(); } + options.port = location.port; options.host = location.host; } + // - // WebSocket requests must have the `GET` method and + // WebSocket requests must have the `GET` method and // the `upgrade:websocket` header // if (req.method !== 'GET' || req.headers.upgrade.toLowerCase() !== 'websocket') { @@ -635,7 +680,7 @@ HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, options // return; } - + // // Helper function for setting appropriate socket values: // 1. Turn of all bufferings @@ -652,14 +697,14 @@ HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, options else if (socket.pair.cleartext.socket.setKeepAlive) { socket.pair.cleartext.socket.setKeepAlive(true, 0); } - } + } else { socket.setEncoding('utf8'); } } - + // - // On `upgrade` from the Agent socket, listen to + // On `upgrade` from the Agent socket, listen to // the appropriate events. // function onUpgrade (reverseProxy, proxySocket) { @@ -668,7 +713,7 @@ HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, options socket.end(); return; } - + // // Any incoming data on this WebSocket to the proxy target // will be written to the `reverseProxy` socket. @@ -678,7 +723,7 @@ HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, options try { self.emit('websocket:outgoing', req, socket, head, data); reverseProxy.incoming.socket.write(data); - } + } catch (e) { reverseProxy.incoming.socket.end(); proxySocket.end(); @@ -694,15 +739,15 @@ HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, options try { self.emit('websocket:incoming', reverseProxy, reverseProxy.incoming, head, data); proxySocket.write(data); - } + } catch (e) { proxySocket.end(); socket.end(); } }); - + // - // Helper function to detach all event listeners + // Helper function to detach all event listeners // from `reverseProxy` and `proxySocket`. // function detach() { @@ -713,19 +758,19 @@ HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, options } // - // If the incoming `proxySocket` socket closes, then - // detach all event listeners. + // If the incoming `proxySocket` socket closes, then + // detach all event listeners. // proxySocket.on('end', listeners.onIncomingClose = function() { reverseProxy.incoming.socket.end(); detach(); - + // Emit the `end` event now that we have completed proxying self.emit('websocket:end', req, socket, head); }); // - // If the `reverseProxy` socket closes, then detach all + // If the `reverseProxy` socket closes, then detach all // event listeners. // reverseProxy.incoming.socket.on('end', listeners.onOutgoingClose = function() { @@ -736,12 +781,12 @@ HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, options // Setup the incoming client socket. _socket(socket); - + function getPort (port) { port = port || 80; return port - 80 === 0 ? '' : ':' + port } - + // // Get the protocol, and host for this request and create an instance // of `http.Agent` or `https.Agent` from the pool managed by `node-http-proxy`. @@ -756,7 +801,7 @@ HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, options req.headers.host = remoteHost; req.headers.origin = protocolName + '://' + remoteHost; } - + // // Make the outgoing WebSocket request // @@ -767,10 +812,11 @@ HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, options path: req.url, headers: req.headers, }; + var reverseProxy = agent.appendMessage(outgoing); // - // On any errors from the `reverseProxy` emit the + // On any errors from the `reverseProxy` emit the // `webSocketProxyError` and close the appropriate // connections. // @@ -779,14 +825,14 @@ HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, options if (self.emit('webSocketProxyError', req, socket, head)) { return; } - + socket.end(); } // // Here we set the incoming `req`, `socket` and `head` data to the outgoing // request so that we can reuse this data later on in the closure scope - // available to the `upgrade` event. This bookkeeping is not tracked anywhere + // available to the `upgrade` event. This bookkeeping is not tracked anywhere // in nodejs core and is **very** specific to proxying WebSockets. // reverseProxy.agent = agent; @@ -795,27 +841,27 @@ HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, options socket: socket, head: head }; - + // // If the agent for this particular `host` and `port` combination // is not already listening for the `upgrade` event, then do so once. - // This will force us not to disconnect. + // This will force us not to disconnect. // // In addition, it's important to note the closure scope here. Since - // there is no mapping of the + // there is no mapping of the // if (!agent._events || agent._events['upgrade'].length === 0) { agent.on('upgrade', function (_, remoteSocket, head) { // - // Prepare the socket for the reverseProxy request and begin to - // stream data between the two sockets. Here it is important to + // Prepare the socket for the reverseProxy request and begin to + // stream data between the two sockets. Here it is important to // note that `remoteSocket._httpMessage === reverseProxy`. // _socket(remoteSocket, true); onUpgrade(remoteSocket._httpMessage, remoteSocket); }); } - + // // If the reverseProxy connection has an underlying socket, // then execute the WebSocket handshake. @@ -824,9 +870,9 @@ HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, options reverseProxy.socket.on('data', function handshake (data) { // // Ok, kind of harmfull part of code. Socket.IO sends a hash - // at the end of handshake if protocol === 76, but we need - // to replace 'host' and 'origin' in response so we split - // data to printable data and to non-printable. (Non-printable + // at the end of handshake if protocol === 76, but we need + // to replace 'host' and 'origin' in response so we split + // data to printable data and to non-printable. (Non-printable // will come after double-CRLF). // var sdata = data.toString(); @@ -836,7 +882,7 @@ HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, options // Get the Non-Printable data data = data.slice(Buffer.byteLength(sdata), data.length); - + if (self.https && !self.target.https) { // // If the proxy server is running HTTPS but the client is running @@ -848,12 +894,12 @@ HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, options try { // // Write the printable and non-printable data to the socket - // from the original incoming request. - // + // from the original incoming request. + // self.emit('websocket:handshake', req, socket, head, sdata, data); socket.write(sdata); socket.write(data); - } + } catch (ex) { proxyError(ex) } @@ -865,7 +911,7 @@ HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, options reverseProxy.socket.removeListener('data', handshake); }); } - + reverseProxy.on('error', proxyError); try { @@ -873,11 +919,11 @@ HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, options // Attempt to write the upgrade-head to the reverseProxy request. // reverseProxy.write(head); - } + } catch (ex) { proxyError(ex); } - + // // If we have been passed buffered data, resume it. //