diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..099c4d9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +node_modules +*.swp +cov +atest.js +notes +primus-proxy.js +tes.js diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..ba5be41 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,12 @@ +language: node_js +node_js: + - "0.10" + - "0.11" + +notifications: + email: + - travis@nodejitsu.com + irc: "irc.freenode.org#nodejitsu" + +script: + npm test diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..21d6bb0 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,4 @@ +### v 1.0.0-alpha + +- Complete refactor with new API + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2bab4b9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,23 @@ + + node-http-proxy + + Copyright (c) Nodejitsu 2013 + + 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. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..3ec4ed5 --- /dev/null +++ b/README.md @@ -0,0 +1,322 @@ +

+ +

+ +node-http-proxy +======= + +`node-http-proxy` is an HTTP programmable proxying library that supports +websockets. It is suitable for implementing components such as +proxies and load balancers. + +### Build Status + +

+ +    + + +

+ +### Looking to Upgrade from 0.8.x ? Click [here](UPGRADING.md) + +### Core Concept + +A new proxy is created by calling `createProxyServer` and passing +an `options` object as argument ([valid properties are available here](lib/http-proxy.js#L26-L39)) + +```javascript +var httpProxy = require('http-proxy'); + +var proxy = httpProxy.createProxyServer(options); +``` + +An object will be returned with four values: + +* web `req, res, [options]` (used for proxying regular HTTP(S) requests) +* ws `req, socket, head, [options]` (used for proxying WS(S) requests) +* listen `port` (a function that wraps the object in a webserver, for your convenience) + +Is it then possible to proxy requests by calling these functions + +```javascript +require('http').createServer(function(req, res) { + proxy.web(req, res, { target: 'http://mytarget.com:8080' }); +}); +``` + +Errors can be listened on either using the Event Emitter API + +```javascript +proxy.on('error', function(e) { + ... +}); +``` + +or using the callback API + +```javascript +proxy.web(req, res, { target: 'http://mytarget.com:8080' }, function(e) { ... }); +``` + +When a request is proxied it follows two different pipelines ([available here](lib/http-proxy/passes)) +which apply transformations to both the `req` and `res` object. +The first pipeline (ingoing) is responsible for the creation and manipulation of the stream that connects your client to the target. +The second pipeline (outgoing) is responsible for the creation and manipulation of the stream that, from your target, returns data +to the client. + + +#### Setup a basic stand-alone proxy server + +```js +var http = require('http'), + httpProxy = require('http-proxy'); +// +// Create your proxy server and set the target in the options. +// +httpProxy.createProxyServer({target:'http://localhost:9000'}).listen(8000); + +// +// Create your target server +// +http.createServer(function (req, res) { + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.write('request successfully proxied!' + '\n' + JSON.stringify(req.headers, true, 2)); + res.end(); +}).listen(9000); +``` + +#### Setup a stand-alone proxy server with custom server logic +This example show how you can proxy a request using your own HTTP server +and also you can put your own logic to handle the request. + +```js +var http = require('http'), + httpProxy = require('http-proxy'); + +// +// Create a proxy server with custom application logic +// +var proxy = httpProxy.createProxyServer({}); + +// +// Create your custom server and just call `proxy.web()` to proxy +// a web request to the target passed in the options +// also you can use `proxy.ws()` to proxy a websockets request +// +var server = require('http').createServer(function(req, res) { + // You can define here your custom logic to handle the request + // and then proxy the request. + proxy.web(req, res, { target: 'http://127.0.0.1:5060' }); +}); + +console.log("listening on port 5050") +server.listen(5050); +``` + +#### Setup a stand-alone proxy server with latency + +```js +var http = require('http'), + httpProxy = require('http-proxy'); + +// +// Create a proxy server with latency +// +var proxy = httpProxy.createProxyServer(); + +// +// Create your server that make an operation that take a while +// and then proxy de request +// +http.createServer(function (req, res) { + // This simulate an operation that take 500ms in execute + setTimeout(function () { + proxy.web(req, res, { + target: 'http://localhost:9008' + }); + }, 500); +}).listen(8008); + +// +// Create your target 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(9008); +``` + +#### Listening for proxy events + +* `error`: The error event is emitted if the request to the target fail. +* `proxyRes`: This event is emitted if the request to the target got a response. + +```js +var httpProxy = require('http-proxy'); +// Error example +// +// Http Proxy Server with bad target +// +var proxy = httpProxy.createServer({ + target:'http://localhost:9005' +}); + +proxy.listen(8005); + +// +// Listen for the `error` event on `proxy`. +proxy.on('error', function (err, req, res) { + res.writeHead(500, { + 'Content-Type': 'text/plain' + }); + + res.end('Something went wrong. And we are reporting a custom error message.'); +}); + +// +// Listen for the `proxyRes` event on `proxy`. +// +proxy.on('proxyRes', function (res) { + console.log('RAW Response from the target', JSON.stringify(res.headers, true, 2)); +}); + +``` + +#### Using HTTPS +You can activate the validation of a secure SSL certificate to the target connection (avoid self signed certs), just set `secure: true` in the options. + +##### HTTPS -> HTTP + +```js +// +// Create the HTTPS proxy server in front of a HTTP server +// +httpProxy.createServer({ + target: { + host: 'localhost', + port: 9009 + }, + ssl: { + key: fs.readFileSync('valid-ssl-key.pem'), 'utf8'), + cert: fs.readFileSync('valid-ssl-cert.pem'), 'utf8') + } +}).listen(8009); +``` + +##### HTTPS -> HTTPS + +```js +// +// Create the proxy server listening on port 443 +// +httpProxy.createServer({ + ssl: { + key: fs.readFileSync('valid-ssl-key.pem'), 'utf8'), + cert: fs.readFileSync('valid-ssl-cert.pem'), 'utf8') + }, + target: 'https://localhost:9010', + secure: true // Depends on your needs, could be false. +}).listen(443); +``` + +#### Proxying WebSockets +You can activate the websocket support for the proxy using `ws:true` in the options. + +```js +// +// Create a proxy server for websockets +// +httpProxy.createServer({ + target: 'ws://localhost:9014', + ws: true +}).listen(8014); +``` + +Also you can proxy the websocket requests just calling the `ws(req, socket, head)` method. + +```js +// +// Setup our server to proxy standard HTTP requests +// +var proxy = new httpProxy.createProxyServer({ + target: { + host: 'localhost', + port: 9015 + } +}); +var proxyServer = http.createServer(function (req, res) { + proxy.web(req, res); +}); + +// +// Listen to the `upgrade` event and proxy the +// WebSocket requests as well. +// +proxyServer.on('upgrade', function (req, socket, head) { + proxy.ws(req, socket, head); +}); + +proxyServer.listen(8015); +``` + +### Contributing and Issues + +* Search on Google/Github +* If you can't find anything, open an issue +* If you feel comfortable about fixing the issue, fork the repo +* Commit to your local branch (which must be different from `master`) +* Submit your Pull Request (be sure to include tests and update documentation) + +### Options + +`httpProxy.createProxyServer` supports the following options: + + * **target**: url string to be parsed with the url module + * **forward**: url string to be parsed with the url module + * **agent**: object to be passed to http(s).request (see Node's [https agent](http://nodejs.org/api/https.html#https_class_https_agent) and [http agent](http://nodejs.org/api/http.html#http_class_http_agent) objects) + * **secure**: true/false, if you want to verify the SSL Certs + +If you are using the `proxyServer.listen` method, the following options are also applicable: + + * **ssl**: object to be passed to https.createServer() + * **ws**: true/false, if you want to proxy websockets + * **xfwd**: true/false, adds x-forward headers + + +### Test + +``` +$ npm test +``` + +### Logo + +Logo created by [Diego Pasquali](http://dribbble.com/diegopq) + +### License + +>The MIT License (MIT) +> +>Copyright (c) 2010 - 2013 Nodejitsu Inc. +> +>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. + + diff --git a/UPGRADING.md b/UPGRADING.md new file mode 100644 index 0000000..8c0db43 --- /dev/null +++ b/UPGRADING.md @@ -0,0 +1,96 @@ +Looking to upgrade from `http-proxy@0.x.x` to `http-proxy@1.0`? You've come to the right place! +`http-proxy@1.0` is a from-scratch implementation of `http-proxy` and, as such +brings some breaking changes to APIs. + +## Server creation + +Available through `.createServer()` or `.createProxyServer()`. + +```javascript +httpProxy.createServer({ + target:'http://localhost:9003' +}).listen(8003); +``` + +Check the [README.md](https://github.com/nodejitsu/node-http-proxy/blob/caronte/README.md) for a more detailed explanation of the parameters. + +## Proxying + +Web proying is done by calling the `.web()` method on a Proxy instance. You can check among some use cases in the [examples folder](https://github.com/nodejitsu/node-http-proxy/tree/caronte/examples/http) + +```javascript +// +// Create a HTTP Proxy server with a HTTPS target +// +httpProxy.createProxyServer({ + target: 'https://google.com', + agent : https.globalAgent, + headers: { + host: 'google.com' + } +}).listen(8011); + +``` + +Websockets are proxied by the `.ws()` method. The [examples folder](https://github.com/nodejitsu/node-http-proxy/tree/caronte/examples/websocket) again provides a lot of useful snippets! + +```javascript +var proxy = new httpProxy.createProxyServer({ + target: { + host: 'localhost', + port: 9015 + } +}); +var proxyServer = http.createServer(function (req, res) { + proxy.web(req, res); +}); + +// +// Listen to the `upgrade` event and proxy the +// WebSocket requests as well. +// +proxyServer.on('upgrade', function (req, socket, head) { + proxy.ws(req, socket, head); +}); +``` + +## Error Handling + +It is possible to listen globally on the `error` event on the server. In alternative, a +callback passed to `.web()` or `.ws()` as last parameter is also accepted. + +```javascript +var proxy = httpProxy.createServer({ + target:'http://localhost:9005' +}); + +// +// Tell the proxy to listen on port 8000 +// +proxy.listen(8005); + +// +// Listen for the `error` event on `proxy`. +proxy.on('error', function (err, req, res) { + res.writeHead(500, { + 'Content-Type': 'text/plain' + }); + + res.end('Something went wrong. And we are reporting a custom error message.'); +}); +``` + +## Dropped + +Since the API was rewritten to be extremely flexible we decided to drop some features +which were in the core and delegate them to eventual "userland" modules. + +- Middleware API +- ProxyTable API + +### Middleware API + +The new API makes it really easy to implement code that behaves like the old Middleware API. You can check some examples [here](https://github.com/nodejitsu/node-http-proxy/tree/caronte/examples/middleware) + + + diff --git a/benchmark/README.md b/benchmark/README.md new file mode 100644 index 0000000..2a852d1 --- /dev/null +++ b/benchmark/README.md @@ -0,0 +1,33 @@ +# Benchmarking `node-http-proxy` + +The long-term goal of these scripts and documentation is to provide a consistent and well understood benchmarking process for `node-http-proxy` so that performance does not degrade over time. They were initially created to compare the performance of `v0.10.3` and `v1.0.0` (which was a significant rewrite). + +## Pre-requisites + +All benchmarking shall be done with [wrk](https://github.com/wg/wrk) which _is the same tool used for performance testing by the node.js core team._ **Make sure you have `wrk` installed before continuing**. + +``` +$ wrk +Usage: wrk + Options: + -c, --connections Connections to keep open + -r, --requests Total requests to make + -t, --threads Number of threads to use + + -H, --header Add header to request + -v, --version Print version details + + Numeric arguments may include a SI unit (2k, 2M, 2G) +``` + +## Benchmarks + +1. [Simple HTTP benchmark](#simple-http) + +### Simple HTTP + +_This benchmark requires three terminals running:_ + +1. **A proxy server:** `node benchmark/scripts/proxy.js` +2. **A target server:** `node benchmark/scripts/hello.js` +3. **A wrk process:** `wrk -c 20 -r 10000 -t 2 http://127.0.0.1:8000` \ No newline at end of file diff --git a/benchmark/scripts/hello.js b/benchmark/scripts/hello.js new file mode 100644 index 0000000..be09b63 --- /dev/null +++ b/benchmark/scripts/hello.js @@ -0,0 +1,3 @@ +require('http').createServer(function(req, res) { + res.end('Hello world!'); +}).listen(9000); \ No newline at end of file diff --git a/benchmark/scripts/proxy.js b/benchmark/scripts/proxy.js new file mode 100644 index 0000000..a70c558 --- /dev/null +++ b/benchmark/scripts/proxy.js @@ -0,0 +1,6 @@ +var http = require('http'), + httpProxy = require('../../'); +// +// Create your proxy server +// +httpProxy.createProxyServer({ target: 'http://localhost:9000' }).listen(8000); \ No newline at end of file diff --git a/benchmark/scripts/websockets-throughput.js b/benchmark/scripts/websockets-throughput.js new file mode 100644 index 0000000..866e424 --- /dev/null +++ b/benchmark/scripts/websockets-throughput.js @@ -0,0 +1,88 @@ +var crypto = require('crypto'), + WebSocket = require('ws'), + async = require('async'), + httpProxy = require('../../'); + +var SERVER_PORT = 8415, + PROXY_PORT = 8514; + +var testSets = [ + { + size: 1024 * 1024, // 1 MB + count: 128 // 128 MB + }, + { + size: 1024, // 1 KB, + count: 1024 // 1 MB + }, + { + size: 128, // 128 B + count: 1024 * 8 // 1 MB + } +]; + +testSets.forEach(function (set) { + set.buffer = new Buffer(crypto.randomBytes(set.size)); + + set.buffers = []; + for (var i = 0; i < set.count; i++) { + set.buffers.push(set.buffer); + } +}); + +function runSet(set, callback) { + function runAgainst(port, callback) { + function send(sock) { + sock.send(set.buffers[got++]); + if (got === set.count) { + t = new Date() - t; + + server.close(); + proxy.close(); + + callback(null, t); + } + } + + var server = new WebSocket.Server({ port: SERVER_PORT }), + proxy = httpProxy.createServer(SERVER_PORT, 'localhost').listen(PROXY_PORT), + client = new WebSocket('ws://localhost:' + port), + got = 0, + t = new Date(); + + server.on('connection', function (ws) { + send(ws); + + ws.on('message', function (msg) { + send(ws); + }); + }); + + client.on('message', function () { + send(client); + }); + } + + async.series({ + server: async.apply(runAgainst, SERVER_PORT), + proxy: async.apply(runAgainst, PROXY_PORT) + }, function (err, results) { + if (err) { + throw err; + } + + var mb = (set.size * set.count) / (1024 * 1024); + console.log(set.size / (1024) + ' KB * ' + set.count + ' (' + mb + ' MB)'); + + Object.keys(results).forEach(function (key) { + var t = results[key], + throughput = mb / (t / 1000); + + console.log(' ' + key + ' took ' + t + ' ms (' + throughput + ' MB/s)'); + }); + + callback(); + }); +} + +async.forEachLimit(testSets, 1, runSet); diff --git a/doc/logo.png b/doc/logo.png new file mode 100644 index 0000000..a36bdcf Binary files /dev/null and b/doc/logo.png differ diff --git a/examples/balancer/simple-balancer-with-websockets.js b/examples/balancer/simple-balancer-with-websockets.js new file mode 100644 index 0000000..cc13f4b --- /dev/null +++ b/examples/balancer/simple-balancer-with-websockets.js @@ -0,0 +1,84 @@ +/* + simple-balancer.js: Example of a simple round robin balancer for websockets + + Copyright (c) Nodejitsu 2013 + + 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 http = require('http'), + httpProxy = require('../../lib/http-proxy'); + +// +// A simple round-robin load balancing strategy. +// +// First, list the servers you want to use in your rotation. +// +var addresses = [ + { + host: 'ws1.0.0.0', + port: 80 + }, + { + host: 'ws2.0.0.0', + port: 80 + } +]; + +// +// Create a HttpProxy object for each target +// + +var proxies = addresses.map(function (target) { + return new httpProxy.createProxyServer({ + target: target + }); +}); + +// +// Get the proxy at the front of the array, put it at the end and return it +// If you want a fancier balancer, put your code here +// + +function nextProxy() { + var proxy = proxies.shift(); + proxies.push(proxy); + return proxy; +} + +// +// Get the 'next' proxy and send the http request +// + +var server = http.createServer(function (req, res) { + nextProxy().web(req, res); +}); + +// +// Get the 'next' proxy and send the upgrade request +// + +server.on('upgrade', function (req, socket, head) { + nextProxy().ws(req, socket, head); +}); + +server.listen(8001); + \ No newline at end of file diff --git a/examples/balancer/simple-balancer.js b/examples/balancer/simple-balancer.js new file mode 100644 index 0000000..3f53cc6 --- /dev/null +++ b/examples/balancer/simple-balancer.js @@ -0,0 +1,64 @@ +/* + simple-balancer.js: Example of a simple round robin balancer + + Copyright (c) Nodejitsu 2013 + + 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 http = require('http'), + httpProxy = require('../../lib/http-proxy'); +// +// A simple round-robin load balancing strategy. +// +// First, list the servers you want to use in your rotation. +// +var addresses = [ + { + host: 'ws1.0.0.0', + port: 80 + }, + { + host: 'ws2.0.0.0', + port: 80 + } +]; +var proxy = httpProxy.createServer(); + +http.createServer(function (req, res) { + // + // On each request, get the first location from the list... + // + var target = { target: addresses.shift() }; + + // + // ...then proxy to the server whose 'turn' it is... + // + console.log('balancing request to: ', target); + proxy.web(req, res, target); + + // + // ...and then the server you just used becomes the last item in the list. + // + addresses.push(target); +}).listen(8021); + +// Rinse; repeat; enjoy. \ No newline at end of file diff --git a/examples/helpers/store.js b/examples/helpers/store.js new file mode 100644 index 0000000..e286057 --- /dev/null +++ b/examples/helpers/store.js @@ -0,0 +1,64 @@ + +// +// 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. +// +var Store = module.exports = 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/http/basic-proxy.js b/examples/http/basic-proxy.js new file mode 100644 index 0000000..e9be0d7 --- /dev/null +++ b/examples/http/basic-proxy.js @@ -0,0 +1,60 @@ +/* + basic-proxy.js: Basic example of proxying over HTTP + + Copyright (c) Nodejitsu 2013 + + 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/http-proxy'); + +var welcome = [ + '# # ##### ##### ##### ##### ##### #### # # # #', + '# # # # # # # # # # # # # # # # ', + '###### # # # # ##### # # # # # # ## # ', + '# # # # ##### ##### ##### # # ## # ', + '# # # # # # # # # # # # # ', + '# # # # # # # # #### # # # ' +].join('\n'); + +util.puts(welcome.rainbow.bold); + +// +// Basic Http Proxy Server +// +httpProxy.createServer({ + target:'http://localhost:9003' +}).listen(8003); + +// +// 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(9003); + +util.puts('http proxy server'.blue + ' started '.green.bold + 'on port '.blue + '8003'.yellow); +util.puts('http server '.blue + 'started '.green.bold + 'on port '.blue + '9003 '.yellow); diff --git a/examples/http/concurrent-proxy.js b/examples/http/concurrent-proxy.js new file mode 100644 index 0000000..30aa53d --- /dev/null +++ b/examples/http/concurrent-proxy.js @@ -0,0 +1,68 @@ +/* + concurrent-proxy.js: check levelof concurrency through proxy. + + Copyright (c) Nodejitsu 2013 + + 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/http-proxy'); + +// +// Basic Http Proxy Server +// +httpProxy.createServer({ + target:'http://localhost:9004' +}).listen(8004); + +// +// 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(9004); + +util.puts('http proxy server'.blue + ' started '.green.bold + 'on port '.blue + '8004'.yellow); +util.puts('http server '.blue + 'started '.green.bold + 'on port '.blue + '9004 '.yellow); diff --git a/examples/http/custom-proxy-error.js b/examples/http/custom-proxy-error.js new file mode 100644 index 0000000..1c54b5a --- /dev/null +++ b/examples/http/custom-proxy-error.js @@ -0,0 +1,55 @@ +/* + custom-proxy-error.js: Example of using the custom `proxyError` event. + + Copyright (c) Nodejitsu 2013 + + 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/http-proxy'); + +// +// Http Proxy Server with bad target +// +var proxy = httpProxy.createServer({ + target:'http://localhost:9005' +}); + +// +// Tell the proxy to listen on port 8000 +// +proxy.listen(8005); + +// +// Listen for the `error` event on `proxy`. +proxy.on('error', function (err, req, res) { + res.writeHead(500, { + 'Content-Type': 'text/plain' + }); + + res.end('Something went wrong. And we are reporting a custom error message.'); +}); + + +util.puts('http proxy server '.blue + 'started '.green.bold + 'on port '.blue + '8005 '.yellow + 'with custom error message'.magenta.underline); \ No newline at end of file diff --git a/examples/http/error-handling.js b/examples/http/error-handling.js new file mode 100644 index 0000000..292fb61 --- /dev/null +++ b/examples/http/error-handling.js @@ -0,0 +1,63 @@ +/* + error-handling.js: Example of handle erros for HTTP and WebSockets + + Copyright (c) Nodejitsu 2013 + + 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/http-proxy'); + +// +// HTTP Proxy Server +// +var proxy = httpProxy.createProxyServer({target:'http://localhost:9000', ws:true}); + +// +// Example of error handling +// +function requestHandler(req, res) { + // Pass a callback to the web proxy method + // and catch the error there. + proxy.web(req, res, function (err) { + // Now you can get the err + // and handle it by your self + // if (err) throw err; + res.writeHead(502); + res.end("There was an error proxying your request"); + }); + + // In a websocket request case + req.on('upgrade', function (req, socket, head) { + proxy.ws(req, socket, head, function (err) { + // Now you can get the err + // and handle it by your self + // if (err) throw err; + socket.close(); + }) + }) +} + +http.createServer(requestHandler).listen(8000); +util.puts('http proxy server'.blue + ' started '.green.bold + 'on port '.blue + '8000'.yellow); diff --git a/examples/http/forward-and-target-proxy.js b/examples/http/forward-and-target-proxy.js new file mode 100644 index 0000000..c564bfb --- /dev/null +++ b/examples/http/forward-and-target-proxy.js @@ -0,0 +1,67 @@ +/* + forward-and-target-proxy.js: Example of proxying over HTTP with additional forward proxy + + Copyright (c) Nodejitsu 2013 + + 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/http-proxy'); + +// +// Setup proxy server with forwarding +// +httpProxy.createServer({ + target: { + port: 9006, + host: 'localhost' + }, + forward: { + port: 9007, + host: 'localhost' + } +}).listen(8006); + +// +// 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(9006); + +// +// Target Http Forwarding Server +// +http.createServer(function (req, res) { + util.puts('Receiving forward for: ' + req.url); + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.write('request successfully forwarded to: ' + req.url + '\n' + JSON.stringify(req.headers, true, 2)); + res.end(); +}).listen(9007); + +util.puts('http proxy server '.blue + 'started '.green.bold + 'on port '.blue + '8006 '.yellow + 'with forward proxy'.magenta.underline); +util.puts('http server '.blue + 'started '.green.bold + 'on port '.blue + '9006 '.yellow); +util.puts('http forward server '.blue + 'started '.green.bold + 'on port '.blue + '9007 '.yellow); \ No newline at end of file diff --git a/examples/http/forward-proxy.js b/examples/http/forward-proxy.js new file mode 100644 index 0000000..d94f484 --- /dev/null +++ b/examples/http/forward-proxy.js @@ -0,0 +1,53 @@ +/* + forward-proxy.js: Example of proxying over HTTP with additional forward proxy + + Copyright (c) Nodejitsu 2013 + + 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/http-proxy'); + +// +// Setup proxy server with forwarding +// +httpProxy.createServer({ + forward: { + port: 9019, + host: 'localhost' + } +}).listen(8019); + +// +// Target Http Forwarding Server +// +http.createServer(function (req, res) { + util.puts('Receiving forward for: ' + req.url); + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.write('request successfully forwarded to: ' + req.url + '\n' + JSON.stringify(req.headers, true, 2)); + res.end(); +}).listen(9019); + +util.puts('http proxy server '.blue + 'started '.green.bold + 'on port '.blue + '8019 '.yellow + 'with forward proxy'.magenta.underline); +util.puts('http forward server '.blue + 'started '.green.bold + 'on port '.blue + '9019 '.yellow); \ No newline at end of file diff --git a/examples/http/latent-proxy.js b/examples/http/latent-proxy.js new file mode 100644 index 0000000..01ec93c --- /dev/null +++ b/examples/http/latent-proxy.js @@ -0,0 +1,54 @@ +/* + latent-proxy.js: Example of proxying over HTTP with latency + + Copyright (c) Nodejitsu 2013 + + 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/http-proxy'); + +// +// Http Proxy Server with Latency +// +var proxy = httpProxy.createProxyServer(); +http.createServer(function (req, res) { + setTimeout(function () { + proxy.web(req, res, { + target: 'http://localhost:9008' + }); + }, 500); +}).listen(8008); + +// +// 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(9008); + +util.puts('http proxy server '.blue + 'started '.green.bold + 'on port '.blue + '8008 '.yellow + 'with latency'.magenta.underline); +util.puts('http server '.blue + 'started '.green.bold + 'on port '.blue + '9008 '.yellow); diff --git a/examples/http/proxy-http-to-https.js b/examples/http/proxy-http-to-https.js new file mode 100644 index 0000000..ba5c838 --- /dev/null +++ b/examples/http/proxy-http-to-https.js @@ -0,0 +1,46 @@ +/* + proxy-http-to-https.js: Basic example of proxying over HTTP to a target HTTPS server + + Copyright (c) Nodejitsu 2013 + + 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 https = require('https'), + http = require('http'), + util = require('util'), + path = require('path'), + fs = require('fs'), + colors = require('colors'), + httpProxy = require('../../lib/http-proxy'); + +// +// Create a HTTP Proxy server with a HTTPS target +// +httpProxy.createProxyServer({ + target: 'https://google.com', + agent : https.globalAgent, + headers: { + host: 'google.com' + } +}).listen(8011); + +util.puts('http proxy server'.blue + ' started '.green.bold + 'on port '.blue + '8011'.yellow); \ No newline at end of file diff --git a/examples/http/proxy-https-to-http.js b/examples/http/proxy-https-to-http.js new file mode 100644 index 0000000..d2a2d5c --- /dev/null +++ b/examples/http/proxy-https-to-http.js @@ -0,0 +1,60 @@ +/* + proxy-https-to-http.js: Basic example of proxying over HTTPS to a target HTTP server + + Copyright (c) Nodejitsu 2013 + + 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 https = require('https'), + http = require('http'), + util = require('util'), + path = require('path'), + fs = require('fs'), + colors = require('colors'), + httpProxy = require('../../lib/http-proxy'), + fixturesDir = path.join(__dirname, '..', '..', 'test', 'fixtures'); + +// +// Create the target HTTP server +// +http.createServer(function (req, res) { + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.write('hello http over https\n'); + res.end(); +}).listen(9009); + +// +// Create the HTTPS proxy server listening on port 8000 +// +httpProxy.createServer({ + target: { + host: 'localhost', + port: 9009 + }, + ssl: { + key: fs.readFileSync(path.join(fixturesDir, 'agent2-key.pem'), 'utf8'), + cert: fs.readFileSync(path.join(fixturesDir, 'agent2-cert.pem'), 'utf8') + } +}).listen(8009); + +util.puts('https proxy server'.blue + ' started '.green.bold + 'on port '.blue + '8009'.yellow); +util.puts('http server '.blue + 'started '.green.bold + 'on port '.blue + '9009 '.yellow); diff --git a/examples/http/proxy-https-to-https.js b/examples/http/proxy-https-to-https.js new file mode 100644 index 0000000..e543f98 --- /dev/null +++ b/examples/http/proxy-https-to-https.js @@ -0,0 +1,59 @@ +/* + proxy-https-to-https.js: Basic example of proxying over HTTPS to a target HTTPS server + + Copyright (c) Nodejitsu 2013 + + 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 https = require('https'), + http = require('http'), + util = require('util'), + fs = require('fs'), + path = require('path'), + colors = require('colors'), + httpProxy = require('../../lib/http-proxy'), + fixturesDir = path.join(__dirname, '..', '..', 'test', 'fixtures'), + httpsOpts = { + key: fs.readFileSync(path.join(fixturesDir, 'agent2-key.pem'), 'utf8'), + cert: fs.readFileSync(path.join(fixturesDir, 'agent2-cert.pem'), 'utf8') + }; + +// +// Create the target HTTPS server +// +https.createServer(httpsOpts, function (req, res) { + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.write('hello https\n'); + res.end(); +}).listen(9010); + +// +// Create the proxy server listening on port 443 +// +httpProxy.createServer({ + ssl: httpsOpts, + target: 'https://localhost:9010', + secure: false +}).listen(8010); + +util.puts('https proxy server'.blue + ' started '.green.bold + 'on port '.blue + '8010'.yellow); +util.puts('https server '.blue + 'started '.green.bold + 'on port '.blue + '9010 '.yellow); diff --git a/examples/http/standalone-proxy.js b/examples/http/standalone-proxy.js new file mode 100644 index 0000000..410d70b --- /dev/null +++ b/examples/http/standalone-proxy.js @@ -0,0 +1,54 @@ +/* + standalone-proxy.js: Example of proxying over HTTP with a standalone HTTP server. + + Copyright (c) Nodejitsu 2013 + + 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/http-proxy'); + +// +// Http Server with proxyRequest Handler and Latency +// +var proxy = new httpProxy.createProxyServer(); +http.createServer(function (req, res) { + setTimeout(function () { + proxy.web(req, res, { + target: 'http://localhost:9002' + }); + }, 200); +}).listen(8002); + +// +// 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(9002); + +util.puts('http server '.blue + 'started '.green.bold + 'on port '.blue + '8002 '.yellow + 'with proxy.web() handler'.cyan.underline + ' and latency'.magenta); +util.puts('http server '.blue + 'started '.green.bold + 'on port '.blue + '9001 '.yellow); diff --git a/examples/middleware/bodyDecoder-middleware.js b/examples/middleware/bodyDecoder-middleware.js new file mode 100644 index 0000000..555c3d1 --- /dev/null +++ b/examples/middleware/bodyDecoder-middleware.js @@ -0,0 +1,119 @@ +/* + bodyDecoder-middleware.js: Basic example of `connect.bodyParser()` middleware in node-http-proxy + + Copyright (c) Nodejitsu 2013 + + 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 http = require('http'), + connect = require('connect'), + request = require('request'), + colors = require('colors'), + util = require('util'), + Store = require('../helpers/store'), + httpProxy = require('../../lib/http-proxy'), + proxy = httpProxy.createProxyServer({}); + +http.createServer(new Store().handler()).listen(7531, function () { + util.puts('http '.blue + 'greetings '.green + 'server'.blue + ' started '.green.bold + 'on port '.blue + '7531'.yellow); +//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 () { + util.puts('http '.blue + 'insults '.red + 'server'.blue + ' started '.green.bold + 'on port '.blue + '2600'.yellow); + + //greetings -> 7531, insults-> 2600 + + // now, start a proxy server. + + //don't worry about incoming contont type + //bodyParser.parse[''] = JSON.parse + + connect.createServer( + //refactor the body parser and re-streamer into a separate package + connect.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) { + //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.web(req, res, { target: { host: 'localhost', port: port }}); + } + ).listen(1337, function () { + util.puts('http proxy server'.blue + ' started '.green.bold + 'on port '.blue + '1337'.yellow); + //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"] + + }) + }) +}); \ No newline at end of file diff --git a/examples/middleware/gzip-middleware.js b/examples/middleware/gzip-middleware.js new file mode 100644 index 0000000..756b68f --- /dev/null +++ b/examples/middleware/gzip-middleware.js @@ -0,0 +1,65 @@ +/* + gzip-middleware.js: Basic example of `connect-gzip` middleware in node-http-proxy + + Copyright (c) Nodejitsu 2013 + + 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'), + connect = require('connect') + httpProxy = require('../../lib/http-proxy'); + +// +// Basic Connect App +// +connect.createServer( + connect.compress({ + // Pass to connect.compress() the options + // that you need, just for show the example + // we use threshold to 1 + threshold: 1 + }), + function (req, res) { + proxy.web(req, res); + } +).listen(8012); + +// +// Basic Http Proxy Server +// +var proxy = httpProxy.createProxyServer({ + target: 'http://localhost:9012' +}); + +// +// 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(9012); + +util.puts('http proxy server'.blue + ' started '.green.bold + 'on port '.blue + '8012'.yellow); +util.puts('http server '.blue + 'started '.green.bold + 'on port '.blue + '9012 '.yellow); diff --git a/examples/middleware/modifyResponse-middleware.js b/examples/middleware/modifyResponse-middleware.js new file mode 100644 index 0000000..fdd7e65 --- /dev/null +++ b/examples/middleware/modifyResponse-middleware.js @@ -0,0 +1,67 @@ +/* + modifyBody-middleware.js: Example of middleware which modifies response + + Copyright (c) Nodejitsu 2013 + + 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'), + connect = require('connect'), + httpProxy = require('../../lib/http-proxy'); + +// +// Basic Connect App +// +connect.createServer( + function (req, res, next) { + var _write = res.write; + + res.write = function (data) { + _write.call(res, data.toString().replace("Ruby", "nodejitsu")); + } + next(); + }, + function (req, res) { + proxy.web(req, res); + } +).listen(8013); + +// +// Basic Http Proxy Server +// +var proxy = httpProxy.createProxyServer({ + target: 'http://localhost:9013' +}); + +// +// Target Http Server +// +http.createServer(function (req, res) { + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.end('Hello, I know Ruby\n'); +}).listen(9013); + +util.puts('http proxy server'.blue + ' started '.green.bold + 'on port '.blue + '8013'.yellow); +util.puts('http server '.blue + 'started '.green.bold + 'on port '.blue + '9013 '.yellow); + diff --git a/examples/package.json b/examples/package.json new file mode 100644 index 0000000..3daede1 --- /dev/null +++ b/examples/package.json @@ -0,0 +1,13 @@ +{ + "name": "http-proxy-examples", + "description": "packages required to run the examples", + "version": "0.0.0", + "dependencies": { + "colors": "~0.6.2", + "socket.io": "~0.9.16", + "socket.io-client": "~0.9.16", + "connect": "~2.11.0", + "request": "~2.27.0", + "connect-restreamer": "~1.0.0" + } +} diff --git a/examples/websocket/latent-websocket-proxy.js b/examples/websocket/latent-websocket-proxy.js new file mode 100644 index 0000000..64d3d7c --- /dev/null +++ b/examples/websocket/latent-websocket-proxy.js @@ -0,0 +1,91 @@ +/* + standalone-websocket-proxy.js: Example of proxying websockets over HTTP with a standalone HTTP server. + + Copyright (c) Nodejitsu 2013 + + 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'), + http = require('http'), + colors = require('colors'), + httpProxy = require('../../lib/http-proxy'); + +try { + var io = require('socket.io'), + client = require('socket.io-client'); +} +catch (ex) { + console.error('Socket.io is required for this example:'); + console.error('npm ' + 'install'.green); + process.exit(1); +} + +// +// Create the target HTTP server and setup +// socket.io on it. +// +var server = io.listen(9016); +server.sockets.on('connection', function (client) { + util.debug('Got websocket connection'); + + client.on('message', function (msg) { + util.debug('Got message from client: ' + msg); + }); + + client.send('from server'); +}); + +// +// Setup our server to proxy standard HTTP requests +// +var proxy = new httpProxy.createProxyServer({ + target: { + host: 'localhost', + port: 9016 + } +}); + +var proxyServer = http.createServer(function (req, res) { + proxy.web(req, res); +}); + +// +// Listen to the `upgrade` event and proxy the +// WebSocket requests as well. +// +proxyServer.on('upgrade', function (req, socket, head) { + setTimeout(function () { + proxy.ws(req, socket, head); + }, 1000); +}); + +proxyServer.listen(8016); + +// +// Setup the socket.io client against our proxy +// +var ws = client.connect('ws://localhost:8016'); + +ws.on('message', function (msg) { + util.debug('Got message: ' + msg); + ws.send('I am the client'); +}); diff --git a/examples/websocket/standalone-websocket-proxy.js b/examples/websocket/standalone-websocket-proxy.js new file mode 100644 index 0000000..81d0196 --- /dev/null +++ b/examples/websocket/standalone-websocket-proxy.js @@ -0,0 +1,88 @@ +/* + standalone-websocket-proxy.js: Example of proxying websockets over HTTP with a standalone HTTP server. + + Copyright (c) Nodejitsu 2013 + + 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'), + http = require('http'), + colors = require('colors'), + httpProxy = require('../../lib/http-proxy'); + +try { + var io = require('socket.io'), + client = require('socket.io-client'); +} +catch (ex) { + console.error('Socket.io is required for this example:'); + console.error('npm ' + 'install'.green); + process.exit(1); +} + +// +// Create the target HTTP server and setup +// socket.io on it. +// +var server = io.listen(9015); +server.sockets.on('connection', function (client) { + util.debug('Got websocket connection'); + + client.on('message', function (msg) { + util.debug('Got message from client: ' + msg); + }); + + client.send('from server'); +}); + +// +// Setup our server to proxy standard HTTP requests +// +var proxy = new httpProxy.createProxyServer({ + target: { + host: 'localhost', + port: 9015 + } +}); +var proxyServer = http.createServer(function (req, res) { + proxy.web(req, res); +}); + +// +// Listen to the `upgrade` event and proxy the +// WebSocket requests as well. +// +proxyServer.on('upgrade', function (req, socket, head) { + proxy.ws(req, socket, head); +}); + +proxyServer.listen(8015); + +// +// Setup the socket.io client against our proxy +// +var ws = client.connect('ws://localhost:8015'); + +ws.on('message', function (msg) { + util.debug('Got message: ' + msg); + ws.send('I am the client'); +}); diff --git a/examples/websocket/websocket-proxy.js b/examples/websocket/websocket-proxy.js new file mode 100644 index 0000000..33d78c6 --- /dev/null +++ b/examples/websocket/websocket-proxy.js @@ -0,0 +1,70 @@ +/* + web-socket-proxy.js: Example of proxying over HTTP and WebSockets. + + Copyright (c) Nodejitsu 2013 + + 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'), + http = require('http'), + colors = require('colors'), + httpProxy = require('../../lib/http-proxy'); + +try { + var io = require('socket.io'), + client = require('socket.io-client'); +} +catch (ex) { + console.error('Socket.io is required for this example:'); + console.error('npm ' + 'install'.green); + process.exit(1); +} + +// +// Create the target HTTP server and setup +// socket.io on it. +// +var server = io.listen(9014); +server.sockets.on('connection', function (client) { + util.debug('Got websocket connection'); + + client.on('message', function (msg) { + util.debug('Got message from client: ' + msg); + }); + + client.send('from server'); +}); + +// +// Create a proxy server with node-http-proxy +// +httpProxy.createServer({ target: 'ws://localhost:9014', ws: true }).listen(8014); + +// +// Setup the socket.io client against our proxy +// +var ws = client.connect('ws://localhost:8014'); + +ws.on('message', function (msg) { + util.debug('Got message: ' + msg); + ws.send('I am the client'); +}); diff --git a/index.js b/index.js new file mode 100644 index 0000000..e6fac85 --- /dev/null +++ b/index.js @@ -0,0 +1,13 @@ +/*! + * Caron dimonio, con occhi di bragia + * loro accennando, tutte le raccoglie; + * batte col remo qualunque s’adagia + * + * Charon the demon, with the eyes of glede, + * Beckoning to them, collects them all together, + * Beats with his oar whoever lags behind + * + * Dante - The Divine Comedy (Canto III) + */ + +module.exports = require('./lib/http-proxy'); \ No newline at end of file diff --git a/lib/http-proxy.js b/lib/http-proxy.js new file mode 100644 index 0000000..196dded --- /dev/null +++ b/lib/http-proxy.js @@ -0,0 +1,48 @@ +var http = require('http'), + https = require('https'), + url = require('url'), + httpProxy = require('./http-proxy/'); + +/** + * Export the the proxy "Server" as the main export + */ +module.exports = httpProxy.Server; + +/** + * Creates the proxy server. + * + * Examples: + * + * httpProxy.createProxyServer({ .. }, 8000) + * // => '{ web: [Function], ws: [Function] ... }' + * + * @param {Object} Options Config object passed to the proxy + * + * @return {Object} Proxy Proxy object with handlers for `ws` and `web` requests + * + * @api public + */ + +module.exports.createProxyServer = module.exports.createServer = function createProxyServer(options) { + /* + * `options` is needed and it must have the following layout: + * + * { + * target : + * forward: + * agent : + * ssl : + * ws : + * xfwd : + * secure : + * } + * + * NOTE: `options.ws` and `options.ssl` are optional. + * `options.target and `options.forward` cannot be + * both missing + * } + */ + + return new httpProxy.Server(options); +}; + diff --git a/lib/http-proxy/common.js b/lib/http-proxy/common.js new file mode 100644 index 0000000..7897578 --- /dev/null +++ b/lib/http-proxy/common.js @@ -0,0 +1,82 @@ +var common = exports, + url = require('url'), + extend = require('util')._extend; + +/** + * Copies the right headers from `options` and `req` to + * `outgoing` which is then used to fire the proxied + * request. + * + * Examples: + * + * common.setupOutgoing(outgoing, options, req) + * // => { host: ..., hostname: ...} + * + * @param {Object} Outgoing Base object to be filled with required properties + * @param {Object} Options Config object passed to the proxy + * @param {ClientRequest} Req Request Object + * @param {String} Forward String to select forward or target + *  + * @return {Object} Outgoing Object with all required properties set + * + * @api private + */ + +common.setupOutgoing = function(outgoing, options, req, forward) { + outgoing.port = options[forward || 'target'].port || + (~['https:', 'wss:'].indexOf(options[forward || 'target'].protocol) ? 443 : 80); + + ['host', 'hostname', 'socketPath'].forEach( + function(e) { outgoing[e] = options[forward || 'target'][e]; } + ); + + ['method', 'headers'].forEach( + function(e) { outgoing[e] = req[e]; } + ); + + if (options.headers){ + extend(outgoing.headers, options.headers); + } + + if (options[forward || 'target'].protocol == 'https:') { + outgoing.rejectUnauthorized = (typeof options.secure === "undefined") ? true : options.secure; + } + + + outgoing.agent = options.agent || false; + outgoing.path = url.parse(req.url).path; + return outgoing; +}; + +/** + * Set the proper configuration for sockets, + * set no delay and set keep alive, also set + * the timeout to 0. + * + * Examples: + * + * common.setupSocket(socket) + * // => Socket + * + * @param {Socket} Socket instance to setup + *  + * @return {Socket} Return the configured socket. + * + * @api private + */ + +common.setupSocket = function(socket) { + socket.setTimeout(0); + socket.setNoDelay(true); + + socket.setKeepAlive(true, 0); + + return socket; +}; + +common.getPort = function(req) { + var res = req.headers.host.match(/:(\d+)/); + return res ? + res[1] : + req.connection.pair ? '443' : '80' ; +} diff --git a/lib/http-proxy/index.js b/lib/http-proxy/index.js new file mode 100644 index 0000000..bd8b8a9 --- /dev/null +++ b/lib/http-proxy/index.js @@ -0,0 +1,149 @@ +var httpProxy = exports, + extend = require('util')._extend, + parse_url = require('url').parse, + EE3 = require('eventemitter3').EventEmitter, + http = require('http'), + https = require('https'), + web = require('./passes/web-incoming'), + ws = require('./passes/ws-incoming'); + +httpProxy.Server = ProxyServer; + +/** + * Returns a function that creates the loader for + * either `ws` or `web`'s passes. + * + * Examples: + * + * httpProxy.createRightProxy('ws') + * // => [Function] + * + * @param {String} Type Either 'ws' or 'web' + *  + * @return {Function} Loader Function that when called returns an iterator for the right passes + * + * @api private + */ + +function createRightProxy(type) { + return function(options) { + return function(req, res /*, [head], [opts] */) { + var passes = (type === 'ws') ? this.wsPasses : this.webPasses, + args = [].slice.call(arguments), + cntr = args.length - 1, + head, cbl; + + /* optional args parse begin */ + if(typeof args[cntr] === 'function') { + cbl = args[cntr]; + + cntr--; + } + + if( + !(args[cntr] instanceof Buffer) && + args[cntr] !== res + ) { + //Copy global options + options = extend({}, options); + //Overwrite with request options + extend(options, args[cntr]); + + cntr--; + } + + if(args[cntr] instanceof Buffer) { + head = args[cntr]; + } + + /* optional args parse end */ + + ['target', 'forward'].forEach(function(e) { + if (typeof options[e] === 'string') + options[e] = parse_url(options[e]); + }); + + + for(var i=0; i < passes.length; i++) { + /** + * Call of passes functions + * pass(req, res, options, head) + * + * In WebSockets case the `res` variable + * refer to the connection socket + * pass(req, socket, options, head) + */ + if(passes[i](req, res, options, head, cbl ? false : this, cbl)) { // passes can return a truthy value to halt the loop + break; + } + } + }; + }; +} + + +function ProxyServer(options) { + EE3.call(this); + + this.web = this.proxyRequest = createRightProxy('web')(options); + this.ws = this.proxyWebsocketRequest = createRightProxy('ws')(options); + this.options = options; + + this.webPasses = Object.keys(web).map(function(pass) { + return web[pass]; + }); + + this.wsPasses = Object.keys(ws).map(function(pass) { + return ws[pass]; + }); +} + +require('util').inherits(ProxyServer, EE3); + +ProxyServer.prototype.listen = function(port) { + var self = this, + closure = function(req, res) { self.web(req, res); }; + + this._server = this.options.ssl ? + https.createServer(this.options.ssl, closure) : + http.createServer(closure); + + if(this.options.ws) { + this._server.on('upgrade', function(req, socket, head) { self.ws(req, socket, head); }); + } + + this._server.listen(port); + + return this; +}; + +ProxyServer.prototype.before = function(type, passName, callback) { + if (type !== 'ws' || type !== 'web') { + throw new Error('type must be `web` or `ws`'); + } + var passes = (type === 'ws') ? this.wsPasses : this.webPasses, + i = false; + + passes.forEach(function(v, idx) { + if(v.name === passName) i = idx; + }) + + if(!i) throw new Error('No such pass'); + + passes.splice(i, 0, callback); +}; +ProxyServer.prototype.after = function(type, passName, callback) { + if (type !== 'ws' || type !== 'web') { + throw new Error('type must be `web` or `ws`'); + } + var passes = (type === 'ws') ? this.wsPasses : this.webPasses, + i = false; + + passes.forEach(function(v, idx) { + if(v.name === passName) i = idx; + }) + + if(!i) throw new Error('No such pass'); + + passes.splice(i++, 0, callback); +}; diff --git a/lib/http-proxy/passes/web-incoming.js b/lib/http-proxy/passes/web-incoming.js new file mode 100644 index 0000000..2b14c87 --- /dev/null +++ b/lib/http-proxy/passes/web-incoming.js @@ -0,0 +1,134 @@ +var http = require('http'), + https = require('https'), + web_o = require('./web-outgoing'), + common = require('../common'), + passes = exports; + +web_o = Object.keys(web_o).map(function(pass) { + return web_o[pass]; +}); + +/*! + * Array of passes. + * + * A `pass` is just a function that is executed on `req, res, options` + * so that you can easily add new checks while still keeping the base + * flexible. + */ + +[ // <-- + + /** + * Sets `content-length` to '0' if request is of DELETE type. + * + * @param {ClientRequest} Req Request object + * @param {IncomingMessage} Res Response object + * @param {Object} Options Config object passed to the proxy + * + * @api private + */ + + function deleteLength(req, res, options) { + if(req.method === 'DELETE' && !req.headers['content-length']) { + req.headers['content-length'] = '0'; + } + }, + + /** + * Sets timeout in request socket if it was specified in options. + * + * @param {ClientRequest} Req Request object + * @param {IncomingMessage} Res Response object + * @param {Object} Options Config object passed to the proxy + * + * @api private + */ + + function timeout(req, res, options) { + if(options.timeout) { + req.socket.setTimeout(options.timeout); + } + }, + + /** + * Sets `x-forwarded-*` headers if specified in config. + * + * @param {ClientRequest} Req Request object + * @param {IncomingMessage} Res Response object + * @param {Object} Options Config object passed to the proxy + * + * @api private + */ + + function XHeaders(req, res, options) { + if(!options.xfwd) return; + + var values = { + for : req.connection.remoteAddress || req.socket.remoteAddress, + port : common.getPort(req), + proto: req.isSpdy ? 'https' : (req.connection.pair ? 'https' : 'http') + }; + + ['for', 'port', 'proto'].forEach(function(header) { + req.headers['x-forwarded-' + header] = + (req.headers['x-forwarded-' + header] || '') + + (req.headers['x-forwarded-' + header] ? ',' : '') + + values[header]; + }); + }, + + /** + * Does the actual proxying. If `forward` is enabled fires up + * a ForwardStream, same happens for ProxyStream. The request + * just dies otherwise. + * + * @param {ClientRequest} Req Request object + * @param {IncomingMessage} Res Response object + * @param {Object} Options Config object passed to the proxy + * + * @api private + */ + + function stream(req, res, options, _, server, clb) { + if(options.forward) { + // If forward enable, so just pipe the request + var forwardReq = (options.forward.protocol === 'https:' ? https : http).request( + common.setupOutgoing(options.ssl || {}, options, req, 'forward') + ); + (options.buffer || req).pipe(forwardReq); + if(!options.target) { return res.end(); } + } + + // Request initalization + var proxyReq = (options.target.protocol === 'https:' ? https : http).request( + common.setupOutgoing(options.ssl || {}, options, req) + ); + + // Error Handler + proxyReq.on('error', function(err){ + if(options.buffer) { options.buffer.destroy(); } + if (clb) { + clb(err); + } else { + server.emit('error', err, req, res); + } + }); + + (options.buffer || req).pipe(proxyReq); + + proxyReq.on('response', function(proxyRes) { + server.emit('proxyRes', proxyRes); + for(var i=0; i < web_o.length; i++) { + if(web_o[i](req, res, proxyRes)) { break; } + } + + proxyRes.pipe(res); + }); + + //proxyReq.end(); + } + +] // <-- + .forEach(function(func) { + passes[func.name] = func; + }); diff --git a/lib/http-proxy/passes/web-outgoing.js b/lib/http-proxy/passes/web-outgoing.js new file mode 100644 index 0000000..9281c4a --- /dev/null +++ b/lib/http-proxy/passes/web-outgoing.js @@ -0,0 +1,78 @@ +var passes = exports; + +/*! + * Array of passes. + * + * A `pass` is just a function that is executed on `req, res, options` + * so that you can easily add new checks while still keeping the base + * flexible. + */ + +[ // <-- + + /** + * If is a HTTP 1.0 request, remove chunk headers + * + * @param {ClientRequest} Req Request object + * @param {IncomingMessage} Res Response object + * @param {proxyResponse} Res Response object from the proxy request + * + * @api private + */ + function removeChunked(req, res, proxyRes) { + if(req.httpVersion === '1.0') { + delete proxyRes.headers['transfer-encoding']; + } + }, + + /** + * If is a HTTP 1.0 request, set the correct connection header + * or if connection header not present, then use `keep-alive` + * + * @param {ClientRequest} Req Request object + * @param {IncomingMessage} Res Response object + * @param {proxyResponse} Res Response object from the proxy request + * + * @api private + */ + function setConnection(req, res, proxyRes) { + if (req.httpVersion === '1.0') { + proxyRes.headers.connection = req.headers.connection || 'close'; + } else if (!proxyRes.headers.connection) { + proxyRes.headers.connection = req.headers.connection || 'keep-alive'; + } + }, + + /** + * Copy headers from proxyResponse to response + * set each header in response object. + * + * @param {ClientRequest} Req Request object + * @param {IncomingMessage} Res Response object + * @param {proxyResponse} Res Response object from the proxy request + * + * @api private + */ + function writeHeaders(req, res, proxyRes) { + Object.keys(proxyRes.headers).forEach(function(key) { + res.setHeader(key, proxyRes.headers[key]); + }); + }, + + /** + * Set the statusCode from the proxyResponse + * + * @param {ClientRequest} Req Request object + * @param {IncomingMessage} Res Response object + * @param {proxyResponse} Res Response object from the proxy request + * + * @api private + */ + function writeStatusCode(req, res, proxyRes) { + res.writeHead(proxyRes.statusCode); + } + +] // <-- + .forEach(function(func) { + passes[func.name] = func; + }); diff --git a/lib/http-proxy/passes/ws-incoming.js b/lib/http-proxy/passes/ws-incoming.js new file mode 100644 index 0000000..720ac27 --- /dev/null +++ b/lib/http-proxy/passes/ws-incoming.js @@ -0,0 +1,121 @@ +var http = require('http'), + https = require('https'), + common = require('../common'), + passes = exports; + +/*! + * Array of passes. + * + * A `pass` is just a function that is executed on `req, socket, options` + * so that you can easily add new checks while still keeping the base + * flexible. + */ + +/* + * Websockets Passes + * + */ + +var passes = exports; + +[ + /** + * WebSocket requests must have the `GET` method and + * the `upgrade:websocket` header + * + * @param {ClientRequest} Req Request object + * @param {Socket} Websocket + * + * @api private + */ + + function checkMethodAndHeader (req, socket) { + if (req.method !== 'GET' || !req.headers.upgrade) { + socket.destroy(); + return true; + } + + if (req.headers.upgrade.toLowerCase() !== 'websocket') { + socket.destroy(); + return true; + } + }, + + /** + * Sets `x-forwarded-*` headers if specified in config. + * + * @param {ClientRequest} Req Request object + * @param {Socket} Websocket + * @param {Object} Options Config object passed to the proxy + * + * @api private + */ + + function XHeaders(req, socket, options) { + if(!options.xfwd) return; + + var values = { + for : req.connection.remoteAddress || req.socket.remoteAddress, + port : common.getPort(req), + proto: req.connection.pair ? 'wss' : 'ws' + }; + + ['for', 'port', 'proto'].forEach(function(header) { + req.headers['x-forwarded-' + header] = + (req.headers['x-forwarded-' + header] || '') + + (req.headers['x-forwarded-' + header] ? ',' : '') + + values[header]; + }); + }, + + /** + * Does the actual proxying. Make the request and upgrade it + * send the Switching Protocols request and pipe the sockets. + * + * @param {ClientRequest} Req Request object + * @param {Socket} Websocket + * @param {Object} Options Config object passed to the proxy + * + * @api private + */ + function stream(req, socket, options, server, head, clb) { + common.setupSocket(socket); + + if (head && head.length) socket.unshift(head); + + + var proxyReq = (~['https:', 'wss:'].indexOf(options.target.protocol) ? https : http).request( + common.setupOutgoing(options.ssl || {}, options, req) + ); + // Error Handler + proxyReq.on('error', onError); + + proxyReq.on('upgrade', function(proxyRes, proxySocket, proxyHead) { + proxySocket.on('error', onError); + + common.setupSocket(proxySocket); + + if (proxyHead && proxyHead.length) proxySocket.unshift(proxyHead); + + socket.write('HTTP/1.1 101 Switching Protocols\r\n'); + socket.write(Object.keys(proxyRes.headers).map(function(i) { + return i + ": " + proxyRes.headers[i]; + }).join('\r\n') + '\r\n\r\n'); + proxySocket.pipe(socket).pipe(proxySocket); + }); + + return proxyReq.end(); // XXX: CHECK IF THIS IS THIS CORRECT + + function onError(err) { + if (clb) { + clb(err); + } else { + server.emit('error', err, req, socket); + } + } + } + +] // <-- + .forEach(function(func) { + passes[func.name] = func; + }); diff --git a/package.json b/package.json new file mode 100644 index 0000000..5c7c3a1 --- /dev/null +++ b/package.json @@ -0,0 +1,40 @@ +{ + "name" : "http-proxy", + "version" : "1.0.0", + "description" : "HTTP proxying for the masses", + "author": "Nodejitsu Inc. ", + "maintainers" : [ + "yawnt ", + "indexzero " + ], + + "main" : "index.js", + + "dependencies" : { + "eventemitter3" : "*" + }, + "devDependencies": { + "mocha" : "*", + "expect.js" : "*", + "dox" : "*", + "coveralls" : "*", + "mocha-lcov-reporter": "*", + "blanket" : "*", + "ws" : "*", + "socket.io" : "*", + "socket.io-client" : "*", + "async" : "*" + }, + "scripts" : { + "coveralls" : "mocha --require blanket --reporter mocha-lcov-reporter | ./node_modules/coveralls/bin/coveralls.js", + "blanket" : { "pattern": "lib/http-proxy" }, + "test" : "mocha -R landing test/*-test.js", + "test-cov" : "mocha --require blanket -R html-cov > cov/coverage.html" + }, + + "engines" : { + "node" : ">=0.10.0" + }, + + "license" : "MIT" +} diff --git a/test/examples-test.js b/test/examples-test.js new file mode 100644 index 0000000..8464e3f --- /dev/null +++ b/test/examples-test.js @@ -0,0 +1,71 @@ +/* + examples-test.js: Test to run all the examples + + Copyright (c) Nodejitsu 2013 + +*/ +var path = require('path'), + fs = require('fs'), + spawn = require('child_process').spawn, + expect = require('expect.js'), + async = require('async'); + +var rootDir = path.join(__dirname, '..'), + examplesDir = path.join(rootDir, 'examples'); + +describe.skip('http-proxy examples', function () { + describe('Before testing examples', function () { + // Set a timeout to avoid this error + this.timeout(30 * 1000); + it('should have installed dependencies', function (done) { + async.waterfall([ + // + // 1. Read files in examples dir + // + async.apply(fs.readdir, examplesDir), + // + // 2. If node_modules exists, continue. Otherwise + // exec `npm` to install them + // + function checkNodeModules(files, next) { + if (files.indexOf('node_modules') !== -1) { + return next(); + } + + console.log('Warning: installing dependencies, this operation could take a while'); + + var child = spawn('npm', ['install', '-f'], { + cwd: examplesDir + }); + + child.on('exit', function (code) { + return code + ? next(new Error('npm install exited with non-zero exit code')) + : next(); + }); + }, + // + // 3. Read files in examples dir again to ensure the install + // worked as expected. + // + async.apply(fs.readdir, examplesDir), + ], done); + }) + }); + + describe('Requiring all the examples', function () { + it('should have no errors', function (done) { + async.each(['balancer', 'http', 'middleware', 'websocket'], function (dir, cb) { + var name = 'examples/' + dir, + files = fs.readdirSync(path.join(rootDir, 'examples', dir)); + + async.each(files, function (file, callback) { + var example; + expect(function () { example = require(path.join(examplesDir, dir, file)); }).to.not.throwException(); + expect(example).to.be.an('object'); + callback(); + }, cb); + }, done); + }) + }) +}) \ No newline at end of file diff --git a/test/fixtures/agent2-cert.pem b/test/fixtures/agent2-cert.pem new file mode 100644 index 0000000..8e4354d --- /dev/null +++ b/test/fixtures/agent2-cert.pem @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIIB7DCCAZYCCQC7gs0MDNn6MTANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJV +UzELMAkGA1UECBMCQ0ExCzAJBgNVBAcTAlNGMQ8wDQYDVQQKEwZKb3llbnQxEDAO +BgNVBAsTB05vZGUuanMxDzANBgNVBAMTBmFnZW50MjEgMB4GCSqGSIb3DQEJARYR +cnlAdGlueWNsb3Vkcy5vcmcwHhcNMTEwMzE0MTgyOTEyWhcNMzgwNzI5MTgyOTEy +WjB9MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExCzAJBgNVBAcTAlNGMQ8wDQYD +VQQKEwZKb3llbnQxEDAOBgNVBAsTB05vZGUuanMxDzANBgNVBAMTBmFnZW50MjEg +MB4GCSqGSIb3DQEJARYRcnlAdGlueWNsb3Vkcy5vcmcwXDANBgkqhkiG9w0BAQEF +AANLADBIAkEAyXb8FrRdKbhrKLgLSsn61i1C7w7fVVVd7OQsmV/7p9WB2lWFiDlC +WKGU9SiIz/A6wNZDUAuc2E+VwtpCT561AQIDAQABMA0GCSqGSIb3DQEBBQUAA0EA +C8HzpuNhFLCI3A5KkBS5zHAQax6TFUOhbpBCR0aTDbJ6F1liDTK1lmU/BjvPoj+9 +1LHwrmh29rK8kBPEjmymCQ== +-----END CERTIFICATE----- diff --git a/test/fixtures/agent2-csr.pem b/test/fixtures/agent2-csr.pem new file mode 100644 index 0000000..a670c4c --- /dev/null +++ b/test/fixtures/agent2-csr.pem @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBXTCCAQcCAQAwfTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMQswCQYDVQQH +EwJTRjEPMA0GA1UEChMGSm95ZW50MRAwDgYDVQQLEwdOb2RlLmpzMQ8wDQYDVQQD +EwZhZ2VudDIxIDAeBgkqhkiG9w0BCQEWEXJ5QHRpbnljbG91ZHMub3JnMFwwDQYJ +KoZIhvcNAQEBBQADSwAwSAJBAMl2/Ba0XSm4ayi4C0rJ+tYtQu8O31VVXezkLJlf ++6fVgdpVhYg5QlihlPUoiM/wOsDWQ1ALnNhPlcLaQk+etQECAwEAAaAlMCMGCSqG +SIb3DQEJBzEWExRBIGNoYWxsZW5nZSBwYXNzd29yZDANBgkqhkiG9w0BAQUFAANB +AJnll2pt5l0pzskQSpjjLVTlFDFmJr/AZ3UK8v0WxBjYjCe5Jx4YehkChpxIyDUm +U3J9q9MDUf0+Y2+EGkssFfk= +-----END CERTIFICATE REQUEST----- diff --git a/test/fixtures/agent2-key.pem b/test/fixtures/agent2-key.pem new file mode 100644 index 0000000..522903c --- /dev/null +++ b/test/fixtures/agent2-key.pem @@ -0,0 +1,9 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIBOgIBAAJBAMl2/Ba0XSm4ayi4C0rJ+tYtQu8O31VVXezkLJlf+6fVgdpVhYg5 +QlihlPUoiM/wOsDWQ1ALnNhPlcLaQk+etQECAwEAAQJBAMT6Bf34+UHKY1ObpsbH +9u2jsVblFq1rWvs8GPMY6oertzvwm3DpuSUp7PTgOB1nLTLYtCERbQ4ovtN8tn3p +OHUCIQDzIEGsoCr5vlxXvy2zJwu+fxYuhTZWMVuo1397L0VyhwIhANQh+yzqUgaf +WRtSB4T2W7ADtJI35ET61jKBty3CqJY3AiAIwju7dVW3A5WeD6Qc1SZGKZvp9yCb +AFI2BfVwwaY11wIgXF3PeGcvACMyMWsuSv7aPXHfliswAbkWuzcwA4TW01ECIGWa +cgsDvVFxmfM5NPSuT/UDTa6R5BFISB5ea0N0AR3I +-----END RSA PRIVATE KEY----- diff --git a/test/fixtures/agent2.cnf b/test/fixtures/agent2.cnf new file mode 100644 index 0000000..0a9f2c7 --- /dev/null +++ b/test/fixtures/agent2.cnf @@ -0,0 +1,19 @@ +[ req ] +default_bits = 1024 +days = 999 +distinguished_name = req_distinguished_name +attributes = req_attributes +prompt = no + +[ req_distinguished_name ] +C = US +ST = CA +L = SF +O = Joyent +OU = Node.js +CN = agent2 +emailAddress = ry@tinyclouds.org + +[ req_attributes ] +challengePassword = A challenge password + diff --git a/test/lib-http-proxy-common-test.js b/test/lib-http-proxy-common-test.js new file mode 100644 index 0000000..7398a0e --- /dev/null +++ b/test/lib-http-proxy-common-test.js @@ -0,0 +1,103 @@ +var common = require('../lib/http-proxy/common'), + expect = require('expect.js'); + +describe('lib/http-proxy/common.js', function () { + describe('#setupOutgoing', function () { + it('should setup the correct headers', function () { + var outgoing = {}; + common.setupOutgoing(outgoing, + { + agent : '?', + target: { + host : 'hey', + hostname : 'how', + socketPath: 'are', + port : 'you', + }, + headers: {'fizz': 'bang', 'overwritten':true}, + }, + { + method : 'i', + url : 'am', + headers : {'pro':'xy','overwritten':false} + }); + + expect(outgoing.host).to.eql('hey'); + expect(outgoing.hostname).to.eql('how'); + expect(outgoing.socketPath).to.eql('are'); + expect(outgoing.port).to.eql('you'); + expect(outgoing.agent).to.eql('?'); + + expect(outgoing.method).to.eql('i'); + expect(outgoing.path).to.eql('am'); + + expect(outgoing.headers.pro).to.eql('xy'); + expect(outgoing.headers.fizz).to.eql('bang'); + expect(outgoing.headers.overwritten).to.eql(true); + }); + + it('should set the agent to false if none is given', function () { + var outgoing = {}; + common.setupOutgoing(outgoing, {target: + 'http://localhost' + }, { url: '/' }); + expect(outgoing.agent).to.eql(false); + }); + + it('set the port according to the protocol', function () { + var outgoing = {}; + common.setupOutgoing(outgoing, + { + agent : '?', + target: { + host : 'how', + hostname : 'are', + socketPath: 'you', + protocol: 'https:' + } + }, + { + method : 'i', + url : 'am', + headers : 'proxy' + }); + + expect(outgoing.host).to.eql('how'); + expect(outgoing.hostname).to.eql('are'); + expect(outgoing.socketPath).to.eql('you'); + expect(outgoing.agent).to.eql('?'); + + expect(outgoing.method).to.eql('i'); + expect(outgoing.path).to.eql('am'); + expect(outgoing.headers).to.eql('proxy') + + expect(outgoing.port).to.eql(443); + }); + }); + + describe('#setupSocket', function () { + it('should setup a socket', function () { + var socketConfig = { + timeout: null, + nodelay: false, + keepalive: false + }, + stubSocket = { + setTimeout: function (num) { + socketConfig.timeout = num; + }, + setNoDelay: function (bol) { + socketConfig.nodelay = bol; + }, + setKeepAlive: function (bol) { + socketConfig.keepalive = bol; + } + } + returnValue = common.setupSocket(stubSocket); + + expect(socketConfig.timeout).to.eql(0); + expect(socketConfig.nodelay).to.eql(true); + expect(socketConfig.keepalive).to.eql(true); + }); + }); +}); \ No newline at end of file diff --git a/test/lib-http-proxy-passes-web-incoming-test.js b/test/lib-http-proxy-passes-web-incoming-test.js new file mode 100644 index 0000000..b25ad6e --- /dev/null +++ b/test/lib-http-proxy-passes-web-incoming-test.js @@ -0,0 +1,130 @@ +var webPasses = require('../lib/http-proxy/passes/web-incoming'), + httpProxy = require('../lib/http-proxy'), + expect = require('expect.js'), + http = require('http'); + +describe('lib/http-proxy/passes/web.js', function() { + describe('#deleteLength', function() { + it('should change `content-length`', function() { + var stubRequest = { + method: 'DELETE', + headers: {} + }; + webPasses.deleteLength(stubRequest, {}, {}); + expect(stubRequest.headers['content-length']).to.eql('0'); + }) + }); + + describe('#timeout', function() { + it('should set timeout on the socket', function() { + var done = false, stubRequest = { + socket: { + setTimeout: function(value) { done = value; } + } + } + + webPasses.timeout(stubRequest, {}, { timeout: 5000}); + expect(done).to.eql(5000); + }); + }); + + describe('#XHeaders', function () { + var stubRequest = { + connection: { + remoteAddress: '192.168.1.2', + remotePort: '8080' + }, + headers: { + host: '192.168.1.2:8080' + } + } + + it('set the correct x-forwarded-* headers', function () { + webPasses.XHeaders(stubRequest, {}, { xfwd: true }); + expect(stubRequest.headers['x-forwarded-for']).to.be('192.168.1.2'); + expect(stubRequest.headers['x-forwarded-port']).to.be('8080'); + expect(stubRequest.headers['x-forwarded-proto']).to.be('http'); + }); + }); +}); + +describe('#createProxyServer.web() using own http server', function () { + it('should proxy the request using the web proxy handler', function (done) { + var proxy = httpProxy.createProxyServer({ + target: 'http://127.0.0.1:8080' + }); + + function requestHandler(req, res) { + proxy.web(req, res); + } + + var proxyServer = http.createServer(requestHandler); + + var source = http.createServer(function(req, res) { + source.close(); + proxyServer.close(); + expect(req.method).to.eql('GET'); + expect(req.headers.host.split(':')[1]).to.eql('8081'); + done(); + }); + + proxyServer.listen('8081'); + source.listen('8080'); + + http.request('http://127.0.0.1:8081', function() {}).end(); + }); + + it('should proxy the request and handle error via callback', function(done) { + var proxy = httpProxy.createProxyServer({ + target: 'http://127.0.0.1:8080' + }); + + var proxyServer = http.createServer(requestHandler); + + function requestHandler(req, res) { + proxy.web(req, res, function (err) { + proxyServer.close(); + expect(err).to.be.an(Error); + expect(err.code).to.be('ECONNREFUSED'); + done(); + }); + } + + proxyServer.listen('8082'); + + http.request({ + hostname: '127.0.0.1', + port: '8082', + method: 'GET', + }, function() {}).end(); + }); + + it('should proxy the request and handle error via event listener', function(done) { + var proxy = httpProxy.createProxyServer({ + target: 'http://127.0.0.1:8080' + }); + + var proxyServer = http.createServer(requestHandler); + + function requestHandler(req, res) { + proxy.once('error', function (err, errReq, errRes) { + proxyServer.close(); + expect(err).to.be.an(Error); + expect(errReq).to.be.equal(req); + expect(errRes).to.be.equal(res); + expect(err.code).to.be('ECONNREFUSED'); + done(); + }); + + proxy.web(req, res); + } + + proxyServer.listen('8083'); + + http.request({ + hostname: '127.0.0.1', + port: '8083', + method: 'GET', + }, function() {}).end(); + }); +}); \ No newline at end of file diff --git a/test/lib-http-proxy-passes-web-outgoing-test.js b/test/lib-http-proxy-passes-web-outgoing-test.js new file mode 100644 index 0000000..0ae0bda --- /dev/null +++ b/test/lib-http-proxy-passes-web-outgoing-test.js @@ -0,0 +1,104 @@ +var httpProxy = require('../lib/http-proxy/passes/web-outgoing'), + expect = require('expect.js'); + +describe('lib/http-proxy/passes/web-outgoing.js', function () { + describe('#setConnection', function () { + it('set the right connection with 1.0 - `close`', function() { + var proxyRes = { headers: {} }; + httpProxy.setConnection({ + httpVersion: '1.0', + headers: { + connection: null + } + }, {}, proxyRes); + + expect(proxyRes.headers.connection).to.eql('close'); + }); + + it('set the right connection with 1.0 - req.connection', function() { + var proxyRes = { headers: {} }; + httpProxy.setConnection({ + httpVersion: '1.0', + headers: { + connection: 'hey' + } + }, {}, proxyRes); + + expect(proxyRes.headers.connection).to.eql('hey'); + }); + + it('set the right connection - req.connection', function() { + var proxyRes = { headers: {} }; + httpProxy.setConnection({ + httpVersion: null, + headers: { + connection: 'hola' + } + }, {}, proxyRes); + + expect(proxyRes.headers.connection).to.eql('hola'); + }); + + it('set the right connection - `keep-alive`', function() { + var proxyRes = { headers: {} }; + httpProxy.setConnection({ + httpVersion: null, + headers: { + connection: null + } + }, {}, proxyRes); + + expect(proxyRes.headers.connection).to.eql('keep-alive'); + }); + + }); + + describe('#writeStatusCode', function () { + it('should write status code', function() { + var res = { + writeHead: function(n) { + expect(n).to.eql(200); + } + } + + httpProxy.writeStatusCode({}, res, { statusCode: 200 }); + }); + }); + + describe('#writeHeaders', function() { + var proxyRes = { + headers: { + hey: 'hello', + how: 'are you?' + } + }; + + var res = { + setHeader: function(k, v) { + this.headers[k] = v; + }, + headers: {} + }; + + httpProxy.writeHeaders({}, res, proxyRes); + + expect(res.headers.hey).to.eql('hello'); + expect(res.headers.how).to.eql('are you?'); + }); + + + describe('#removeChunked', function() { + var proxyRes = { + headers: { + 'transfer-encoding': 'hello' + } + }; + + + httpProxy.removeChunked({ httpVersion: '1.0' }, {}, proxyRes); + + expect(proxyRes.headers['transfer-encoding']).to.eql(undefined); + }); + +}); + diff --git a/test/lib-http-proxy-passes-ws-incoming-test.js b/test/lib-http-proxy-passes-ws-incoming-test.js new file mode 100644 index 0000000..bfed888 --- /dev/null +++ b/test/lib-http-proxy-passes-ws-incoming-test.js @@ -0,0 +1,120 @@ +var httpProxy = require('../lib/http-proxy/passes/ws-incoming'), + expect = require('expect.js'); + +describe('lib/http-proxy/passes/ws-incoming.js', function () { + describe('#checkMethodAndHeader', function () { + it('should drop non-GET connections', function () { + var destroyCalled = false, + stubRequest = { + method: 'DELETE', + headers: {} + }, + stubSocket = { + destroy: function () { + // Simulate Socket.destroy() method when call + destroyCalled = true; + } + } + returnValue = httpProxy.checkMethodAndHeader(stubRequest, stubSocket); + expect(returnValue).to.be(true); + expect(destroyCalled).to.be(true); + }) + + it('should drop connections when no upgrade header', function () { + var destroyCalled = false, + stubRequest = { + method: 'GET', + headers: {} + }, + stubSocket = { + destroy: function () { + // Simulate Socket.destroy() method when call + destroyCalled = true; + } + } + returnValue = httpProxy.checkMethodAndHeader(stubRequest, stubSocket); + expect(returnValue).to.be(true); + expect(destroyCalled).to.be(true); + }) + + it('should drop connections when upgrade header is different of `websocket`', function () { + var destroyCalled = false, + stubRequest = { + method: 'GET', + headers: { + upgrade: 'anotherprotocol' + } + }, + stubSocket = { + destroy: function () { + // Simulate Socket.destroy() method when call + destroyCalled = true; + } + } + returnValue = httpProxy.checkMethodAndHeader(stubRequest, stubSocket); + expect(returnValue).to.be(true); + expect(destroyCalled).to.be(true); + }) + + it('should return nothing when all is ok', function () { + var destroyCalled = false, + stubRequest = { + method: 'GET', + headers: { + upgrade: 'websocket' + } + }, + stubSocket = { + destroy: function () { + // Simulate Socket.destroy() method when call + destroyCalled = true; + } + } + returnValue = httpProxy.checkMethodAndHeader(stubRequest, stubSocket); + expect(returnValue).to.be(undefined); + expect(destroyCalled).to.be(false); + }) + }); + + describe('#XHeaders', function () { + it('return if no forward request', function () { + var returnValue = httpProxy.XHeaders({}, {}, {}); + expect(returnValue).to.be(undefined); + }); + + it('set the correct x-forwarded-* headers from req.connection', function () { + var stubRequest = { + connection: { + remoteAddress: '192.168.1.2', + remotePort: '8080' + }, + headers: { + host: '192.168.1.2:8080' + } + } + httpProxy.XHeaders(stubRequest, {}, { xfwd: true }); + expect(stubRequest.headers['x-forwarded-for']).to.be('192.168.1.2'); + expect(stubRequest.headers['x-forwarded-port']).to.be('8080'); + expect(stubRequest.headers['x-forwarded-proto']).to.be('ws'); + }); + + it('set the correct x-forwarded-* headers from req.socket', function () { + var stubRequest = { + socket: { + remoteAddress: '192.168.1.3', + remotePort: '8181' + }, + connection: { + pair: true + }, + headers: { + host: '192.168.1.3:8181' + } + }; + httpProxy.XHeaders(stubRequest, {}, { xfwd: true }); + expect(stubRequest.headers['x-forwarded-for']).to.be('192.168.1.3'); + expect(stubRequest.headers['x-forwarded-port']).to.be('8181'); + expect(stubRequest.headers['x-forwarded-proto']).to.be('wss'); + }); + }); +}); diff --git a/test/lib-http-proxy-test.js b/test/lib-http-proxy-test.js new file mode 100644 index 0000000..6b2ce03 --- /dev/null +++ b/test/lib-http-proxy-test.js @@ -0,0 +1,297 @@ +var httpProxy = require('../lib/http-proxy'), + expect = require('expect.js'), + http = require('http'), + ws = require('ws') + io = require('socket.io'), + ioClient = require('socket.io-client'); + +// +// Expose a port number generator. +// thanks to @3rd-Eden +// +var initialPort = 1024, gen = {}; +Object.defineProperty(gen, 'port', { + get: function get() { + return initialPort++; + } +}); + + +describe('lib/http-proxy.js', function() { + describe('#createProxyServer', function() { + it.skip('should throw without options', function() { + var error; + try { + httpProxy.createProxyServer(); + } catch(e) { + error = e; + } + + expect(error).to.be.an(Error); + }) + + it('should return an object otherwise', function() { + var obj = httpProxy.createProxyServer({ + target: 'http://www.google.com:80' + }); + + expect(obj.web).to.be.a(Function); + expect(obj.ws).to.be.a(Function); + expect(obj.listen).to.be.a(Function); + }); + }); + + describe('#createProxyServer with forward options and using web-incoming passes', function () { + it('should pipe the request using web-incoming#stream method', function (done) { + var ports = { source: gen.port, proxy: gen.port }; + var proxy = httpProxy.createProxyServer({ + forward: 'http://127.0.0.1:' + ports.source + }).listen(ports.proxy); + + var source = http.createServer(function(req, res) { + expect(req.method).to.eql('GET'); + expect(req.headers.host.split(':')[1]).to.eql(ports.proxy); + source.close(); + proxy._server.close(); + done(); + }); + + source.listen(ports.source); + http.request('http://127.0.0.1:' + ports.proxy, function() {}).end(); + }) + }); + + describe('#createProxyServer using the web-incoming passes', function () { + it('should make the request on pipe and finish it', function(done) { + var ports = { source: gen.port, proxy: gen.port }; + var proxy = httpProxy.createProxyServer({ + target: 'http://127.0.0.1:' + ports.source + }).listen(ports.proxy); + + var source = http.createServer(function(req, res) { + expect(req.method).to.eql('POST'); + expect(req.headers['x-forwarded-for']).to.eql('127.0.0.1'); + expect(req.headers.host.split(':')[1]).to.eql(ports.proxy); + source.close(); + proxy._server.close(); + done(); + }); + + source.listen(ports.source); + + http.request({ + hostname: '127.0.0.1', + port: ports.proxy, + method: 'POST', + headers: { + 'x-forwarded-for': '127.0.0.1' + } + }, function() {}).end(); + }); + }); + + describe('#createProxyServer using the web-incoming passes', function () { + it('should make the request, handle response and finish it', function(done) { + var ports = { source: gen.port, proxy: gen.port }; + var proxy = httpProxy.createProxyServer({ + target: 'http://127.0.0.1:' + ports.source + }).listen(ports.proxy); + + var source = http.createServer(function(req, res) { + expect(req.method).to.eql('GET'); + expect(req.headers.host.split(':')[1]).to.eql(ports.proxy); + res.writeHead(200, {'Content-Type': 'text/plain'}) + res.end('Hello from ' + source.address().port); + }); + + source.listen(ports.source); + + http.request({ + hostname: '127.0.0.1', + port: ports.proxy, + method: 'GET' + }, function(res) { + expect(res.statusCode).to.eql(200); + + res.on('data', function (data) { + expect(data.toString()).to.eql('Hello from ' + ports.source); + }); + + res.on('end', function () { + source.close(); + proxy._server.close(); + done(); + }); + }).end(); + }); + }); + + describe('#createProxyServer() method with error response', function () { + it('should make the request and emit the error event', function(done) { + var ports = { source: gen.port, proxy: gen.port }; + var proxy = httpProxy.createProxyServer({ + target: 'http://127.0.0.1:' + ports.source + }); + + proxy.on('error', function (err) { + expect(err).to.be.an(Error); + expect(err.code).to.be('ECONNREFUSED'); + proxy._server.close(); + done(); + }) + + proxy.listen(ports.proxy); + + http.request({ + hostname: '127.0.0.1', + port: ports.proxy, + method: 'GET', + }, function() {}).end(); + }); + }); + + describe('#createProxyServer setting the correct timeout value', function () { + it('should hang up the socket at the timeout', function (done) { + this.timeout(30); + var ports = { source: gen.port, proxy: gen.port }; + var proxy = httpProxy.createProxyServer({ + target: 'http://127.0.0.1:' + ports.source, + timeout: 3 + }).listen(ports.proxy); + + var source = http.createServer(function(req, res) { + setTimeout(function () { + res.end('At this point the socket should be closed'); + }, 5) + }); + + source.listen(ports.source); + + var testReq = http.request({ + hostname: '127.0.0.1', + port: ports.proxy, + method: 'GET', + }, function() {}); + + testReq.on('error', function (e) { + expect(e).to.be.an(Error); + expect(e.code).to.be.eql('ECONNRESET'); + proxy._server.close(); + source.close(); + done(); + }); + + testReq.end(); + }) + }) + + // describe('#createProxyServer using the web-incoming passes', function () { + // it('should emit events correclty', function(done) { + // var proxy = httpProxy.createProxyServer({ + // target: 'http://127.0.0.1:8080' + // }), + + // proxyServer = proxy.listen('8081'), + + // source = http.createServer(function(req, res) { + // expect(req.method).to.eql('GET'); + // expect(req.headers.host.split(':')[1]).to.eql('8081'); + // res.writeHead(200, {'Content-Type': 'text/plain'}) + // res.end('Hello from ' + source.address().port); + // }), + + // events = []; + + // source.listen('8080'); + + // proxy.ee.on('http-proxy:**', function (uno, dos, tres) { + // events.push(this.event); + // }) + + // http.request({ + // hostname: '127.0.0.1', + // port: '8081', + // method: 'GET', + // }, function(res) { + // expect(res.statusCode).to.eql(200); + + // res.on('data', function (data) { + // expect(data.toString()).to.eql('Hello from 8080'); + // }); + + // res.on('end', function () { + // expect(events).to.contain('http-proxy:outgoing:web:begin'); + // expect(events).to.contain('http-proxy:outgoing:web:end'); + // source.close(); + // proxyServer._server.close(); + // done(); + // }); + // }).end(); + // }); + // }); + + describe('#createProxyServer using the ws-incoming passes', function () { + it('should proxy the websockets stream', function (done) { + var ports = { source: gen.port, proxy: gen.port }; + var proxy = httpProxy.createProxyServer({ + target: 'ws://127.0.0.1:' + ports.source, + ws: true + }), + proxyServer = proxy.listen(ports.proxy), + destiny = new ws.Server({ port: ports.source }, function () { + var client = new ws('ws://127.0.0.1:' + ports.proxy); + + client.on('open', function () { + client.send('hello there'); + }); + + client.on('message', function (msg) { + expect(msg).to.be('Hello over websockets'); + client.close(); + proxyServer._server.close(); + destiny.close(); + done(); + }); + }); + + destiny.on('connection', function (socket) { + socket.on('message', function (msg) { + expect(msg).to.be('hello there'); + socket.send('Hello over websockets'); + }); + }); + }); + }); + + describe('#createProxyServer using the ws-incoming passes', function () { + it('should proxy a socket.io stream', function (done) { + var ports = { source: gen.port, proxy: gen.port }; + var proxy = httpProxy.createProxyServer({ + target: 'ws://127.0.0.1:' + ports.source, + ws: true + }), + proxyServer = proxy.listen(ports.proxy), + destiny = io.listen(ports.source, function () { + var client = ioClient.connect('ws://127.0.0.1:' + ports.proxy); + + client.on('connect', function () { + client.emit('incoming', 'hello there'); + }); + + client.on('outgoing', function (data) { + expect(data).to.be('Hello over websockets'); + proxyServer._server.close(); + destiny.server.close(); + done(); + }); + }); + + destiny.sockets.on('connection', function (socket) { + socket.on('incoming', function (msg) { + expect(msg).to.be('hello there'); + socket.emit('outgoing', 'Hello over websockets'); + }); + }) + }); + }) +}); \ No newline at end of file diff --git a/test/lib-https-proxy-test.js b/test/lib-https-proxy-test.js new file mode 100644 index 0000000..1925049 --- /dev/null +++ b/test/lib-https-proxy-test.js @@ -0,0 +1,222 @@ +var httpProxy = require('../lib/http-proxy'), + expect = require('expect.js'), + http = require('http') + https = require('https'), + path = require('path'), + fs = require('fs'); + +// +// Expose a port number generator. +// thanks to @3rd-Eden +// +var initialPort = 1024, gen = {}; +Object.defineProperty(gen, 'port', { + get: function get() { + return initialPort++; + } +}); + +describe('lib/http-proxy.js', function() { + describe('HTTPS #createProxyServer', function() { + describe('HTTPS to HTTP', function () { + it('should proxy the request en send back the response', function (done) { + var ports = { source: gen.port, proxy: gen.port }; + var source = http.createServer(function(req, res) { + expect(req.method).to.eql('GET'); + expect(req.headers.host.split(':')[1]).to.eql(ports.proxy); + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.end('Hello from ' + ports.source); + }); + + source.listen(ports.source); + + var proxy = httpProxy.createProxyServer({ + target: 'http://127.0.0.1:' + ports.source, + ssl: { + key: fs.readFileSync(path.join(__dirname, 'fixtures', 'agent2-key.pem')), + cert: fs.readFileSync(path.join(__dirname, 'fixtures', 'agent2-cert.pem')), + } + }).listen(ports.proxy); + + https.request({ + host: 'localhost', + port: ports.proxy, + path: '/', + method: 'GET', + rejectUnauthorized: false + }, function(res) { + expect(res.statusCode).to.eql(200); + + res.on('data', function (data) { + expect(data.toString()).to.eql('Hello from ' + ports.source); + }); + + res.on('end', function () { + source.close(); + proxy._server.close(); + done(); + }) + }).end(); + }) + }); + describe('HTTP to HTTPS', function () { + it('should proxy the request en send back the response', function (done) { + var ports = { source: gen.port, proxy: gen.port }; + var source = https.createServer({ + key: fs.readFileSync(path.join(__dirname, 'fixtures', 'agent2-key.pem')), + cert: fs.readFileSync(path.join(__dirname, 'fixtures', 'agent2-cert.pem')), + }, function (req, res) { + expect(req.method).to.eql('GET'); + expect(req.headers.host.split(':')[1]).to.eql(ports.proxy); + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.end('Hello from ' + ports.source); + }); + + source.listen(ports.source); + + var proxy = httpProxy.createProxyServer({ + target: 'https://127.0.0.1:' + ports.source, + // Allow to use SSL self signed + secure: false + }).listen(ports.proxy); + + http.request({ + hostname: '127.0.0.1', + port: ports.proxy, + method: 'GET' + }, function(res) { + expect(res.statusCode).to.eql(200); + + res.on('data', function (data) { + expect(data.toString()).to.eql('Hello from ' + ports.source); + }); + + res.on('end', function () { + source.close(); + proxy._server.close(); + done(); + }); + }).end(); + }) + }) + describe('HTTPS to HTTPS', function () { + it('should proxy the request en send back the response', function (done) { + var ports = { source: gen.port, proxy: gen.port }; + var source = https.createServer({ + key: fs.readFileSync(path.join(__dirname, 'fixtures', 'agent2-key.pem')), + cert: fs.readFileSync(path.join(__dirname, 'fixtures', 'agent2-cert.pem')), + }, function(req, res) { + expect(req.method).to.eql('GET'); + expect(req.headers.host.split(':')[1]).to.eql(ports.proxy); + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.end('Hello from ' + ports.source); + }); + + source.listen(ports.source); + + var proxy = httpProxy.createProxyServer({ + target: 'https://127.0.0.1:' + ports.source, + ssl: { + key: fs.readFileSync(path.join(__dirname, 'fixtures', 'agent2-key.pem')), + cert: fs.readFileSync(path.join(__dirname, 'fixtures', 'agent2-cert.pem')), + }, + secure: false + }).listen(ports.proxy); + + https.request({ + host: 'localhost', + port: ports.proxy, + path: '/', + method: 'GET', + rejectUnauthorized: false + }, function(res) { + expect(res.statusCode).to.eql(200); + + res.on('data', function (data) { + expect(data.toString()).to.eql('Hello from ' + ports.source); + }); + + res.on('end', function () { + source.close(); + proxy._server.close(); + done(); + }) + }).end(); + }) + }); + describe('HTTPS not allow SSL self signed', function () { + it('should fail with error', function (done) { + var ports = { source: gen.port, proxy: gen.port }; + var source = https.createServer({ + key: fs.readFileSync(path.join(__dirname, 'fixtures', 'agent2-key.pem')), + cert: fs.readFileSync(path.join(__dirname, 'fixtures', 'agent2-cert.pem')), + }).listen(ports.source); + + var proxy = httpProxy.createProxyServer({ + target: 'https://127.0.0.1:' + ports.source, + secure: true + }); + + proxy.listen(ports.proxy); + + proxy.on('error', function (err, req, res) { + expect(err).to.be.an(Error); + expect(err.toString()).to.be('Error: DEPTH_ZERO_SELF_SIGNED_CERT') + done(); + }) + + http.request({ + hostname: '127.0.0.1', + port: ports.proxy, + method: 'GET' + }).end(); + }) + }) + describe('HTTPS to HTTP using own server', function () { + it('should proxy the request en send back the response', function (done) { + var ports = { source: gen.port, proxy: gen.port }; + var source = http.createServer(function(req, res) { + expect(req.method).to.eql('GET'); + expect(req.headers.host.split(':')[1]).to.eql(ports.proxy); + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.end('Hello from ' + ports.source); + }); + + source.listen(ports.source); + + var proxy = httpProxy.createServer({ + agent: new http.Agent({ maxSockets: 2 }) + }); + + var ownServer = https.createServer({ + key: fs.readFileSync(path.join(__dirname, 'fixtures', 'agent2-key.pem')), + cert: fs.readFileSync(path.join(__dirname, 'fixtures', 'agent2-cert.pem')), + }, function (req, res) { + proxy.web(req, res, { + target: 'http://127.0.0.1:' + ports.source + }) + }).listen(ports.proxy); + + https.request({ + host: 'localhost', + port: ports.proxy, + path: '/', + method: 'GET', + rejectUnauthorized: false + }, function(res) { + expect(res.statusCode).to.eql(200); + + res.on('data', function (data) { + expect(data.toString()).to.eql('Hello from ' + ports.source); + }); + + res.on('end', function () { + source.close(); + ownServer.close(); + done(); + }) + }).end(); + }) + }) + }); +}); \ No newline at end of file