diff --git a/.gitignore b/.gitignore index 468b525c9..099c4d9df 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ -config.json -node_modules/ -npm-debug.log +node_modules +*.swp +cov +atest.js +notes +primus-proxy.js +tes.js diff --git a/.travis.yml b/.travis.yml index efd470846..ba5be4174 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,5 @@ language: node_js node_js: - - 0.8 - "0.10" - "0.11" @@ -8,3 +7,6 @@ notifications: email: - travis@nodejitsu.com irc: "irc.freenode.org#nodejitsu" + +script: + npm test diff --git a/CHANGELOG.md b/CHANGELOG.md index 2205ee874..21d6bb03c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,97 +1,4 @@ -## ChangeLog for: node-http-proxy +### v 1.0.0-alpha -## Version 0.10.0 - 3/18/2013 +- Complete refactor with new API -- Breaking change: `proxyResponse` events are emitted on the `HttpProxy` or `RoutingProxy` instances as originally was intended in `0.9.x`. - -## Version 0.9.1 - 3/9/2013 - -- Ensure that `webSocketProxyError` and `proxyError` both receive the error (indexzero). - -## Version 0.9.0 - 3/9/2013 -- Fix #276 Ensure response.headers.location is defined (indexzero) -- Fix #248 Make options immutable in RoutingProxy (indexzero) -- Fix #359 Do not modify the protocol in redirect request for external sites. (indexzero) -- Fix #373 Do not use "Transfer-Encoding: chunked" header for proxied DELETE requests with no "Content-Length" header. (indexzero) -- Fix #338 Set "content-length" header to "0" if it is not already set on DELETE requests. (indexzero) -- Updates to README.md and Examples (ramitos, jamie-stackhouse, oost, indexzero) -- Fixes to ProxyTable and Routing Proxy (adjohnson916, otavoijr) -- New API for ProxyTable (mikkel, tglines) -- Add `options.timeout` for specifying socket timeouts (pdoran) -- Improve bin/node-http-proxy (niallo) -- Don't emit `proxyError` twice (erasmospunk) -- Fix memory leaks in WebSocket proxying -- Support UNIX Sockets (yosefd) -- Fix truncated chunked respones (jpetazzo) -- Allow upstream listeners to get `proxyResponse` (colinmollenhour) - -## Version 0.8.1 - 6/5/2012 -- Fix re-emitting of events in RoutingProxy (coderarity) -- New load balancer and middleware examples (marak) -- Docs updated including changelog (lot of gently people) - -## Version 0.8.0 - 12/23/2011 -- Improve support and tests for url segment routing (maxogden) -- Fix aborting connections when request close (c4milo) -- Avoid 'Transfer-Encoding' on HTTP/1.0 clients (koichik). -- Support for Node.js 0.6.x (mmalecki) - -## Version 0.7.3 - 10/4/2011 -- Fix setting x-forwarded headers (jesusabdullah) -- Updated examples (AvianFlu) - -## Version 0.7.0 - 9/10/2011 -- Handles to every throw-able resume() call (isaacs) -- Updated tests, README and package.json (indexzero) -- Added HttpProxy.close() method (indexzero) - -## Version 0.6.6 - 8/31/2011 -- Add more examples (dominictarr) -- Use of 'pkginfo' (indexzero) -- Handle cases where res.write throws (isaacs) -- Handles to every throw-able res.end call (isaacs) - -## Version 0.5.11 - 6/21/2011 -- Add more examples with WebSockets (indexzero) -- Update the documentation (indexzero) - -## Version 0.5.7 - 5/19/2011 -- Fix to README related to markup and fix some examples (benatkin) -- Improve WebSockets handling (indexzero) -- Improve WebSockets tests (indexzero) -- Improve https tests (olauzon) -- Add devDependencies to package.json (olauzon) -- Add 'proxyError' event (indexzero) -- Add 'x-forwarded-{port|proto}' headers support (indexzero) -- Keep-Alive connection supported (indexzero) - -## Version 0.5.0 - 4/15/2011 -- Remove winston in favor of custom events (indexzero) -- Add x-forwarded-for Header (indexzero) -- Fix WebSocket support (indexzero) -- Add tests / examples for WebSocket support (indexzero) -- Update .proxyRequest() and .proxyWebSocketRequest() APIs (indexzero) -- Add HTTPS support (indexzero) -- Add tests / examples for HTTPS support (indexzero) - -## Version 0.4.1 - 3/20/2011 -- Include missing dependency in package.json (indexzero) - -## Version 0.4.0 - 3/20/2011 -- Update for node.js 0.4.0 (indexzero) -- Remove pool dependency in favor of http.Agent (indexzero) -- Store buffered data using `.buffer()` instead of on the HttpProxy instance (indexzero) -- Change the ProxyTable to be a lookup table instead of actively proxying (indexzero) -- Allow for pure host-only matching in ProxyTable (indexzero) -- Use winston for logging (indexzero) -- Improve tests with async setup and more coverage (indexzero) -- Improve code documentation (indexzero) - -### Version 0.3.1 - 11/22/2010 -- Added node-http-proxy binary script (indexzero) -- Added experimental WebSocket support (indutny) -- Added forward proxy functionality (indexzero) -- Added proxy table for multiple target lookup (indexzero) -- Simplified tests using helpers.js (indexzero) -- Fixed uncaughtException bug with invalid proxy target (indutny) -- Added configurable logging for HttpProxy and ProxyTable (indexzero) \ No newline at end of file diff --git a/LICENSE b/LICENSE index db9b0b1ed..2bab4b9b1 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,7 @@ node-http-proxy - Copyright (c) 2010 Charlie Robbins, Mikeal Rogers, Fedor Indutny, & Marak Squires + 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 diff --git a/README.md b/README.md index 594d02ad1..4d68f3d5e 100644 --- a/README.md +++ b/README.md @@ -1,70 +1,80 @@ -# node-http-proxy [![Build Status](https://secure.travis-ci.org/nodejitsu/node-http-proxy.png)](http://travis-ci.org/nodejitsu/node-http-proxy) +

+ +

- +node-http-proxy +======= -## Battle-hardened node.js 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. -### Features +### Build Status -* Reverse proxies incoming http.ServerRequest streams -* Can be used as a CommonJS module in node.js -* Reverse or Forward Proxy based on simple JSON-based configuration -* Supports [WebSockets][1] -* Supports [HTTPS][2] -* Minimal request overhead and latency -* Full suite of functional tests -* Battled-hardened through __production usage__ @ [nodejitsu.com][0] -* Written entirely in Javascript -* Easy to use API +

+ +    + + +

+### Looking to Upgrade from 0.8.x ? Click [here](UPGRADING.md) -node-http-proxy is `<= 0.8.x` compatible, if you're looking for a `>= 0.10` compatible version please check [caronte](https://github.com/nodejitsu/node-http-proxy/tree/caronte) +### Core Concept -### When to use node-http-proxy +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)) -Let's suppose you were running multiple http application servers, but you only wanted to expose one machine to the internet. You could setup node-http-proxy on that one machine and then reverse-proxy the incoming http requests to locally running services which were not exposed to the outside network. - -### Installing npm (node package manager) +```javascript +var httpProxy = require('http-proxy'); -``` -curl https://npmjs.org/install.sh | sh +var proxy = httpProxy.createProxyServer(options); ``` -### Installing node-http-proxy +An object will be returned with four values: -``` -npm install http-proxy +* 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' }); +}); ``` -## Using node-http-proxy +Errors can be listened on either using the Event Emitter API -There are several ways to use node-http-proxy; the library is designed to be flexible so that it can be used by itself, or in conjunction with other node.js libraries / tools: +```javascript +proxy.on('error', function(e) { + ... +}); +``` -1. Standalone HTTP Proxy server -2. Inside of another HTTP server (like Connect) -3. In conjunction with a Proxy Routing Table -4. As a forward-proxy with a reverse proxy -5. From the command-line as a long running process -6. customized with 3rd party middleware. +or using the callback API -In each of these scenarios node-http-proxy can handle any of these types of requests: +```javascript +proxy.web(req, res, { target: 'http://mytarget.com:8080' }, function(e) { ... }); +``` -1. HTTP Requests (http://) -2. HTTPS Requests (https://) -3. WebSocket Requests (ws://) -4. Secure WebSocket Requests (wss://) +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. -See the [examples][3] for more working sample code. -### Setup a basic stand-alone proxy server +#### Setup a basic stand-alone proxy server -``` js +```js var http = require('http'), httpProxy = require('http-proxy'); // -// Create your proxy server +// Create your proxy server and set the target in the options. // -httpProxy.createServer(9000, 'localhost').listen(8000); +httpProxy.createProxyServer({target:'http://localhost:9000'}).listen(8000); // // Create your target server @@ -76,571 +86,237 @@ http.createServer(function (req, res) { }).listen(9000); ``` -### Setup a stand-alone proxy server with custom server logic +#### 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 +```js var http = require('http'), httpProxy = require('http-proxy'); // // Create a proxy server with custom application logic // -httpProxy.createServer(function (req, res, proxy) { - // - // Put your custom server logic here - // - proxy.proxyRequest(req, res, { - host: 'localhost', - port: 9000 - }); -}).listen(8000); - -http.createServer(function (req, res) { - res.writeHead(200, { 'Content-Type': 'text/plain' }); - res.write('request successfully proxied: ' + req.url +'\n' + JSON.stringify(req.headers, true, 2)); - res.end(); -}).listen(9000); -``` - -### Setup a stand-alone proxy server with latency (e.g. IO, etc) - -``` js -var http = require('http'), - httpProxy = require('http-proxy'); +var proxy = httpProxy.createProxyServer({}); // -// Create a proxy server with custom application logic +// 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 // -httpProxy.createServer(function (req, res, proxy) { - // - // Buffer the request so that `data` and `end` events - // are not lost during async operation(s). - // - var buffer = httpProxy.buffer(req); - - // - // Wait for two seconds then respond: this simulates - // performing async actions before proxying a request - // - setTimeout(function () { - proxy.proxyRequest(req, res, { - host: 'localhost', - port: 9000, - buffer: buffer - }); - }, 2000); -}).listen(8000); +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' }); +}); -http.createServer(function (req, res) { - res.writeHead(200, { 'Content-Type': 'text/plain' }); - res.write('request successfully proxied: ' + req.url +'\n' + JSON.stringify(req.headers, true, 2)); - res.end(); -}).listen(9000); +console.log("listening on port 5050") +server.listen(5050); ``` -### Proxy requests within another http server +#### Setup a stand-alone proxy server with latency -``` js +```js var http = require('http'), httpProxy = require('http-proxy'); - + // -// Create a new instance of HttProxy to use in your server +// Create a proxy server with latency // -var proxy = new httpProxy.RoutingProxy(); +var proxy = httpProxy.createProxyServer(); // -// Create a regular http server and proxy its handler +// Create your server that make an operation that take a while +// and then proxy de request // http.createServer(function (req, res) { - // - // Put your custom server logic here, then proxy - // - proxy.proxyRequest(req, res, { - host: 'localhost', - port: 9000 - }); -}).listen(8001); + // 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: ' + req.url +'\n' + JSON.stringify(req.headers, true, 2)); + res.write('request successfully proxied to: ' + req.url + '\n' + JSON.stringify(req.headers, true, 2)); res.end(); -}).listen(9000); +}).listen(9008); ``` -### Proxy requests using a ProxyTable -A Proxy Table is a simple lookup table that maps incoming requests to proxy target locations. Take a look at an example of the options you need to pass to httpProxy.createServer: +#### Listening for proxy events -``` js -var options = { - router: { - 'foo.com/baz': '127.0.0.1:8001', - 'foo.com/buz': '127.0.0.1:8002', - 'bar.com/buz': '127.0.0.1:8003' - } -}; -``` - -The above route table will take incoming requests to 'foo.com/baz' and forward them to '127.0.0.1:8001'. Likewise it will take incoming requests to 'foo.com/buz' and forward them to '127.0.0.1:8002'. The routes themselves are later converted to regular expressions to enable more complex matching functionality. We can create a proxy server with these options by using the following code: - -``` js -var proxyServer = httpProxy.createServer(options); -proxyServer.listen(80); -``` - -### Proxy requests using a 'Hostname Only' ProxyTable -As mentioned in the previous section, all routes passes to the ProxyTable are by default converted to regular expressions that are evaluated at proxy-time. This is good for complex URL rewriting of proxy requests, but less efficient when one simply wants to do pure hostname routing based on the HTTP 'Host' header. If you are only concerned with hostname routing, you change the lookup used by the internal ProxyTable: - -``` js -var options = { - hostnameOnly: true, - router: { - 'foo.com': '127.0.0.1:8001', - 'bar.com': '127.0.0.1:8002' - } -} -``` - -Notice here that I have not included paths on the individual domains because this is not possible when using only the HTTP 'Host' header. Care to learn more? See [RFC2616: HTTP/1.1, Section 14.23, "Host"][4]. - -### Proxy requests using a 'Pathname Only' ProxyTable - -If you dont care about forwarding to different hosts, you can redirect based on the request path. - -``` js -var options = { - pathnameOnly: true, - router: { - '/wiki': '127.0.0.1:8001', - '/blog': '127.0.0.1:8002', - '/api': '127.0.0.1:8003' - } -} -``` - -This comes in handy if you are running separate services or applications on separate paths. Note, using this option disables routing by hostname entirely. - - -### Proxy requests with an additional forward proxy -Sometimes in addition to a reverse proxy, you may want your front-facing server to forward traffic to another location. For example, if you wanted to load test your staging environment. This is possible when using node-http-proxy using similar JSON-based configuration to a proxy table: - -``` js -var proxyServerWithForwarding = httpProxy.createServer(9000, 'localhost', { - forward: { - port: 9000, - host: 'staging.com' - } -}); -proxyServerWithForwarding.listen(80); -``` - -The forwarding option can be used in conjunction with the proxy table options by simply including both the 'forward' and 'router' properties in the options passed to 'createServer'. +* `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. -### Listening for proxy events -Sometimes you want to listen to an event on a proxy. For example, you may want to listen to the 'end' event, which represents when the proxy has finished proxying a request. - -``` js +```js var httpProxy = require('http-proxy'); - -var server = httpProxy.createServer(function (req, res, proxy) { - var buffer = httpProxy.buffer(req); - - proxy.proxyRequest(req, res, { - host: '127.0.0.1', - port: 9000, - buffer: buffer - }); -}); - -server.proxy.on('end', function () { - console.log("The request was proxied."); +// Error example +// +// Http Proxy Server with bad target +// +var proxy = httpProxy.createServer({ + target:'http://localhost:9005' }); -server.listen(8000); -``` - -It's important to remember not to listen for events on the proxy object in the function passed to `httpProxy.createServer`. Doing so would add a new listener on every request, which would end up being a disaster. - -## Using HTTPS -You have all the full flexibility of node-http-proxy offers in HTTPS as well as HTTP. The two basic scenarios are: with a stand-alone proxy server or in conjunction with another HTTPS server. +proxy.listen(8005); -### Proxying to HTTP from HTTPS -This is probably the most common use-case for proxying in conjunction with HTTPS. You have some front-facing HTTPS server, but all of your internal traffic is HTTP. In this way, you can reduce the number of servers to which your CA and other important security files are deployed and reduce the computational overhead from HTTPS traffic. - -Using HTTPS in `node-http-proxy` is relatively straight-forward: - -``` js -var fs = require('fs'), - http = require('http'), - https = require('https'), - httpProxy = require('http-proxy'); - -var options = { - https: { - key: fs.readFileSync('path/to/your/key.pem', 'utf8'), - cert: fs.readFileSync('path/to/your/cert.pem', 'utf8') - } -}; - -// -// Create a standalone HTTPS proxy server // -httpProxy.createServer(8000, 'localhost', options).listen(8001); +// 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.'); +}); // -// Create an instance of HttpProxy to use with another HTTPS server +// Listen for the `proxyRes` event on `proxy`. // -var proxy = new httpProxy.HttpProxy({ - target: { - host: 'localhost', - port: 8000 - } +proxy.on('proxyRes', function (res) { + console.log('RAW Response from the target', JSON.stringify(res.headers, true, 2)); }); -https.createServer(options.https, function (req, res) { - proxy.proxyRequest(req, res) -}).listen(8002); -// -// Create the target HTTPS server for both cases -// -http.createServer(function (req, res) { - res.writeHead(200, { 'Content-Type': 'text/plain' }); - res.write('hello https\n'); - res.end(); -}).listen(8000); ``` -### Using two certificates - -Suppose that your reverse proxy will handle HTTPS traffic for two different domains `fobar.com` and `barbaz.com`. -If you need to use two different certificates you can take advantage of [Server Name Indication](http://en.wikipedia.org/wiki/Server_Name_Indication). +#### 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. -``` js -var https = require('https'), - path = require("path"), - fs = require("fs"), - crypto = require("crypto"); - -// -// generic function to load the credentials context from disk -// -function getCredentialsContext (cer) { - return crypto.createCredentials({ - key: fs.readFileSync(path.join(__dirname, 'certs', cer + '.key')), - cert: fs.readFileSync(path.join(__dirname, 'certs', cer + '.crt')) - }).context; -} - -// -// A certificate per domain hash -// -var certs = { - "fobar.com": getCredentialsContext("foobar"), - "barbaz.com": getCredentialsContext("barbaz") -}; +##### HTTPS -> HTTP +```js // -// Proxy options -// -// This section assumes that myCert, myKey and myCa are defined (they are not -// in this example). With a SNICallback, the proxy needs a default set of -// certificates to use. +// Create the HTTPS proxy server in front of a HTTP server // -var options = { - https: { - SNICallback: function (hostname) { - return certs[hostname]; - }, - cert: myCert, - key: myKey, - ca: [myCa] +httpProxy.createServer({ + target: { + host: 'localhost', + port: 9009 }, - hostnameOnly: true, - router: { - 'fobar.com': '127.0.0.1:8001', - 'barbaz.com': '127.0.0.1:8002' + ssl: { + key: fs.readFileSync('valid-ssl-key.pem'), 'utf8'), + cert: fs.readFileSync('valid-ssl-cert.pem'), 'utf8') } -}; - -// -// Create a standalone HTTPS proxy server -// -httpProxy.createServer(options).listen(8001); - -// -// Create the target HTTPS server -// -http.createServer(function (req, res) { - res.writeHead(200, { 'Content-Type': 'text/plain' }); - res.write('hello https\n'); - res.end(); -}).listen(8000); - +}).listen(8009); ``` -### Proxying to HTTPS from HTTPS -Proxying from HTTPS to HTTPS is essentially the same as proxying from HTTPS to HTTP, but you must include the `target` option in when calling `httpProxy.createServer` or instantiating a new instance of `HttpProxy`. - -``` js -var fs = require('fs'), - https = require('https'), - httpProxy = require('http-proxy'); - -var options = { - https: { - key: fs.readFileSync('path/to/your/key.pem', 'utf8'), - cert: fs.readFileSync('path/to/your/cert.pem', 'utf8') - }, - target: { - https: true // This could also be an Object with key and cert properties - } -}; - -// -// Create a standalone HTTPS proxy server -// -httpProxy.createServer(8000, 'localhost', options).listen(8001); +##### HTTPS -> HTTPS +```js // -// Create an instance of HttpProxy to use with another HTTPS server +// Create the proxy server listening on port 443 // -var proxy = new httpProxy.HttpProxy({ - target: { - host: 'localhost', - port: 8000, - https: true - } -}); +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); +``` -https.createServer(options.https, function (req, res) { - proxy.proxyRequest(req, res); -}).listen(8002); +#### Proxying WebSockets +You can activate the websocket support for the proxy using `ws:true` in the options. +```js // -// Create the target HTTPS server for both cases +// Create a proxy server for websockets // -https.createServer(options.https, function (req, res) { - res.writeHead(200, { 'Content-Type': 'text/plain' }); - res.write('hello https\n'); - res.end(); -}).listen(8000); +httpProxy.createServer({ + target: 'ws://localhost:9014', + ws: true +}).listen(8014); ``` -## Middleware -`node-http-proxy` now supports connect middleware. Add middleware functions to your createServer call: - -``` js -httpProxy.createServer( - require('connect-gzip').gzip(), - 9000, 'localhost' -).listen(8000); -``` - -A regular request we receive is to support the modification of html/xml content that is returned in the response from an upstream server. - -[Harmon](https://github.com/No9/harmon/) is a stream based middleware plugin that is designed to solve that problem in the most effective way possible. - -If you would like to handle errors passed to `next()` then attach a listener to the proxy: - - server = httpProxy.createServer( - myMiddleWare, - 9000, 'localhost' - ).listen(8000); - - server.proxy.on('middlewareError', function (err, req, res) { - // handle the error here and call res.end() - }); - -## Proxying WebSockets -Websockets are handled automatically when using `httpProxy.createServer()`, however, if you supply a callback inside the createServer call, you will need to handle the 'upgrade' proxy event yourself. Here's how: +Also you can proxy the websocket requests just calling the `ws(req, socket, head)` method. ```js - -var options = { - .... -}; - -var server = httpProxy.createServer( - callback/middleware, - options -); - -server.listen(port, function () { ... }); -server.on('upgrade', function (req, socket, head) { - server.proxy.proxyWebSocketRequest(req, socket, head); -}); -``` - -If you would rather not use createServer call, and create the server that proxies yourself, see below: - -``` js -var http = require('http'), - httpProxy = require('http-proxy'); - // -// Create an instance of node-http-proxy +// Setup our server to proxy standard HTTP requests // -var proxy = new httpProxy.HttpProxy({ +var proxy = new httpProxy.createProxyServer({ target: { host: 'localhost', - port: 8000 + port: 9015 } }); - -var server = http.createServer(function (req, res) { - // - // Proxy normal HTTP requests - // - proxy.proxyRequest(req, res); +var proxyServer = http.createServer(function (req, res) { + proxy.web(req, res); }); -server.on('upgrade', function (req, socket, head) { - // - // Proxy websocket requests too - // - proxy.proxyWebSocketRequest(req, socket, head); +// +// Listen to the `upgrade` event and proxy the +// WebSocket requests as well. +// +proxyServer.on('upgrade', function (req, socket, head) { + proxy.ws(req, socket, head); }); -server.listen(8080); +proxyServer.listen(8015); ``` -### with custom server logic +### Contributing and Issues -``` js -var httpProxy = require('http-proxy') +* 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) -var server = httpProxy.createServer(function (req, res, proxy) { - // - // Put your custom server logic here - // - proxy.proxyRequest(req, res, { - host: 'localhost', - port: 9000 - }); -}) +### Options -server.on('upgrade', function (req, socket, head) { - // - // Put your custom server logic here - // - server.proxy.proxyWebSocketRequest(req, socket, head, { - host: 'localhost', - port: 9000 - }); -}); - -server.listen(8080); -``` - -### Configuring your Socket limits - -By default, `node-http-proxy` will set a 100 socket limit for all `host:port` proxy targets. You can change this in two ways: +`httpProxy.createProxyServer` supports the following options: -1. By passing the `maxSockets` option to `httpProxy.createServer()` -2. By calling `httpProxy.setMaxSockets(n)`, where `n` is the number of sockets you with to use. + * **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 -## POST requests and buffering +If you are using the `proxyServer.listen` method, the following options are also applicable: -express.bodyParser will interfere with proxying of POST requests (and other methods that have a request -body). With bodyParser active, proxied requests will never send anything to the upstream server, and -the original client will just hang. See https://github.com/nodejitsu/node-http-proxy/issues/180 for options. + * **ssl**: object to be passed to https.createServer() + * **ws**: true/false, if you want to proxy websockets + * **xfwd**: true/false, adds x-forward headers -## Using node-http-proxy from the command line -When you install this package with npm, a node-http-proxy binary will become available to you. Using this binary is easy with some simple options: -``` js -usage: node-http-proxy [options] +### Test -All options should be set with the syntax --option=value - -options: - --port PORT Port that the proxy server should run on - --target HOST:PORT Location of the server the proxy will target - --config OUTFILE Location of the configuration file for the proxy server - --silent Silence the log output from the proxy server - -h, --help You're staring at it ``` - -
-## Why doesn't node-http-proxy have more advanced features like x, y, or z? - -If you have a suggestion for a feature currently not supported, feel free to open a [support issue][6]. node-http-proxy is designed to just proxy http requests from one server to another, but we will be soon releasing many other complimentary projects that can be used in conjunction with node-http-proxy. - -## Options - -### Http Proxy - -`createServer()` supports the following options - -```javascript -{ - forward: { // options for forward-proxy - port: 8000, - host: 'staging.com' - }, - target : { // options for proxy target - port : 8000, - host : 'localhost', - }; - source : { // additional options for websocket proxying - host : 'localhost', - port : 8000, - https: true - }, - enable : { - xforward: true // enables X-Forwarded-For - }, - changeOrigin: false, // changes the origin of the host header to the target URL - timeout: 120000 // override the default 2 minute http socket timeout value in milliseconds -} +$ npm test ``` -## Run Tests -The test suite is designed to fully cover the combinatoric possibilities of HTTP and HTTPS proxying: +### Logo -1. HTTP --> HTTP -2. HTTPS --> HTTP -3. HTTPS --> HTTPS -4. HTTP --> HTTPS +Logo created by [Diego Pasquali](http://dribbble.com/diegopq) -``` -vows test/*-test.js --spec -vows test/*-test.js --spec --https -vows test/*-test.js --spec --https --target=https -vows test/*-test.js --spec --target=https -``` - -
### License -(The MIT License) - -Copyright (c) 2010 Charlie Robbins, Mikeal Rogers, Fedor Indutny, & Marak Squires - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -[0]: http://nodejitsu.com -[1]: https://github.com/nodejitsu/node-http-proxy/blob/master/examples/websocket/websocket-proxy.js -[2]: https://github.com/nodejitsu/node-http-proxy/blob/master/examples/http/proxy-https-to-http.js -[3]: https://github.com/nodejitsu/node-http-proxy/tree/master/examples -[4]: http://www.ietf.org/rfc/rfc2616.txt -[5]: http://socket.io -[6]: http://github.com/nodejitsu/node-http-proxy/issues +>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 000000000..8c0db431c --- /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 000000000..2a852d14e --- /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 000000000..be09b6388 --- /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 000000000..a70c55817 --- /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/websockets-throughput.js b/benchmark/scripts/websockets-throughput.js similarity index 98% rename from benchmark/websockets-throughput.js rename to benchmark/scripts/websockets-throughput.js index 6787385ad..866e42461 100644 --- a/benchmark/websockets-throughput.js +++ b/benchmark/scripts/websockets-throughput.js @@ -1,7 +1,7 @@ var crypto = require('crypto'), WebSocket = require('ws'), async = require('async'), - httpProxy = require('../'); + httpProxy = require('../../'); var SERVER_PORT = 8415, PROXY_PORT = 8514; diff --git a/bin/node-http-proxy b/bin/node-http-proxy deleted file mode 100755 index 07d199ca6..000000000 --- a/bin/node-http-proxy +++ /dev/null @@ -1,113 +0,0 @@ -#!/usr/bin/env node - -var path = require('path'), - fs = require('fs'), - util = require('util'), - argv = require('optimist').argv, - httpProxy = require('../lib/node-http-proxy'); - -var help = [ - "usage: node-http-proxy [options] ", - "", - "Starts a node-http-proxy server using the specified command-line options", - "", - "options:", - " --port PORT Port that the proxy server should run on", - " --host HOST Host that the proxy server should run on", - " --target HOST:PORT Location of the server the proxy will target", - " --config OUTFILE Location of the configuration file for the proxy server", - " --silent Silence the log output from the proxy server", - " --user USER User to drop privileges to once server socket is bound", - " -h, --help You're staring at it" -].join('\n'); - -if (argv.h || argv.help || Object.keys(argv).length === 2) { - return util.puts(help); -} - -var location, config = {}, - port = argv.port || 80, - host = argv.host || undefined, - target = argv.target; - user = argv.user; - -// -// If we were passed a config, parse it -// -if (argv.config) { - try { - var data = fs.readFileSync(argv.config); - config = JSON.parse(data.toString()); - } catch (ex) { - util.puts('Error starting node-http-proxy: ' + ex); - process.exit(1); - } -} - -// -// If `config.https` is set, then load the required file contents into the config options. -// -if (config.https) { - Object.keys(config.https).forEach(function (key) { - // If CA certs are specified, load those too. - if (key === "ca") { - for (var i=0; i < config.https.ca.length; i++) { - if (config.https.ca === undefined) { - config.https.ca = []; - } - config.https.ca[i] = fs.readFileSync(config.https[key][i], 'utf8'); - } - } else { - config.https[key] = fs.readFileSync(config.https[key], 'utf8'); - } - }); -} - -// -// Check to see if we should silence the logs -// -config.silent = typeof argv.silent !== 'undefined' ? argv.silent : config.silent; - -// -// If we were passed a target, parse the url string -// -if (typeof target === 'string') location = target.split(':'); - -// -// Create the server with the specified options -// -var server; -if (location) { - var targetPort = location.length === 1 ? 80 : parseInt(location[1]); - server = httpProxy.createServer(targetPort, location[0], config); -} -else if (config.router) { - server = httpProxy.createServer(config); -} -else { - return util.puts(help); -} - -// -// Start the server -// -if (host) { - server.listen(port, host); -} else { - server.listen(port); -} - - -// -// Drop privileges if requested -// -if (typeof user === 'string') { - process.setuid(user); -} - -// -// Notify that the server is started -// -if (!config.silent) { - util.puts('node-http-proxy server now listening on port: ' + port); -} diff --git a/config.sample.json b/config.sample.json deleted file mode 100644 index 8f15f882b..000000000 --- a/config.sample.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "silent": false, - "router": { - "localhost": "localhost:9000" - }, - "forward": { - "port": 9001, - "host": "localhost" - } -} \ No newline at end of file diff --git a/doc/logo.png b/doc/logo.png new file mode 100644 index 000000000..a36bdcf2a 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 index 04564cef2..cc13f4b5c 100644 --- a/examples/balancer/simple-balancer-with-websockets.js +++ b/examples/balancer/simple-balancer-with-websockets.js @@ -1,5 +1,31 @@ +/* + 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/node-http-proxy'); + httpProxy = require('../../lib/http-proxy'); // // A simple round-robin load balancing strategy. @@ -22,7 +48,7 @@ var addresses = [ // var proxies = addresses.map(function (target) { - return new httpProxy.HttpProxy({ + return new httpProxy.createProxyServer({ target: target }); }); @@ -43,7 +69,7 @@ function nextProxy() { // var server = http.createServer(function (req, res) { - nextProxy().proxyRequest(req, res); + nextProxy().web(req, res); }); // @@ -51,8 +77,8 @@ var server = http.createServer(function (req, res) { // server.on('upgrade', function (req, socket, head) { - nextProxy().proxyWebSocketRequest(req, socket, head); + nextProxy().ws(req, socket, head); }); -server.listen(8080); +server.listen(8001); \ No newline at end of file diff --git a/examples/balancer/simple-balancer.js b/examples/balancer/simple-balancer.js index f1fa0c06b..3f53cc63d 100644 --- a/examples/balancer/simple-balancer.js +++ b/examples/balancer/simple-balancer.js @@ -1,4 +1,31 @@ -var httpProxy = require('../../lib/node-http-proxy'); +/* + 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. // @@ -14,23 +41,24 @@ var addresses = [ port: 80 } ]; +var proxy = httpProxy.createServer(); -httpProxy.createServer(function (req, res, proxy) { +http.createServer(function (req, res) { // // On each request, get the first location from the list... // - var target = addresses.shift(); + var target = { target: addresses.shift() }; // // ...then proxy to the server whose 'turn' it is... // console.log('balancing request to: ', target); - proxy.proxyRequest(req, res, target); + proxy.web(req, res, target); // // ...and then the server you just used becomes the last item in the list. // addresses.push(target); -}).listen(8000); +}).listen(8021); // Rinse; repeat; enjoy. \ No newline at end of file diff --git a/examples/http/basic-proxy.js b/examples/http/basic-proxy.js index b890e69a0..e9be0d79b 100644 --- a/examples/http/basic-proxy.js +++ b/examples/http/basic-proxy.js @@ -1,7 +1,7 @@ /* basic-proxy.js: Basic example of proxying over HTTP - Copyright (c) 2010 Charlie Robbins, Mikeal Rogers, Fedor Indutny, & Marak Squires. + 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 @@ -27,7 +27,7 @@ var util = require('util'), colors = require('colors'), http = require('http'), - httpProxy = require('../../lib/node-http-proxy'); + httpProxy = require('../../lib/http-proxy'); var welcome = [ '# # ##### ##### ##### ##### ##### #### # # # #', @@ -43,7 +43,9 @@ util.puts(welcome.rainbow.bold); // // Basic Http Proxy Server // -httpProxy.createServer(9000, 'localhost').listen(8000); +httpProxy.createServer({ + target:'http://localhost:9003' +}).listen(8003); // // Target Http Server @@ -52,7 +54,7 @@ http.createServer(function (req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }); res.write('request successfully proxied to: ' + req.url + '\n' + JSON.stringify(req.headers, true, 2)); res.end(); -}).listen(9000); +}).listen(9003); -util.puts('http proxy server'.blue + ' started '.green.bold + 'on port '.blue + '8000'.yellow); -util.puts('http server '.blue + 'started '.green.bold + 'on port '.blue + '9000 '.yellow); +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 index 230dfc651..30aa53dd6 100644 --- a/examples/http/concurrent-proxy.js +++ b/examples/http/concurrent-proxy.js @@ -1,7 +1,7 @@ /* concurrent-proxy.js: check levelof concurrency through proxy. - Copyright (c) 2010 Charlie Robbins, Mikeal Rogers, Fedor Indutny, & Marak Squires. + 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 @@ -27,12 +27,14 @@ var util = require('util'), colors = require('colors'), http = require('http'), - httpProxy = require('../../lib/node-http-proxy'); + httpProxy = require('../../lib/http-proxy'); // // Basic Http Proxy Server // -httpProxy.createServer(9000, 'localhost').listen(8000); +httpProxy.createServer({ + target:'http://localhost:9004' +}).listen(8004); // // Target Http Server @@ -60,7 +62,7 @@ http.createServer(function (req, res) { connections.shift()(); } } -}).listen(9000); +}).listen(9004); -util.puts('http proxy server'.blue + ' started '.green.bold + 'on port '.blue + '8000'.yellow); -util.puts('http server '.blue + 'started '.green.bold + 'on port '.blue + '9000 '.yellow); +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 index dc439ea19..1c54b5ab8 100644 --- a/examples/http/custom-proxy-error.js +++ b/examples/http/custom-proxy-error.js @@ -1,7 +1,7 @@ /* custom-proxy-error.js: Example of using the custom `proxyError` event. - Copyright (c) 2010 Charlie Robbins, Mikeal Rogers, Fedor Indutny, & Marak Squires. + 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 @@ -27,22 +27,23 @@ var util = require('util'), colors = require('colors'), http = require('http'), - httpProxy = require('../../lib/node-http-proxy'); + httpProxy = require('../../lib/http-proxy'); // -// Http Proxy Server with Latency +// Http Proxy Server with bad target // -var server = httpProxy.createServer(9000, 'localhost'); +var proxy = httpProxy.createServer({ + target:'http://localhost:9005' +}); // -// Tell the server to listen on port 8002 +// Tell the proxy to listen on port 8000 // -server.listen(8002); +proxy.listen(8005); // -// Listen for the `proxyError` event on `server.proxy`. _It will not -// be raised on the server itself._ -server.proxy.on('proxyError', function (err, req, res) { +// Listen for the `error` event on `proxy`. +proxy.on('error', function (err, req, res) { res.writeHead(500, { 'Content-Type': 'text/plain' }); @@ -51,4 +52,4 @@ server.proxy.on('proxyError', function (err, req, res) { }); -util.puts('http proxy server '.blue + 'started '.green.bold + 'on port '.blue + '8002 '.yellow + 'with custom error message'.magenta.underline); \ No newline at end of file +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/middleware/gzip-middleware-proxytable.js b/examples/http/error-handling.js similarity index 57% rename from examples/middleware/gzip-middleware-proxytable.js rename to examples/http/error-handling.js index 527d3d7b8..292fb6144 100644 --- a/examples/middleware/gzip-middleware-proxytable.js +++ b/examples/http/error-handling.js @@ -1,7 +1,7 @@ /* - gzip-middleware-proxytable.js: Basic example of `connect-gzip` middleware in node-http-proxy + error-handling.js: Example of handle erros for HTTP and WebSockets - Copyright (c) 2010 Charlie Robbins, Mikeal Rogers, Fedor Indutny, Marak Squires, & Dominic Tarr. + 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 @@ -27,28 +27,37 @@ var util = require('util'), colors = require('colors'), http = require('http'), - httpProxy = require('../../lib/node-http-proxy'); + httpProxy = require('../../lib/http-proxy'); // -// Basic Http Proxy Server +// HTTP Proxy Server // -httpProxy.createServer( - require('connect-gzip').gzip({ matchType: /.?/ }), - { - router: { - "localhost/fun": "localhost:9000" - } - } -).listen(8000); +var proxy = httpProxy.createProxyServer({target:'http://localhost:9000', ws:true}); // -// Target Http Server +// Example of error handling // -http.createServer(function (req, res) { - res.writeHead(200, { 'Content-Type': 'text/plain' }); - res.write('request successfully proxied to: ' + req.url + '\n' + JSON.stringify(req.headers, true, 2)); - res.end(); -}).listen(9000); - +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); -util.puts('http server '.blue + 'started '.green.bold + 'on port '.blue + '9000 '.yellow); diff --git a/examples/http/proxy-table.js b/examples/http/forward-and-target-proxy.js similarity index 64% rename from examples/http/proxy-table.js rename to examples/http/forward-and-target-proxy.js index 55d97aed5..c564bfbbd 100644 --- a/examples/http/proxy-table.js +++ b/examples/http/forward-and-target-proxy.js @@ -1,7 +1,7 @@ /* - proxy-table.js: Example of proxying over HTTP with proxy table + forward-and-target-proxy.js: Example of proxying over HTTP with additional forward proxy - Copyright (c) 2010 Charlie Robbins, Mikeal Rogers, Fedor Indutny, & Marak Squires. + 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 @@ -27,16 +27,21 @@ var util = require('util'), colors = require('colors'), http = require('http'), - httpProxy = require('../../lib/node-http-proxy'); + httpProxy = require('../../lib/http-proxy'); // -// Http Proxy Server with Proxy Table +// Setup proxy server with forwarding // httpProxy.createServer({ - router: { - 'localhost': 'localhost:9000' + target: { + port: 9006, + host: 'localhost' + }, + forward: { + port: 9007, + host: 'localhost' } -}).listen(8001); +}).listen(8006); // // Target Http Server @@ -45,7 +50,18 @@ http.createServer(function (req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }); res.write('request successfully proxied to: ' + req.url + '\n' + JSON.stringify(req.headers, true, 2)); res.end(); -}).listen(9000); +}).listen(9006); -util.puts('http proxy server '.blue + 'started '.green.bold + 'on port '.blue + '8001 '.yellow + 'with proxy table'.magenta.underline); -util.puts('http server '.blue + 'started '.green.bold + 'on port '.blue + '9000 '.yellow); +// +// 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 index ecc20fd4c..d94f48414 100644 --- a/examples/http/forward-proxy.js +++ b/examples/http/forward-proxy.js @@ -1,7 +1,7 @@ /* forward-proxy.js: Example of proxying over HTTP with additional forward proxy - Copyright (c) 2010 Charlie Robbins, Mikeal Rogers, Fedor Indutny, & Marak Squires. + 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 @@ -27,26 +27,17 @@ var util = require('util'), colors = require('colors'), http = require('http'), - httpProxy = require('../../lib/node-http-proxy'); + httpProxy = require('../../lib/http-proxy'); // // Setup proxy server with forwarding // -httpProxy.createServer(9000, 'localhost', { +httpProxy.createServer({ forward: { - port: 9001, + port: 9019, host: 'localhost' } -}).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(9000); +}).listen(8019); // // Target Http Forwarding Server @@ -56,8 +47,7 @@ http.createServer(function (req, res) { 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(9001); +}).listen(9019); -util.puts('http proxy server '.blue + 'started '.green.bold + 'on port '.blue + '8003 '.yellow + 'with forward proxy'.magenta.underline); -util.puts('http server '.blue + 'started '.green.bold + 'on port '.blue + '9000 '.yellow); -util.puts('http forward server '.blue + 'started '.green.bold + 'on port '.blue + '9001 '.yellow); \ No newline at end of file +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 index 2063d0e5b..01ec93cc7 100644 --- a/examples/http/latent-proxy.js +++ b/examples/http/latent-proxy.js @@ -1,7 +1,7 @@ /* latent-proxy.js: Example of proxying over HTTP with latency - Copyright (c) 2010 Charlie Robbins, Mikeal Rogers, Fedor Indutny, & Marak Squires. + 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 @@ -27,21 +27,19 @@ var util = require('util'), colors = require('colors'), http = require('http'), - httpProxy = require('../../lib/node-http-proxy'); + httpProxy = require('../../lib/http-proxy'); // // Http Proxy Server with Latency // -httpProxy.createServer(function (req, res, proxy) { - var buffer = httpProxy.buffer(req); +var proxy = httpProxy.createProxyServer(); +http.createServer(function (req, res) { setTimeout(function () { - proxy.proxyRequest(req, res, { - port: 9000, - host: 'localhost', - buffer: buffer + proxy.web(req, res, { + target: 'http://localhost:9008' }); - }, 200); -}).listen(8002); + }, 500); +}).listen(8008); // // Target Http Server @@ -50,7 +48,7 @@ http.createServer(function (req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }); res.write('request successfully proxied to: ' + req.url + '\n' + JSON.stringify(req.headers, true, 2)); res.end(); -}).listen(9000); +}).listen(9008); -util.puts('http proxy server '.blue + 'started '.green.bold + 'on port '.blue + '8002 '.yellow + 'with latency'.magenta.underline); -util.puts('http server '.blue + 'started '.green.bold + 'on port '.blue + '9000 '.yellow); +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/middleware/url-middleware.js b/examples/http/proxy-http-to-https.js similarity index 50% rename from examples/middleware/url-middleware.js rename to examples/http/proxy-http-to-https.js index b4f304562..ba5c83816 100644 --- a/examples/middleware/url-middleware.js +++ b/examples/http/proxy-http-to-https.js @@ -1,7 +1,7 @@ /* - url-middleware.js: Example of a simple url routing middleware for node-http-proxy + proxy-http-to-https.js: Basic example of proxying over HTTP to a target HTTPS server - Copyright (c) 2010 Charlie Robbins, Mikeal Rogers, Fedor Indutny, & Marak Squires. + 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 @@ -24,35 +24,23 @@ */ -var util = require('util'), +var https = require('https'), + http = require('http'), + util = require('util'), + path = require('path'), + fs = require('fs'), colors = require('colors'), - http = require('http'), - httpProxy = require('../../lib/node-http-proxy'); + httpProxy = require('../../lib/http-proxy'); // -// Now we set up our proxy. +// Create a HTTP Proxy server with a HTTPS target // -httpProxy.createServer( - // - // This is where our middlewares go, with any options desired - in this case, - // the list of routes/URLs and their destinations. - // - require('proxy-by-url')({ - '/hello': { port: 9000, host: 'localhost' }, - '/charlie': { port: 80, host: 'charlieistheman.com' }, - '/google': { port: 80, host: 'google.com' } - }) -).listen(8000); - -// -// Target Http Server (to listen for requests on 'localhost') -// -http.createServer(function (req, res) { - res.writeHead(200, { 'Content-Type': 'text/plain' }); - res.write('request successfully proxied to: ' + req.url + '\n' + JSON.stringify(req.headers, true, 2)); - res.end(); -}).listen(9000); - -// And finally, some colored startup output. -util.puts('http proxy server'.blue + ' started '.green.bold + 'on port '.blue + '8000'.yellow); -util.puts('http server '.blue + 'started '.green.bold + 'on port '.blue + '9000 '.yellow); +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 index 378de0362..d2a2d5c0d 100644 --- a/examples/http/proxy-https-to-http.js +++ b/examples/http/proxy-https-to-http.js @@ -1,7 +1,7 @@ /* proxy-https-to-http.js: Basic example of proxying over HTTPS to a target HTTP server - Copyright (c) 2010 Charlie Robbins, Mikeal Rogers, Fedor Indutny, & Marak Squires. + 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 @@ -25,27 +25,36 @@ */ var https = require('https'), - http = require('http'), - util = require('util'), + http = require('http'), + util = require('util'), + path = require('path'), + fs = require('fs'), colors = require('colors'), - httpProxy = require('../../lib/node-http-proxy'), - helpers = require('../../test/helpers'); + httpProxy = require('../../lib/http-proxy'), + fixturesDir = path.join(__dirname, '..', '..', 'test', 'fixtures'); // -// Create the target HTTPS server +// 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(8000); +}).listen(9009); // -// Create the proxy server listening on port 443 +// Create the HTTPS proxy server listening on port 8000 // -httpProxy.createServer(8000, 'localhost', { - https: helpers.https -}).listen(8080); - -util.puts('https proxy server'.blue + ' started '.green.bold + 'on port '.blue + '8080'.yellow); -util.puts('http server '.blue + 'started '.green.bold + 'on port '.blue + '8000 '.yellow); +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 index af0d92275..e543f98a7 100644 --- a/examples/http/proxy-https-to-https.js +++ b/examples/http/proxy-https-to-https.js @@ -1,7 +1,7 @@ /* proxy-https-to-https.js: Basic example of proxying over HTTPS to a target HTTPS server - Copyright (c) 2010 Charlie Robbins, Mikeal Rogers, Fedor Indutny, & Marak Squires. + 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 @@ -27,29 +27,33 @@ var https = require('https'), http = require('http'), util = require('util'), + fs = require('fs'), + path = require('path'), colors = require('colors'), - httpProxy = require('../../lib/node-http-proxy'), - helpers = require('../../test/helpers'); + 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(helpers.https, function (req, res) { +https.createServer(httpsOpts, function (req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }); res.write('hello https\n'); res.end(); -}).listen(8000); +}).listen(9010); // // Create the proxy server listening on port 443 // -httpProxy.createServer(8000, 'localhost', { - https: helpers.https, - target: { - https: true, - rejectUnauthorized: false - } -}).listen(8080); - -util.puts('https proxy server'.blue + ' started '.green.bold + 'on port '.blue + '8080'.yellow); -util.puts('https server '.blue + 'started '.green.bold + 'on port '.blue + '8000 '.yellow); +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 index e1b1011c5..410d70b31 100644 --- a/examples/http/standalone-proxy.js +++ b/examples/http/standalone-proxy.js @@ -1,7 +1,7 @@ /* standalone-proxy.js: Example of proxying over HTTP with a standalone HTTP server. - Copyright (c) 2010 Charlie Robbins, Mikeal Rogers, Fedor Indutny, & Marak Squires. + 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 @@ -27,22 +27,19 @@ var util = require('util'), colors = require('colors'), http = require('http'), - httpProxy = require('../../lib/node-http-proxy'); + httpProxy = require('../../lib/http-proxy'); // // Http Server with proxyRequest Handler and Latency // -var proxy = new httpProxy.RoutingProxy(); +var proxy = new httpProxy.createProxyServer(); http.createServer(function (req, res) { - var buffer = httpProxy.buffer(req); setTimeout(function () { - proxy.proxyRequest(req, res, { - port: 9000, - host: 'localhost', - buffer: buffer + proxy.web(req, res, { + target: 'http://localhost:9002' }); }, 200); -}).listen(8004); +}).listen(8002); // // Target Http Server @@ -51,7 +48,7 @@ http.createServer(function (req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }); res.write('request successfully proxied to: ' + req.url + '\n' + JSON.stringify(req.headers, true, 2)); res.end(); -}).listen(9000); +}).listen(9002); -util.puts('http server '.blue + 'started '.green.bold + 'on port '.blue + '8004 '.yellow + 'with proxyRequest handler'.cyan.underline + ' and latency'.magenta); -util.puts('http server '.blue + 'started '.green.bold + 'on port '.blue + '9000 '.yellow); +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 index d889548a0..555c3d154 100644 --- a/examples/middleware/bodyDecoder-middleware.js +++ b/examples/middleware/bodyDecoder-middleware.js @@ -1,8 +1,40 @@ +/* + bodyDecoder-middleware.js: Basic example of `connect.bodyParser()` middleware in node-http-proxy -var Store = require('../helpers/store') - , http = require('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 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 @@ -33,31 +65,31 @@ http.createServer(new Store().handler()).listen(7531, function () { //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. - var bodyParser = require('connect/lib/middleware/bodyParser') //don't worry about incoming contont type //bodyParser.parse[''] = JSON.parse - require('../../lib/node-http-proxy').createServer( + connect.createServer( //refactor the body parser and re-streamer into a separate package - bodyParser(), + 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, proxy) { + 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.proxyRequest(req, res, {host: 'localhost', port: port}) + proxy.web(req, res, { target: { host: 'localhost', port: port }}); } ).listen(1337, function () { - var request = require('request') + 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) { @@ -84,4 +116,4 @@ http.createServer(new Store().handler()).listen(7531, function () { }) }) -}) +}); \ No newline at end of file diff --git a/examples/middleware/gzip-middleware.js b/examples/middleware/gzip-middleware.js index 29097ecd3..756b68fa3 100644 --- a/examples/middleware/gzip-middleware.js +++ b/examples/middleware/gzip-middleware.js @@ -1,7 +1,7 @@ /* gzip-middleware.js: Basic example of `connect-gzip` middleware in node-http-proxy - Copyright (c) 2010 Charlie Robbins, Mikeal Rogers, Fedor Indutny, Marak Squires, & Dominic Tarr. + 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 @@ -27,15 +27,30 @@ var util = require('util'), colors = require('colors'), http = require('http'), - httpProxy = require('../../lib/node-http-proxy'); + 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 // -httpProxy.createServer( - require('connect-gzip').gzip({ matchType: /.?/ }), - 9000, 'localhost' -).listen(8000); +var proxy = httpProxy.createProxyServer({ + target: 'http://localhost:9012' +}); // // Target Http Server @@ -44,7 +59,7 @@ http.createServer(function (req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }); res.write('request successfully proxied to: ' + req.url + '\n' + JSON.stringify(req.headers, true, 2)); res.end(); -}).listen(9000); +}).listen(9012); -util.puts('http proxy server'.blue + ' started '.green.bold + 'on port '.blue + '8000'.yellow); -util.puts('http server '.blue + 'started '.green.bold + 'on port '.blue + '9000 '.yellow); +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/jsonp-middleware.js b/examples/middleware/jsonp-middleware.js deleted file mode 100644 index 15ab9ee05..000000000 --- a/examples/middleware/jsonp-middleware.js +++ /dev/null @@ -1,30 +0,0 @@ -var Store = require('../helpers/store') - , http = require('http') - -// -// jsonp is a handy technique for getting around the limitations of the same-origin policy. -// (http://en.wikipedia.org/wiki/Same_origin_policy) -// -// normally, to dynamically update a page you use an XmlHttpRequest. this has flakey support -// is some browsers and is restricted by the same origin policy. you cannot perform XHR requests to -// someone else's server. one way around this would be to proxy requests to all the servers you want -// to xhr to, and your core server - so that everything has the same port and host. -// -// another way, is to turn json into javascript. (which is exempt from the same origin policy) -// this is done by wrapping the json object in a function call, and then including a script tag. -// -// here we're proxing our own JSON returning server, but we could proxy any server on the internet, -// and our client side app would be slurping down JSONP from anywhere. -// -// curl localhost:1337/whatever?callback=alert -// alert([]) //which is valid javascript! -// -// also see http://en.wikipedia.org/wiki/JSONP#JSONP -// - -http.createServer(new Store().handler()).listen(7531) - -require('../../lib/node-http-proxy').createServer( - require('connect-jsonp')(true), - 'localhost', 7531 -).listen(1337) diff --git a/examples/middleware/modifyResponse-middleware.js b/examples/middleware/modifyResponse-middleware.js index af21236ef..fdd7e6596 100644 --- a/examples/middleware/modifyResponse-middleware.js +++ b/examples/middleware/modifyResponse-middleware.js @@ -1,7 +1,7 @@ /* modifyBody-middleware.js: Example of middleware which modifies response - Copyright (c) 2010 Charlie Robbins, Mikeal Rogers, Fedor Indutny, Marak Squires, & Dominic Tarr. + 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 @@ -27,12 +27,13 @@ var util = require('util'), colors = require('colors'), http = require('http'), - httpProxy = require('../../lib/node-http-proxy'); + connect = require('connect'), + httpProxy = require('../../lib/http-proxy'); // -// Basic Http Proxy Server +// Basic Connect App // -httpProxy.createServer( +connect.createServer( function (req, res, next) { var _write = res.write; @@ -41,8 +42,17 @@ httpProxy.createServer( } next(); }, - 9000, 'localhost' -).listen(8000); + function (req, res) { + proxy.web(req, res); + } +).listen(8013); + +// +// Basic Http Proxy Server +// +var proxy = httpProxy.createProxyServer({ + target: 'http://localhost:9013' +}); // // Target Http Server @@ -50,8 +60,8 @@ httpProxy.createServer( http.createServer(function (req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end('Hello, I know Ruby\n'); -}).listen(9000); +}).listen(9013); -util.puts('http proxy server'.blue + ' started '.green.bold + 'on port '.blue + '8000'.yellow); -util.puts('http server '.blue + 'started '.green.bold + 'on port '.blue + '9000 '.yellow); +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/middleware/url-middleware2.js b/examples/middleware/url-middleware2.js deleted file mode 100644 index 2c894e1af..000000000 --- a/examples/middleware/url-middleware2.js +++ /dev/null @@ -1,30 +0,0 @@ -var util = require('util'), - colors = require('colors'), - http = require('http'), - httpProxy = require('../../lib/node-http-proxy'), - Store = require('../helpers/store') - -http.createServer(new Store().handler()).listen(7531) - -// Now we set up our proxy. -httpProxy.createServer( - // This is where our middlewares go, with any options desired - in this case, - // the list of routes/URLs and their destinations. - require('proxy-by-url')({ - '/store': { port: 7531, host: 'localhost' }, - '/': { port: 9000, host: 'localhost' } - }) -).listen(8000); - -// -// Target Http Server (to listen for requests on 'localhost') -// -http.createServer(function (req, res) { - res.writeHead(200, { 'Content-Type': 'text/plain' }); - res.write('request successfully proxied to: ' + req.url + '\n' + JSON.stringify(req.headers, true, 2)); - res.end(); -}).listen(9000); - -// And finally, some colored startup output. -util.puts('http proxy server'.blue + ' started '.green.bold + 'on port '.blue + '8000'.yellow); -util.puts('http server '.blue + 'started '.green.bold + 'on port '.blue + '9000 '.yellow); diff --git a/examples/package.json b/examples/package.json index ca95fd8cf..3daede1f1 100644 --- a/examples/package.json +++ b/examples/package.json @@ -1,12 +1,13 @@ { - "name": "http-proxy-examples", - "description": "packages required to run the examples", + "name": "http-proxy-examples", + "description": "packages required to run the examples", "version": "0.0.0", "dependencies": { - "connect": "1.6", - "connect-gzip": "0.1", - "connect-jsonp": "0.0.5", - "connect-restreamer": "1", - "proxy-by-url": ">= 0.0.1" + "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" } -} \ No newline at end of file +} diff --git a/examples/websocket/latent-websocket-proxy.js b/examples/websocket/latent-websocket-proxy.js index 99a07281e..64d3d7ce0 100644 --- a/examples/websocket/latent-websocket-proxy.js +++ b/examples/websocket/latent-websocket-proxy.js @@ -1,7 +1,7 @@ /* standalone-websocket-proxy.js: Example of proxying websockets over HTTP with a standalone HTTP server. - Copyright (c) 2010 Charlie Robbins, Mikeal Rogers, Fedor Indutny, & Marak Squires. + 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 @@ -27,7 +27,7 @@ var util = require('util'), http = require('http'), colors = require('colors'), - httpProxy = require('../../lib/node-http-proxy'); + httpProxy = require('../../lib/http-proxy'); try { var io = require('socket.io'), @@ -43,7 +43,7 @@ catch (ex) { // Create the target HTTP server and setup // socket.io on it. // -var server = io.listen(8080); +var server = io.listen(9016); server.sockets.on('connection', function (client) { util.debug('Got websocket connection'); @@ -57,15 +57,15 @@ server.sockets.on('connection', function (client) { // // Setup our server to proxy standard HTTP requests // -var proxy = new httpProxy.HttpProxy({ +var proxy = new httpProxy.createProxyServer({ target: { host: 'localhost', - port: 8080 + port: 9016 } }); var proxyServer = http.createServer(function (req, res) { - proxy.proxyRequest(req, res); + proxy.web(req, res); }); // @@ -73,20 +73,19 @@ var proxyServer = http.createServer(function (req, res) { // WebSocket requests as well. // proxyServer.on('upgrade', function (req, socket, head) { - var buffer = httpProxy.buffer(socket); - setTimeout(function () { - proxy.proxyWebSocketRequest(req, socket, head, buffer); + proxy.ws(req, socket, head); }, 1000); }); -proxyServer.listen(8081); +proxyServer.listen(8016); // // Setup the socket.io client against our proxy // -var ws = client.connect('ws://localhost:8081'); +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 index acf43b979..81d019650 100644 --- a/examples/websocket/standalone-websocket-proxy.js +++ b/examples/websocket/standalone-websocket-proxy.js @@ -1,7 +1,7 @@ /* standalone-websocket-proxy.js: Example of proxying websockets over HTTP with a standalone HTTP server. - Copyright (c) 2010 Charlie Robbins, Mikeal Rogers, Fedor Indutny, & Marak Squires. + 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 @@ -27,7 +27,7 @@ var util = require('util'), http = require('http'), colors = require('colors'), - httpProxy = require('../../lib/node-http-proxy'); + httpProxy = require('../../lib/http-proxy'); try { var io = require('socket.io'), @@ -43,7 +43,7 @@ catch (ex) { // Create the target HTTP server and setup // socket.io on it. // -var server = io.listen(8080); +var server = io.listen(9015); server.sockets.on('connection', function (client) { util.debug('Got websocket connection'); @@ -57,14 +57,14 @@ server.sockets.on('connection', function (client) { // // Setup our server to proxy standard HTTP requests // -var proxy = new httpProxy.HttpProxy({ +var proxy = new httpProxy.createProxyServer({ target: { - host: 'localhost', - port: 8080 + host: 'localhost', + port: 9015 } }); var proxyServer = http.createServer(function (req, res) { - proxy.proxyRequest(req, res); + proxy.web(req, res); }); // @@ -72,16 +72,17 @@ var proxyServer = http.createServer(function (req, res) { // WebSocket requests as well. // proxyServer.on('upgrade', function (req, socket, head) { - proxy.proxyWebSocketRequest(req, socket, head); + proxy.ws(req, socket, head); }); -proxyServer.listen(8081); +proxyServer.listen(8015); // // Setup the socket.io client against our proxy // -var ws = client.connect('ws://localhost:8081'); +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 index 4e3cf6fd1..33d78c675 100644 --- a/examples/websocket/websocket-proxy.js +++ b/examples/websocket/websocket-proxy.js @@ -1,7 +1,7 @@ /* web-socket-proxy.js: Example of proxying over HTTP and WebSockets. - Copyright (c) 2010 Charlie Robbins, Mikeal Rogers, Fedor Indutny, & Marak Squires. + 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 @@ -27,7 +27,7 @@ var util = require('util'), http = require('http'), colors = require('colors'), - httpProxy = require('../../lib/node-http-proxy'); + httpProxy = require('../../lib/http-proxy'); try { var io = require('socket.io'), @@ -43,7 +43,7 @@ catch (ex) { // Create the target HTTP server and setup // socket.io on it. // -var server = io.listen(8080); +var server = io.listen(9014); server.sockets.on('connection', function (client) { util.debug('Got websocket connection'); @@ -57,13 +57,14 @@ server.sockets.on('connection', function (client) { // // Create a proxy server with node-http-proxy // -httpProxy.createServer(8080, 'localhost').listen(8081); +httpProxy.createServer({ target: 'ws://localhost:9014', ws: true }).listen(8014); // // Setup the socket.io client against our proxy // -var ws = client.connect('ws://localhost:8081'); +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 000000000..e6fac8584 --- /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 000000000..34029e864 --- /dev/null +++ b/lib/http-proxy.js @@ -0,0 +1,55 @@ +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; + +module.exports.createProxy = function(options) { + return { + web: httpProxy.createRightProxy('web')(options), + ws: httpProxy.createRightProxy('ws')(options) + }; +} + +/** + * 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 000000000..789757880 --- /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 000000000..4436c1351 --- /dev/null +++ b/lib/http-proxy/index.js @@ -0,0 +1,153 @@ +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; + } + } + }; + }; +} +httpProxy.createRightProxy = createRightProxy; + +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]; + }); + + this.on('error', function(err) { + console.log(err); + }); +} + +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 000000000..305365f7a --- /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) { + if(server) { 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 000000000..9281c4a45 --- /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 000000000..720ac2745 --- /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/lib/node-http-proxy.js b/lib/node-http-proxy.js deleted file mode 100644 index b5de6bb66..000000000 --- a/lib/node-http-proxy.js +++ /dev/null @@ -1,397 +0,0 @@ -/* - node-http-proxy.js: http proxy for node.js - - Copyright (c) 2010 Charlie Robbins, Mikeal Rogers, Marak Squires, Fedor Indutny - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "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'), - https = require('https'), - events = require('events'), - maxSockets = 100; - -// -// Expose version information through `pkginfo`. -// -require('pkginfo')(module, 'version'); - -// -// ### Export the relevant objects exposed by `node-http-proxy` -// -var HttpProxy = exports.HttpProxy = require('./node-http-proxy/http-proxy').HttpProxy, - ProxyTable = exports.ProxyTable = require('./node-http-proxy/proxy-table').ProxyTable, - RoutingProxy = exports.RoutingProxy = require('./node-http-proxy/routing-proxy').RoutingProxy; - -// -// ### function createServer ([port, host, options, handler]) -// #### @port {number} **Optional** Port to use on the proxy target host. -// #### @host {string} **Optional** Host of the proxy target. -// #### @options {Object} **Optional** Options for the HttpProxy instance used -// #### @handler {function} **Optional** Request handler for the server -// Returns a server that manages an instance of HttpProxy. Flexible arguments allow for: -// -// * `httpProxy.createServer(9000, 'localhost')` -// * `httpProxy.createServer(9000, 'localhost', options) -// * `httpPRoxy.createServer(function (req, res, proxy) { ... })` -// -exports.createServer = function () { - var args = Array.prototype.slice.call(arguments), - handlers = [], - callback, - options = {}, - message, - handler, - server, - proxy, - host, - port; - - // - // Liberally parse arguments of the form: - // - // httpProxy.createServer('localhost', 9000, callback); - // httpProxy.createServer({ host: 'localhost', port: 9000 }, callback); - // **NEED MORE HERE!!!** - // - args.forEach(function (arg) { - arg = Number(arg) || arg; - switch (typeof arg) { - case 'string': host = arg; break; - case 'number': port = arg; break; - case 'object': options = arg || {}; break; - case 'function': callback = arg; handlers.push(callback); break; - }; - }); - - // - // Helper function to create intelligent error message(s) - // for the very liberal arguments parsing performed by - // `require('http-proxy').createServer()`. - // - function validArguments() { - var conditions = { - 'port and host': function () { - return port && host; - }, - 'options.target or options.router': function () { - return options && (options.router || - (options.target && options.target.host && options.target.port)); - }, - 'or proxy handlers': function () { - return handlers && handlers.length; - } - } - - var missing = Object.keys(conditions).filter(function (name) { - return !conditions[name](); - }); - - if (missing.length === 3) { - message = 'Cannot proxy without ' + missing.join(', '); - return false; - } - - return true; - } - - if (!validArguments()) { - // - // If `host`, `port` and `options` are all not passed (with valid - // options) then this server is improperly configured. - // - throw new Error(message); - return; - } - - // - // Hoist up any explicit `host` or `port` arguments - // that have been passed in to the options we will - // pass to the `httpProxy.HttpProxy` constructor. - // - options.target = options.target || {}; - options.target.port = options.target.port || port; - options.target.host = options.target.host || host; - - if (options.target && options.target.host && options.target.port) { - // - // If an explicit `host` and `port` combination has been passed - // to `.createServer()` then instantiate a hot-path optimized - // `HttpProxy` object and add the "proxy" middleware layer. - // - proxy = new HttpProxy(options); - handlers.push(function (req, res) { - proxy.proxyRequest(req, res); - }); - } - else { - // - // If no explicit `host` or `port` combination has been passed then - // we have to assume that this is a "go-anywhere" Proxy (i.e. a `RoutingProxy`). - // - proxy = new RoutingProxy(options); - - if (options.router) { - // - // If a routing table has been supplied than we assume - // the user intends us to add the "proxy" middleware layer - // for them - // - handlers.push(function (req, res) { - proxy.proxyRequest(req, res); - }); - - proxy.on('routes', function (routes) { - server.emit('routes', routes); - }); - } - } - - // - // Create the `http[s].Server` instance which will use - // an instance of `httpProxy.HttpProxy`. - // - handler = handlers.length > 1 - ? exports.stack(handlers, proxy) - : function (req, res) { handlers[0](req, res, proxy) }; - - server = options.https - ? https.createServer(options.https, handler) - : http.createServer(handler); - - server.on('close', function () { - proxy.close(); - }); - - if (!callback) { - // - // If an explicit callback has not been supplied then - // automagically proxy the request using the `HttpProxy` - // instance we have created. - // - server.on('upgrade', function (req, socket, head) { - proxy.proxyWebSocketRequest(req, socket, head); - }); - } - - // - // Set the proxy on the server so it is available - // to the consumer of the server - // - server.proxy = proxy; - return server; -}; - -// -// ### function buffer (obj) -// #### @obj {Object} Object to pause events from -// Buffer `data` and `end` events from the given `obj`. -// Consumers of HttpProxy performing async tasks -// __must__ utilize this utility, to re-emit data once -// the async operation has completed, otherwise these -// __events will be lost.__ -// -// var buffer = httpProxy.buffer(req); -// fs.readFile(path, function () { -// httpProxy.proxyRequest(req, res, host, port, buffer); -// }); -// -// __Attribution:__ This approach is based heavily on -// [Connect](https://github.com/senchalabs/connect/blob/master/lib/utils.js#L157). -// However, this is not a big leap from the implementation in node-http-proxy < 0.4.0. -// This simply chooses to manage the scope of the events on a new Object literal as opposed to -// [on the HttpProxy instance](https://github.com/nodejitsu/node-http-proxy/blob/v0.3.1/lib/node-http-proxy.js#L154). -// -exports.buffer = function (obj) { - var events = [], - onData, - onEnd; - - obj.on('data', onData = function (data, encoding) { - events.push(['data', data, encoding]); - }); - - obj.on('end', onEnd = function (data, encoding) { - events.push(['end', data, encoding]); - }); - - return { - end: function () { - obj.removeListener('data', onData); - obj.removeListener('end', onEnd); - }, - destroy: function () { - this.end(); - this.resume = function () { - console.error("Cannot resume buffer after destroying it."); - }; - - onData = onEnd = events = obj = null; - }, - resume: function () { - this.end(); - for (var i = 0, len = events.length; i < len; ++i) { - obj.emit.apply(obj, events[i]); - } - } - }; -}; - -// -// ### function getMaxSockets () -// Returns the maximum number of sockets -// allowed on __every__ outgoing request -// made by __all__ instances of `HttpProxy` -// -exports.getMaxSockets = function () { - return maxSockets; -}; - -// -// ### function setMaxSockets () -// Sets the maximum number of sockets -// allowed on __every__ outgoing request -// made by __all__ instances of `HttpProxy` -// -exports.setMaxSockets = function (value) { - maxSockets = value; -}; - -// -// ### function stack (middlewares, proxy) -// #### @middlewares {Array} Array of functions to stack. -// #### @proxy {HttpProxy|RoutingProxy} Proxy instance to -// Iteratively build up a single handler to the `http.Server` -// `request` event (i.e. `function (req, res)`) by wrapping -// each middleware `layer` into a `child` middleware which -// is in invoked by the parent (i.e. predecessor in the Array). -// -// adapted from https://github.com/creationix/stack -// -exports.stack = function stack (middlewares, proxy) { - var handle; - middlewares.reverse().forEach(function (layer) { - var child = handle; - handle = function (req, res) { - var next = function (err) { - if (err) { - if (! proxy.emit('middlewareError', err, req, res)) { - console.error('Error in middleware(s): %s', err.stack); - } - - if (res._headerSent) { - res.destroy(); - } - else { - res.statusCode = 500; - res.setHeader('Content-Type', 'text/plain'); - res.end('Internal Server Error'); - } - - return; - } - - if (child) { - child(req, res); - } - }; - - // - // Set the prototype of the `next` function to the instance - // of the `proxy` so that in can be used interchangably from - // a `connect` style callback and a true `HttpProxy` object. - // - // e.g. `function (req, res, next)` vs. `function (req, res, proxy)` - // - next.__proto__ = proxy; - layer(req, res, next); - }; - }); - - return handle; -}; - -// -// ### function _getAgent (host, port, secure) -// #### @options {Object} Options to use when creating the agent. -// -// { -// host: 'localhost', -// port: 9000, -// https: true, -// maxSockets: 100 -// } -// -// Createsan agent from the `http` or `https` module -// and sets the `maxSockets` property appropriately. -// -exports._getAgent = function _getAgent (options) { - if (!options || !options.host) { - throw new Error('`options.host` is required to create an Agent.'); - } - - if (!options.port) { - options.port = options.https ? 443 : 80; - } - - var Agent = options.https ? https.Agent : http.Agent, - agent; - - // require('http-proxy').setMaxSockets() should override http's default - // configuration value (which is pretty low). - options.maxSockets = options.maxSockets || maxSockets; - agent = new Agent(options); - - return agent; -} - -// -// ### function _getProtocol (options) -// #### @options {Object} Options for the proxy target. -// Returns the appropriate node.js core protocol module (i.e. `http` or `https`) -// based on the `options` supplied. -// -exports._getProtocol = function _getProtocol (options) { - return options.https ? https : http; -}; - - -// -// ### function _getBase (options) -// #### @options {Object} Options for the proxy target. -// Returns the relevate base object to create on outgoing proxy request. -// If `options.https` are supplied, this function respond with an object -// containing the relevant `ca`, `key`, and `cert` properties. -// -exports._getBase = function _getBase (options) { - var result = function () {}; - - if (options.https && typeof options.https === 'object') { - ['ca', 'cert', 'key'].forEach(function (key) { - if (options.https[key]) { - result.prototype[key] = options.https[key]; - } - }); - } - - return result; -}; diff --git a/lib/node-http-proxy/http-proxy.js b/lib/node-http-proxy/http-proxy.js deleted file mode 100644 index 92541baec..000000000 --- a/lib/node-http-proxy/http-proxy.js +++ /dev/null @@ -1,983 +0,0 @@ -/* - node-http-proxy.js: http proxy for node.js - - Copyright (c) 2010 Charlie Robbins, Mikeal Rogers, Marak Squires, Fedor Indutny - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "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 events = require('events'), - http = require('http'), - util = require('util'), - url = require('url'), - httpProxy = require('../node-http-proxy'); - -// -// @private {RegExp} extractPort -// Reusable regular expression for getting the -// port from a host string. -// -var extractPort = /:(\d+)$/; - -// -// ### function HttpProxy (options) -// #### @options {Object} Options for this instance. -// Constructor function for new instances of HttpProxy responsible -// for managing the life-cycle of streaming reverse proxyied HTTP requests. -// -// Example options: -// -// { -// target: { -// host: 'localhost', -// port: 9000 -// }, -// forward: { -// host: 'localhost', -// port: 9001 -// } -// } -// -var HttpProxy = exports.HttpProxy = function (options) { - if (!options || !options.target) { - throw new Error('Both `options` and `options.target` are required.'); - } - - events.EventEmitter.call(this); - - var self = this; - - // - // Setup basic proxying options: - // - // * forward {Object} Options for a forward-proxy (if-any) - // * target {Object} Options for the **sole** proxy target of this instance - // - this.forward = options.forward; - this.target = options.target; - this.timeout = options.timeout; - - // - // Setup the necessary instances instance variables for - // the `target` and `forward` `host:port` combinations - // used by this instance. - // - // * agent {http[s].Agent} Agent to be used by this instance. - // * protocol {http|https} Core node.js module to make requests with. - // * base {Object} Base object to create when proxying containing any https settings. - // - function setupProxy (key) { - self[key].agent = httpProxy._getAgent(self[key]); - self[key].protocol = httpProxy._getProtocol(self[key]); - self[key].base = httpProxy._getBase(self[key]); - } - - setupProxy('target'); - if (this.forward) { - setupProxy('forward'); - } - - // - // Setup opt-in features - // - this.enable = options.enable || {}; - this.enable.xforward = typeof this.enable.xforward === 'boolean' - ? this.enable.xforward - : true; - - // if event listener is set then use it else unlimited. - this.eventListenerCount = typeof options.eventListenerCount === 'number'? options.eventListenerCount : 0 ; - - // - // Setup additional options for WebSocket proxying. When forcing - // the WebSocket handshake to change the `sec-websocket-location` - // and `sec-websocket-origin` headers `options.source` **MUST** - // be provided or the operation will fail with an `origin mismatch` - // by definition. - // - this.source = options.source || { host: 'localhost', port: 80 }; - this.source.https = this.source.https || options.https; - this.changeOrigin = options.changeOrigin || false; -}; - -// Inherit from events.EventEmitter -util.inherits(HttpProxy, events.EventEmitter); - -// -// ### function proxyRequest (req, res, buffer) -// #### @req {ServerRequest} Incoming HTTP Request to proxy. -// #### @res {ServerResponse} Outgoing HTTP Request to write proxied data to. -// #### @buffer {Object} Result from `httpProxy.buffer(req)` -// -HttpProxy.prototype.proxyRequest = function (req, res, buffer) { - var self = this, - errState = false, - outgoing = new(this.target.base), - reverseProxy, - location; - - // If this is a DELETE request then set the "content-length" - // header (if it is not already set) - if (req.method === 'DELETE') { - req.headers['content-length'] = req.headers['content-length'] || '0'; - } - - // - // Add common proxy headers to the request so that they can - // be availible to the proxy target server. If the proxy is - // part of proxy chain it will append the address: - // - // * `x-forwarded-for`: IP Address of the original request - // * `x-forwarded-proto`: Protocol of the original request - // * `x-forwarded-port`: Port of the original request. - // - if (this.enable.xforward && req.connection && req.socket) { - if (req.headers['x-forwarded-for']) { - var addressToAppend = "," + req.connection.remoteAddress || req.socket.remoteAddress; - req.headers['x-forwarded-for'] += addressToAppend; - } - else { - req.headers['x-forwarded-for'] = req.connection.remoteAddress || req.socket.remoteAddress; - } - - if (req.headers['x-forwarded-port']) { - var portToAppend = "," + getPortFromHostHeader(req); - req.headers['x-forwarded-port'] += portToAppend; - } - else { - req.headers['x-forwarded-port'] = getPortFromHostHeader(req); - } - - if (req.headers['x-forwarded-proto']) { - var protoToAppend = "," + getProto(req); - req.headers['x-forwarded-proto'] += protoToAppend; - } - else { - req.headers['x-forwarded-proto'] = getProto(req); - } - } - - if (this.timeout) { - req.socket.setTimeout(this.timeout); - } - - // - // Emit the `start` event indicating that we have begun the proxy operation. - // - this.emit('start', req, res, this.target); - - // - // If forwarding is enabled for this instance, foward proxy the - // specified request to the address provided in `this.forward` - // - if (this.forward) { - this.emit('forward', req, res, this.forward); - this._forwardRequest(req); - } - - // - // #### function proxyError (err) - // #### @err {Error} Error contacting the proxy target - // Short-circuits `res` in the event of any error when - // contacting the proxy target at `host` / `port`. - // - function proxyError(err) { - errState = true; - - // - // Emit an `error` event, allowing the application to use custom - // error handling. The error handler should end the response. - // - if (self.emit('proxyError', err, req, res)) { - return; - } - - res.writeHead(500, { 'Content-Type': 'text/plain' }); - - if (req.method !== 'HEAD') { - // - // This NODE_ENV=production behavior is mimics Express and - // Connect. - // - if (process.env.NODE_ENV === 'production') { - res.write('Internal Server Error'); - } - else { - res.write('An error has occurred: ' + JSON.stringify(err)); - } - } - - try { res.end() } - catch (ex) { console.error("res.end error: %s", ex.message) } - } - - // - // Setup outgoing proxy with relevant properties. - // - outgoing.host = this.target.host; - outgoing.hostname = this.target.hostname; - outgoing.port = this.target.port; - outgoing.socketPath = this.target.socketPath; - outgoing.agent = this.target.agent; - outgoing.method = req.method; - outgoing.path = url.parse(req.url).path; - outgoing.headers = req.headers; - - // - // If the changeOrigin option is specified, change the - // origin of the host header to the target URL! Please - // don't revert this without documenting it! - // - if (this.changeOrigin) { - outgoing.headers.host = this.target.host; - // Only add port information to the header if not default port - // for this protocol. - // See https://github.com/nodejitsu/node-http-proxy/issues/458 - if (this.target.port !== 443 && this.target.https || - this.target.port !== 80 && !this.target.https) { - outgoing.headers.host += ':' + this.target.port; - } - } - - // - // Open new HTTP request to internal resource with will act - // as a reverse proxy pass - // - reverseProxy = this.target.protocol.request(outgoing, function (response) { - // - // Process the `reverseProxy` `response` when it's received. - // - if (req.httpVersion === '1.0') { - if (req.headers.connection) { - response.headers.connection = req.headers.connection - } else { - response.headers.connection = 'close' - } - } else if (!response.headers.connection) { - if (req.headers.connection) { response.headers.connection = req.headers.connection } - else { - response.headers.connection = 'keep-alive' - } - } - - // Remove `Transfer-Encoding` header if client's protocol is HTTP/1.0 - // or if this is a DELETE request with no content-length header. - // See: https://github.com/nodejitsu/node-http-proxy/pull/373 - if (req.httpVersion === '1.0' || (req.method === 'DELETE' - && !req.headers['content-length'])) { - delete response.headers['transfer-encoding']; - } - - if ((response.statusCode === 301 || response.statusCode === 302) - && typeof response.headers.location !== 'undefined') { - location = url.parse(response.headers.location); - if (location.host === req.headers.host) { - if (self.source.https && !self.target.https) { - response.headers.location = response.headers.location.replace(/^http\:/, 'https:'); - } - if (self.target.https && !self.source.https) { - response.headers.location = response.headers.location.replace(/^https\:/, 'http:'); - } - } - } - - // - // When the `reverseProxy` `response` ends, end the - // corresponding outgoing `res` unless we have entered - // an error state. In which case, assume `res.end()` has - // already been called and the 'error' event listener - // removed. - // - var ended = false; - response.on('close', function () { - if (!ended) { response.emit('end') } - }); - - // - // After reading a chunked response, the underlying socket - // will hit EOF and emit a 'end' event, which will abort - // the request. If the socket was paused at that time, - // pending data gets discarded, truncating the response. - // This code makes sure that we flush pending data. - // - response.connection.on('end', function () { - if (response.readable && response.resume) { - response.resume(); - } - }); - - response.on('end', function () { - ended = true; - if (!errState) { - try { res.end() } - catch (ex) { console.error("res.end error: %s", ex.message) } - - // Emit the `end` event now that we have completed proxying - self.emit('end', req, res, response); - } - }); - - // Allow observer to modify headers or abort response - try { self.emit('proxyResponse', req, res, response) } - catch (ex) { - errState = true; - return; - } - - // Set the headers of the client response - if (res.sentHeaders !== true) { - Object.keys(response.headers).forEach(function (key) { - res.setHeader(key, response.headers[key]); - }); - res.writeHead(response.statusCode); - } - - function ondata(chunk) { - if (res.writable) { - // Only pause if the underlying buffers are full, - // *and* the connection is not in 'closing' state. - // Otherwise, the pause will cause pending data to - // be discarded and silently lost. - if (false === res.write(chunk) && response.pause - && response.connection.readable) { - response.pause(); - } - } - } - - response.on('data', ondata); - - function ondrain() { - if (response.readable && response.resume) { - response.resume(); - } - } - - res.on('drain', ondrain); - }); - - // allow unlimited listeners ... - reverseProxy.setMaxListeners(this.eventListenerCount); - - // - // Handle 'error' events from the `reverseProxy`. Setup timeout override if needed - // - reverseProxy.once('error', proxyError); - - // Set a timeout on the socket if `this.timeout` is specified. - reverseProxy.once('socket', function (socket) { - if (self.timeout) { - socket.setTimeout(self.timeout); - } - }); - - // - // Handle 'error' events from the `req` (e.g. `Parse Error`). - // - req.on('error', proxyError); - - // - // If `req` is aborted, we abort our `reverseProxy` request as well. - // - req.on('aborted', function () { - reverseProxy.abort(); - }); - - // - // For each data `chunk` received from the incoming - // `req` write it to the `reverseProxy` request. - // - req.on('data', function (chunk) { - if (!errState) { - var flushed = reverseProxy.write(chunk); - if (!flushed) { - req.pause(); - reverseProxy.once('drain', function () { - try { req.resume() } - catch (er) { console.error("req.resume error: %s", er.message) } - }); - - // - // Force the `drain` event in 100ms if it hasn't - // happened on its own. - // - setTimeout(function () { - reverseProxy.emit('drain'); - }, 100); - } - } - }); - - // - // When the incoming `req` ends, end the corresponding `reverseProxy` - // request unless we have entered an error state. - // - req.on('end', function () { - if (!errState) { - reverseProxy.end(); - } - }); - - //Aborts reverseProxy if client aborts the connection. - req.on('close', function () { - if (!errState) { - reverseProxy.abort(); - } - }); - - // - // If we have been passed buffered data, resume it. - // - if (buffer) { - return !errState - ? buffer.resume() - : buffer.destroy(); - } -}; - -// -// ### function proxyWebSocketRequest (req, socket, head, buffer) -// #### @req {ServerRequest} Websocket request to proxy. -// #### @socket {net.Socket} Socket for the underlying HTTP request -// #### @head {string} Headers for the Websocket request. -// #### @buffer {Object} Result from `httpProxy.buffer(req)` -// Performs a WebSocket proxy operation to the location specified by -// `this.target`. -// -HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, upgradeHead, buffer) { - var self = this, - outgoing = new(this.target.base), - listeners = {}, - errState = false, - CRLF = '\r\n', - //copy upgradeHead to avoid retention of large slab buffers used in node core - head = new Buffer(upgradeHead.length); - upgradeHead.copy(head); - - // - // WebSocket requests must have the `GET` method and - // the `upgrade:websocket` header - // - if (req.method !== 'GET' || req.headers.upgrade.toLowerCase() !== 'websocket') { - // - // This request is not WebSocket request - // - return socket.destroy(); - } - - // - // Add common proxy headers to the request so that they can - // be availible to the proxy target server. If the proxy is - // part of proxy chain it will append the address: - // - // * `x-forwarded-for`: IP Address of the original request - // * `x-forwarded-proto`: Protocol of the original request - // * `x-forwarded-port`: Port of the original request. - // - if (this.enable.xforward && req.connection) { - if (req.headers['x-forwarded-for']) { - var addressToAppend = "," + req.connection.remoteAddress || socket.remoteAddress; - req.headers['x-forwarded-for'] += addressToAppend; - } - else { - req.headers['x-forwarded-for'] = req.connection.remoteAddress || socket.remoteAddress; - } - - if (req.headers['x-forwarded-port']) { - var portToAppend = "," + getPortFromHostHeader(req); - req.headers['x-forwarded-port'] += portToAppend; - } - else { - req.headers['x-forwarded-port'] = getPortFromHostHeader(req); - } - - if (req.headers['x-forwarded-proto']) { - var protoToAppend = "," + (req.connection.pair ? 'wss' : 'ws'); - req.headers['x-forwarded-proto'] += protoToAppend; - } - else { - req.headers['x-forwarded-proto'] = req.connection.pair ? 'wss' : 'ws'; - } - } - - self.emit('websocket:start', req, socket, head, this.target); - - // - // Helper function for setting appropriate socket values: - // 1. Turn of all bufferings - // 2. For server set KeepAlive - // - function _socket(socket, keepAlive) { - socket.setTimeout(0); - socket.setNoDelay(true); - - if (keepAlive) { - if (socket.setKeepAlive) { - socket.setKeepAlive(true, 0); - } - else if (socket.pair.cleartext.socket.setKeepAlive) { - socket.pair.cleartext.socket.setKeepAlive(true, 0); - } - } - } - - // - // Setup the incoming client socket. - // - _socket(socket, true); - - // - // On `upgrade` from the Agent socket, listen to - // the appropriate events. - // - function onUpgrade (reverseProxy, proxySocket) { - if (!reverseProxy) { - proxySocket.end(); - socket.end(); - return; - } - - // - // Any incoming data on this WebSocket to the proxy target - // will be written to the `reverseProxy` socket. - // - proxySocket.on('data', listeners.onIncoming = function (data) { - if (reverseProxy.incoming.socket.writable) { - try { - self.emit('websocket:outgoing', req, socket, head, data); - var flushed = reverseProxy.incoming.socket.write(data); - if (!flushed) { - proxySocket.pause(); - reverseProxy.incoming.socket.once('drain', function () { - try { proxySocket.resume() } - catch (er) { console.error("proxySocket.resume error: %s", er.message) } - }); - - // - // Force the `drain` event in 100ms if it hasn't - // happened on its own. - // - setTimeout(function () { - reverseProxy.incoming.socket.emit('drain'); - }, 100); - } - } - catch (ex) { - detach(); - } - } - }); - - // - // Any outgoing data on this Websocket from the proxy target - // will be written to the `proxySocket` socket. - // - reverseProxy.incoming.socket.on('data', listeners.onOutgoing = function (data) { - try { - self.emit('websocket:incoming', reverseProxy, reverseProxy.incoming, head, data); - var flushed = proxySocket.write(data); - if (!flushed) { - reverseProxy.incoming.socket.pause(); - proxySocket.once('drain', function () { - try { reverseProxy.incoming.socket.resume() } - catch (er) { console.error("reverseProxy.incoming.socket.resume error: %s", er.message) } - }); - - // - // Force the `drain` event in 100ms if it hasn't - // happened on its own. - // - setTimeout(function () { - proxySocket.emit('drain'); - }, 100); - } - } - catch (ex) { - detach(); - } - }); - - // - // Helper function to detach all event listeners - // from `reverseProxy` and `proxySocket`. - // - function detach() { - proxySocket.destroySoon(); - proxySocket.removeListener('end', listeners.onIncomingClose); - proxySocket.removeListener('data', listeners.onIncoming); - reverseProxy.incoming.socket.destroySoon(); - reverseProxy.incoming.socket.removeListener('end', listeners.onOutgoingClose); - reverseProxy.incoming.socket.removeListener('data', listeners.onOutgoing); - } - - // - // If the incoming `proxySocket` socket closes, then - // detach all event listeners. - // - listeners.onIncomingClose = function () { - reverseProxy.incoming.socket.destroy(); - detach(); - - // Emit the `end` event now that we have completed proxying - self.emit('websocket:end', req, socket, head); - } - - // - // If the `reverseProxy` socket closes, then detach all - // event listeners. - // - listeners.onOutgoingClose = function () { - proxySocket.destroy(); - detach(); - } - - proxySocket.on('end', listeners.onIncomingClose); - proxySocket.on('close', listeners.onIncomingClose); - reverseProxy.incoming.socket.on('end', listeners.onOutgoingClose); - reverseProxy.incoming.socket.on('close', listeners.onOutgoingClose); - } - - function getPort (port) { - port = port || 80; - return port - 80 === 0 ? '' : ':' + port; - } - - // - // Get the protocol, and host for this request and create an instance - // of `http.Agent` or `https.Agent` from the pool managed by `node-http-proxy`. - // - var agent = this.target.agent, - protocolName = this.target.https ? 'https' : 'http', - portUri = getPort(this.source.port), - remoteHost = this.target.host + portUri; - - // - // Change headers (if requested). - // - if (this.changeOrigin) { - req.headers.host = remoteHost; - req.headers.origin = protocolName + '://' + remoteHost; - } - - // - // Make the outgoing WebSocket request - // - outgoing.host = this.target.host; - outgoing.port = this.target.port; - outgoing.agent = agent; - outgoing.method = 'GET'; - outgoing.path = req.url; - outgoing.headers = req.headers; - outgoing.agent = agent; - - var reverseProxy = this.target.protocol.request(outgoing); - - // - // On any errors from the `reverseProxy` emit the - // `webSocketProxyError` and close the appropriate - // connections. - // - function proxyError (err) { - reverseProxy.destroy(); - - process.nextTick(function () { - // - // Destroy the incoming socket in the next tick, in case the error handler - // wants to write to it. - // - socket.destroy(); - }); - - self.emit('webSocketProxyError', err, req, socket, head); - } - - // - // Here we set the incoming `req`, `socket` and `head` data to the outgoing - // request so that we can reuse this data later on in the closure scope - // available to the `upgrade` event. This bookkeeping is not tracked anywhere - // in nodejs core and is **very** specific to proxying WebSockets. - // - reverseProxy.incoming = { - request: req, - socket: socket, - head: head - }; - - // - // Here we set the handshake `headers` and `statusCode` data to the outgoing - // request so that we can reuse this data later. - // - reverseProxy.handshake = { - headers: {}, - statusCode: null, - } - - // - // If the agent for this particular `host` and `port` combination - // is not already listening for the `upgrade` event, then do so once. - // This will force us not to disconnect. - // - // In addition, it's important to note the closure scope here. Since - // there is no mapping of the socket to the request bound to it. - // - reverseProxy.on('upgrade', function (res, remoteSocket, head) { - // - // Prepare handshake response 'headers' and 'statusCode'. - // - reverseProxy.handshake = { - headers: res.headers, - statusCode: res.statusCode, - } - - // - // Prepare the socket for the reverseProxy request and begin to - // stream data between the two sockets. Here it is important to - // note that `remoteSocket._httpMessage === reverseProxy`. - // - _socket(remoteSocket, true); - onUpgrade(remoteSocket._httpMessage, remoteSocket); - }); - - // - // If the reverseProxy connection has an underlying socket, - // then execute the WebSocket handshake. - // - reverseProxy.once('socket', function (revSocket) { - revSocket.on('data', function handshake (data) { - // Set empty headers - var headers = ''; - - // - // If the handshake statusCode 101, concat headers. - // - if (reverseProxy.handshake.statusCode && reverseProxy.handshake.statusCode == 101) { - headers = [ - 'HTTP/1.1 101 Switching Protocols', - 'Upgrade: websocket', - 'Connection: Upgrade', - 'Sec-WebSocket-Accept: ' + reverseProxy.handshake.headers['sec-websocket-accept'] - ]; - - headers = headers.concat('', '').join('\r\n'); - } - - // - // Ok, kind of harmfull part of code. Socket.IO sends a hash - // at the end of handshake if protocol === 76, but we need - // to replace 'host' and 'origin' in response so we split - // data to printable data and to non-printable. (Non-printable - // will come after double-CRLF). - // - var sdata = data.toString(); - - // Get the Printable data - sdata = sdata.substr(0, sdata.search(CRLF + CRLF)); - - // Get the Non-Printable data - data = data.slice(Buffer.byteLength(sdata), data.length); - - if (self.source.https && !self.target.https) { - // - // If the proxy server is running HTTPS but the client is running - // HTTP then replace `ws` with `wss` in the data sent back to the client. - // - sdata = sdata.replace('ws:', 'wss:'); - } - - try { - // - // Write the printable and non-printable data to the socket - // from the original incoming request. - // - self.emit('websocket:handshake', req, socket, head, sdata, data); - // add headers to the socket - socket.write(headers + sdata); - var flushed = socket.write(data); - if (!flushed) { - revSocket.pause(); - socket.once('drain', function () { - try { revSocket.resume() } - catch (er) { console.error("reverseProxy.socket.resume error: %s", er.message) } - }); - - // - // Force the `drain` event in 100ms if it hasn't - // happened on its own. - // - setTimeout(function () { - socket.emit('drain'); - }, 100); - } - } - catch (ex) { - // - // Remove data listener on socket error because the - // 'handshake' has failed. - // - revSocket.removeListener('data', handshake); - return proxyError(ex); - } - - // - // Remove data listener now that the 'handshake' is complete - // - revSocket.removeListener('data', handshake); - }); - }); - - // - // Handle 'error' events from the `reverseProxy`. - // - reverseProxy.on('error', proxyError); - - // - // Handle 'error' events from the `req` (e.g. `Parse Error`). - // - req.on('error', proxyError); - - try { - // - // Attempt to write the upgrade-head to the reverseProxy - // request. This is small, and there's only ever one of - // it; no need for pause/resume. - // - // XXX This is very wrong and should be fixed in node's core - // - reverseProxy.write(head); - if (head && head.length === 0) { - reverseProxy._send(''); - } - } - catch (ex) { - return proxyError(ex); - } - - // - // If we have been passed buffered data, resume it. - // - if (buffer) { - return !errState - ? buffer.resume() - : buffer.destroy(); - } -}; - -// -// ### function close() -// Closes all sockets associated with the Agents -// belonging to this instance. -// -HttpProxy.prototype.close = function () { - [this.forward, this.target].forEach(function (proxy) { - if (proxy && proxy.agent) { - for (var host in proxy.agent.sockets) { - proxy.agent.sockets[host].forEach(function (socket) { - socket.end(); - }); - } - } - }); -}; - -// -// ### @private function _forwardRequest (req) -// #### @req {ServerRequest} Incoming HTTP Request to proxy. -// Forwards the specified `req` to the location specified -// by `this.forward` ignoring errors and the subsequent response. -// -HttpProxy.prototype._forwardRequest = function (req) { - var self = this, - outgoing = new(this.forward.base), - forwardProxy; - - // - // Setup outgoing proxy with relevant properties. - // - outgoing.host = this.forward.host; - outgoing.port = this.forward.port, - outgoing.agent = this.forward.agent; - outgoing.method = req.method; - outgoing.path = req.url; - outgoing.headers = req.headers; - - // - // Open new HTTP request to internal resource with will - // act as a reverse proxy pass. - // - forwardProxy = this.forward.protocol.request(outgoing, function (response) { - // - // Ignore the response from the forward proxy since this is a 'fire-and-forget' proxy. - // Remark (indexzero): We will eventually emit a 'forward' event here for performance tuning. - // - }); - - // - // Add a listener for the connection timeout event. - // - // Remark: Ignoring this error in the event - // forward target doesn't exist. - // - forwardProxy.once('error', function (err) { }); - - // - // Chunk the client request body as chunks from - // the proxied request come in - // - req.on('data', function (chunk) { - var flushed = forwardProxy.write(chunk); - if (!flushed) { - req.pause(); - forwardProxy.once('drain', function () { - try { req.resume() } - catch (er) { console.error("req.resume error: %s", er.message) } - }); - - // - // Force the `drain` event in 100ms if it hasn't - // happened on its own. - // - setTimeout(function () { - forwardProxy.emit('drain'); - }, 100); - } - }); - - // - // At the end of the client request, we are going to - // stop the proxied request - // - req.on('end', function () { - forwardProxy.end(); - }); -}; - -function getPortFromHostHeader(req) { - var match; - if ((match = extractPort.exec(req.headers.host))) { - return parseInt(match[1]); - } - - return getProto(req) === 'https' ? 443 : 80; -} - -function getProto(req) { - return req.isSpdy ? 'https' : (req.connection.pair ? 'https' : 'http'); -} diff --git a/lib/node-http-proxy/proxy-table.js b/lib/node-http-proxy/proxy-table.js deleted file mode 100644 index 320396fe8..000000000 --- a/lib/node-http-proxy/proxy-table.js +++ /dev/null @@ -1,282 +0,0 @@ -/* - node-http-proxy.js: Lookup table for proxy targets in node.js - - Copyright (c) 2010 Charlie Robbins - - 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'), - events = require('events'), - fs = require('fs'), - url = require('url'); - -// -// ### function ProxyTable (router, silent) -// #### @router {Object} Object containing the host based routes -// #### @silent {Boolean} Value indicating whether we should suppress logs -// #### @hostnameOnly {Boolean} Value indicating if we should route based on __hostname string only__ -// #### @pathnameOnly {Boolean} Value indicating if we should route based on only the pathname. __This causes hostnames to be ignored.__. Using this along with hostnameOnly wont work at all. -// Constructor function for the ProxyTable responsible for getting -// locations of proxy targets based on ServerRequest headers; specifically -// the HTTP host header. -// -var ProxyTable = exports.ProxyTable = function (options) { - events.EventEmitter.call(this); - - this.silent = options.silent || options.silent !== true; - this.target = options.target || {}; - this.pathnameOnly = options.pathnameOnly === true; - this.hostnameOnly = options.hostnameOnly === true; - - if (typeof options.router === 'object') { - // - // If we are passed an object literal setup - // the routes with RegExps from the router - // - this.setRoutes(options.router); - } - else if (typeof options.router === 'string') { - // - // If we are passed a string then assume it is a - // file path, parse that file and watch it for changes - // - var self = this; - this.routeFile = options.router; - this.setRoutes(JSON.parse(fs.readFileSync(options.router)).router); - - fs.watchFile(this.routeFile, function () { - fs.readFile(self.routeFile, function (err, data) { - if (err) { - self.emit('error', err); - } - - self.setRoutes(JSON.parse(data).router); - self.emit('routes', self.hostnameOnly === false ? self.routes : self.router); - }); - }); - } - else { - throw new Error('Cannot parse router with unknown type: ' + typeof router); - } -}; - -// -// Inherit from `events.EventEmitter` -// -util.inherits(ProxyTable, events.EventEmitter); - -// -// ### function addRoute (route, target) -// #### @route {String} String containing route coming in -// #### @target {String} String containing the target -// Adds a host-based route to this instance. -// -ProxyTable.prototype.addRoute = function (route, target) { - if (!this.router) { - throw new Error('Cannot update ProxyTable routes without router.'); - } - - this.router[route] = target; - this.setRoutes(this.router); -}; - -// -// ### function removeRoute (route) -// #### @route {String} String containing route to remove -// Removes a host-based route from this instance. -// -ProxyTable.prototype.removeRoute = function (route) { - if (!this.router) { - throw new Error('Cannot update ProxyTable routes without router.'); - } - - delete this.router[route]; - this.setRoutes(this.router); -}; - -// -// ### function setRoutes (router) -// #### @router {Object} Object containing the host based routes -// Sets the host-based routes to be used by this instance. -// -ProxyTable.prototype.setRoutes = function (router) { - if (!router) { - throw new Error('Cannot update ProxyTable routes without router.'); - } - - var self = this; - this.router = router; - - if (this.hostnameOnly === false) { - this.routes = []; - - Object.keys(router).forEach(function (path) { - if (!/http[s]?/.test(router[path])) { - router[path] = (self.target.https ? 'https://' : 'http://') - + router[path]; - } - - var target = url.parse(router[path]), - defaultPort = self.target.https ? 443 : 80; - - // - // Setup a robust lookup table for the route: - // - // { - // source: { - // regexp: /^foo.com/i, - // sref: 'foo.com', - // url: { - // protocol: 'http:', - // slashes: true, - // host: 'foo.com', - // hostname: 'foo.com', - // href: 'http://foo.com/', - // pathname: '/', - // path: '/' - // } - // }, - // { - // target: { - // sref: '127.0.0.1:8000/', - // url: { - // protocol: 'http:', - // slashes: true, - // host: '127.0.0.1:8000', - // hostname: '127.0.0.1', - // href: 'http://127.0.0.1:8000/', - // pathname: '/', - // path: '/' - // } - // }, - // - self.routes.push({ - source: { - regexp: new RegExp('^' + path, 'i'), - sref: path, - url: url.parse('http://' + path) - }, - target: { - sref: target.hostname + ':' + (target.port || defaultPort) + target.path, - url: target - } - }); - }); - } -}; - -// -// ### function getProxyLocation (req) -// #### @req {ServerRequest} The incoming server request to get proxy information about. -// Returns the proxy location based on the HTTP Headers in the ServerRequest `req` -// available to this instance. -// -ProxyTable.prototype.getProxyLocation = function (req) { - if (!req || !req.headers || !req.headers.host) { - return null; - } - - var targetHost = req.headers.host.split(':')[0]; - if (this.hostnameOnly === true) { - var target = targetHost; - if (this.router.hasOwnProperty(target)) { - var location = this.router[target].split(':'), - host = location[0], - port = location.length === 1 ? 80 : location[1]; - - return { - port: port, - host: host - }; - } - } - else if (this.pathnameOnly === true) { - var target = req.url; - for (var i in this.routes) { - var route = this.routes[i]; - // - // If we are matching pathname only, we remove the matched pattern. - // - // IE /wiki/heartbeat - // is redirected to - // /heartbeat - // - // for the route "/wiki" : "127.0.0.1:8020" - // - if (target.match(route.source.regexp)) { - req.url = url.format(target.replace(route.source.regexp, '')); - return { - protocol: route.target.url.protocol.replace(':', ''), - host: route.target.url.hostname, - port: route.target.url.port - || (this.target.https ? 443 : 80) - }; - } - } - - } - else { - var target = targetHost + req.url; - for (var i in this.routes) { - var route = this.routes[i]; - if (target.match(route.source.regexp)) { - // - // Attempt to perform any path replacement for differences - // between the source path and the target path. This replaces the - // path's part of the URL to the target's part of the URL. - // - // 1. Parse the request URL - // 2. Replace any portions of the source path with the target path - // 3. Set the request URL to the formatted URL with replacements. - // - var parsed = url.parse(req.url); - - parsed.pathname = parsed.pathname.replace( - route.source.url.pathname, - route.target.url.pathname - ); - - req.url = url.format(parsed); - - return { - protocol: route.target.url.protocol.replace(':', ''), - host: route.target.url.hostname, - port: route.target.url.port - || (this.target.https ? 443 : 80) - }; - } - } - } - - return null; -}; - -// -// ### close function () -// Cleans up the event listeneners maintained -// by this instance. -// -ProxyTable.prototype.close = function () { - if (typeof this.routeFile === 'string') { - fs.unwatchFile(this.routeFile); - } -}; diff --git a/lib/node-http-proxy/routing-proxy.js b/lib/node-http-proxy/routing-proxy.js deleted file mode 100644 index b294fb15d..000000000 --- a/lib/node-http-proxy/routing-proxy.js +++ /dev/null @@ -1,322 +0,0 @@ -/* - * routing-proxy.js: A routing proxy consuming a RoutingTable and multiple HttpProxy instances - * - * (C) 2011 Nodejitsu Inc. - * MIT LICENCE - * - */ - -var events = require('events'), - utile = require('utile'), - HttpProxy = require('./http-proxy').HttpProxy, - ProxyTable = require('./proxy-table').ProxyTable; - -// -// ### function RoutingProxy (options) -// #### @options {Object} Options for this instance -// Constructor function for the RoutingProxy object, a higher level -// reverse proxy Object which can proxy to multiple hosts and also interface -// easily with a RoutingTable instance. -// -var RoutingProxy = exports.RoutingProxy = function (options) { - events.EventEmitter.call(this); - - var self = this; - options = options || {}; - - if (options.router) { - this.proxyTable = new ProxyTable(options); - this.proxyTable.on('routes', function (routes) { - self.emit('routes', routes); - }); - } - - // - // Create a set of `HttpProxy` objects to be used later on calls - // to `.proxyRequest()` and `.proxyWebSocketRequest()`. - // - this.proxies = {}; - - // - // Setup default target options (such as `https`). - // - this.target = {}; - this.target.https = options.target && options.target.https; - this.target.maxSockets = options.target && options.target.maxSockets; - - // - // Setup other default options to be used for instances of - // `HttpProxy` created by this `RoutingProxy` instance. - // - this.source = options.source || { host: 'localhost', port: 8000 }; - this.https = this.source.https || options.https; - this.enable = options.enable; - this.forward = options.forward; - this.changeOrigin = options.changeOrigin || false; - - // - // Listen for 'newListener' events so that we can bind 'proxyError' - // listeners to each HttpProxy's 'proxyError' event. - // - this.on('newListener', function (evt) { - if (evt === 'proxyError' || evt === 'webSocketProxyError') { - Object.keys(self.proxies).forEach(function (key) { - self.proxies[key].on(evt, self.emit.bind(self, evt)); - }); - } - }); -}; - - -// -// Inherit from `events.EventEmitter`. -// -utile.inherits(RoutingProxy, events.EventEmitter); - -// -// ### function add (options) -// #### @options {Object} Options for the `HttpProxy` to add. -// Adds a new instance of `HttpProxy` to this `RoutingProxy` instance -// for the specified `options.host` and `options.port`. -// -RoutingProxy.prototype.add = function (options) { - var self = this, - key = this._getKey(options); - - // - // TODO: Consume properties in `options` related to the `ProxyTable`. - // - options.target = options.target || {}; - options.target.host = options.target.host || options.host; - options.target.port = options.target.port || options.port; - options.target.socketPath = options.target.socketPath || options.socketPath; - options.target.https = this.target && this.target.https || - options.target && options.target.https; - options.target.maxSockets = this.target && this.target.maxSockets; - - // - // Setup options to pass-thru to the new `HttpProxy` instance - // for the specified `options.host` and `options.port` pair. - // - ['https', 'enable', 'forward', 'changeOrigin'].forEach(function (key) { - if (options[key] !== false && self[key]) { - options[key] = self[key]; - } - }); - - this.proxies[key] = new HttpProxy(options); - - if (this.listeners('proxyError').length > 0) { - this.proxies[key].on('proxyError', this.emit.bind(this, 'proxyError')); - } - - if (this.listeners('webSocketProxyError').length > 0) { - this.proxies[key].on('webSocketProxyError', this.emit.bind(this, 'webSocketProxyError')); - } - - [ - 'start', - 'forward', - 'end', - 'proxyResponse', - 'websocket:start', - 'websocket:end', - 'websocket:incoming', - 'websocket:outgoing' - ].forEach(function (event) { - this.proxies[key].on(event, this.emit.bind(this, event)); - }, this); -}; - -// -// ### function remove (options) -// #### @options {Object} Options mapping to the `HttpProxy` to remove. -// Removes an instance of `HttpProxy` from this `RoutingProxy` instance -// for the specified `options.host` and `options.port` (if they exist). -// -RoutingProxy.prototype.remove = function (options) { - var key = this._getKey(options), - proxy = this.proxies[key]; - - delete this.proxies[key]; - return proxy; -}; - -// -// ### function close() -// Cleans up any state left behind (sockets, timeouts, etc) -// associated with this instance. -// -RoutingProxy.prototype.close = function () { - var self = this; - - if (this.proxyTable) { - // - // Close the `RoutingTable` associated with - // this instance (if any). - // - this.proxyTable.close(); - } - - // - // Close all sockets for all `HttpProxy` object(s) - // associated with this instance. - // - Object.keys(this.proxies).forEach(function (key) { - self.proxies[key].close(); - }); -}; - -// -// ### function proxyRequest (req, res, [port, host, paused]) -// #### @req {ServerRequest} Incoming HTTP Request to proxy. -// #### @res {ServerResponse} Outgoing HTTP Request to write proxied data to. -// #### @options {Object} Options for the outgoing proxy request. -// -// options.port {number} Port to use on the proxy target host. -// options.host {string} Host of the proxy target. -// options.buffer {Object} Result from `httpProxy.buffer(req)` -// options.https {Object|boolean} Settings for https. -// -RoutingProxy.prototype.proxyRequest = function (req, res, options) { - options = options || {}; - - var location; - - // - // Check the proxy table for this instance to see if we need - // to get the proxy location for the request supplied. We will - // always ignore the proxyTable if an explicit `port` and `host` - // arguments are supplied to `proxyRequest`. - // - if (this.proxyTable && !options.host) { - location = this.proxyTable.getProxyLocation(req); - - // - // If no location is returned from the ProxyTable instance - // then respond with `404` since we do not have a valid proxy target. - // - if (!location) { - try { - if (!this.emit('notFound', req, res)) { - res.writeHead(404); - res.end(); - } - } - catch (er) { - console.error("res.writeHead/res.end error: %s", er.message); - } - - return; - } - - // - // When using the ProxyTable in conjunction with an HttpProxy instance - // only the following arguments are valid: - // - // * `proxy.proxyRequest(req, res, { host: 'localhost' })`: This will be skipped - // * `proxy.proxyRequest(req, res, { buffer: buffer })`: Buffer will get updated appropriately - // * `proxy.proxyRequest(req, res)`: Options will be assigned appropriately. - // - options.port = location.port; - options.host = location.host; - } - - var key = this._getKey(options), - proxy; - - if ((this.target && this.target.https) - || (location && location.protocol === 'https')) { - options.target = options.target || {}; - options.target.https = true; - } - - if (!this.proxies[key]) { - this.add(utile.clone(options)); - } - - proxy = this.proxies[key]; - proxy.proxyRequest(req, res, options.buffer); -}; - -// -// ### function proxyWebSocketRequest (req, socket, head, options) -// #### @req {ServerRequest} Websocket request to proxy. -// #### @socket {net.Socket} Socket for the underlying HTTP request -// #### @head {string} Headers for the Websocket request. -// #### @options {Object} Options to use when proxying this request. -// -// options.port {number} Port to use on the proxy target host. -// options.host {string} Host of the proxy target. -// options.buffer {Object} Result from `httpProxy.buffer(req)` -// options.https {Object|boolean} Settings for https. -// -RoutingProxy.prototype.proxyWebSocketRequest = function (req, socket, head, options) { - options = options || {}; - - var location, - proxy, - key; - - if (this.proxyTable && !options.host) { - location = this.proxyTable.getProxyLocation(req); - - if (!location) { - return socket.destroy(); - } - - options.port = location.port; - options.host = location.host; - } - - key = this._getKey(options); - - if (!this.proxies[key]) { - this.add(utile.clone(options)); - } - - proxy = this.proxies[key]; - proxy.proxyWebSocketRequest(req, socket, head, options.buffer); -}; - -// -// ### function addHost (host, target) -// #### @host {String} Host to add to proxyTable -// #### @target {String} Target to add to proxyTable -// Adds a host to proxyTable -// -RoutingProxy.prototype.addHost = function (host, target) { - if (this.proxyTable) { - this.proxyTable.addRoute(host, target); - } -}; - -// -// ### function removeHost (host) -// #### @host {String} Host to remove from proxyTable -// Removes a host to proxyTable -// -RoutingProxy.prototype.removeHost = function (host) { - if (this.proxyTable) { - this.proxyTable.removeRoute(host); - } -}; - -// -// ### @private function _getKey (options) -// #### @options {Object} Options to extract the key from -// Ensures that the appropriate options are present in the `options` -// provided and responds with a string key representing the `host`, `port` -// combination contained within. -// -RoutingProxy.prototype._getKey = function (options) { - if (!options || ((!options.host || !options.port) - && (!options.target || !options.target.host || !options.target.port))) { - throw new Error('options.host and options.port or options.target are required.'); - } - - return [ - options.host || options.target.host, - options.port || options.target.port - ].join(':'); -}; diff --git a/package.json b/package.json index cd3ff0add..5c7c3a1eb 100644 --- a/package.json +++ b/package.json @@ -1,47 +1,40 @@ { - "name": "http-proxy", - "version": "0.10.4", - "description": "A full-featured http reverse proxy for node.js", + "name" : "http-proxy", + "version" : "1.0.0", + "description" : "HTTP proxying for the masses", "author": "Nodejitsu Inc. ", - "maintainers": [ - "indexzero ", - "AvianFlu " + "maintainers" : [ + "yawnt ", + "indexzero " ], - "repository": { - "type": "git", - "url": "http://github.com/nodejitsu/node-http-proxy.git" - }, - "keywords": [ - "reverse", - "proxy", - "http" - ], - "dependencies": { - "colors": "0.x.x", - "optimist": "0.6.x", - "pkginfo": "0.3.x", - "utile": "~0.2.1" + + "main" : "index.js", + + "dependencies" : { + "eventemitter3" : "*" }, "devDependencies": { - "request": "2.14.x", - "vows": "0.7.x", - "async": "0.2.x", - "socket.io": "0.9.11", - "socket.io-client": "0.9.11", - "ws": "0.4.23" + "mocha" : "*", + "expect.js" : "*", + "dox" : "*", + "coveralls" : "*", + "mocha-lcov-reporter": "*", + "blanket" : "*", + "ws" : "*", + "socket.io" : "*", + "socket.io-client" : "*", + "async" : "*" }, - "main": "./lib/node-http-proxy", - "bin": { - "node-http-proxy": "./bin/node-http-proxy" + "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" }, - "scripts": { - "test": "npm run-script test-http && npm run-script test-https && npm run-script test-core", - "test-http": "vows --spec && vows --spec --target=https", - "test-https": "vows --spec --proxy=https && vows --spec --proxy=https --target=https", - "test-core": "test/core/run" + + "engines" : { + "node" : ">=0.10.0" }, - "engines": { - "node": ">= 0.6.6" - } -} + "license" : "MIT" +} diff --git a/test/core/README.md b/test/core/README.md deleted file mode 100644 index 152e5c668..000000000 --- a/test/core/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# `test/core` - -`test/core` directory is a place where tests from node.js core go. They are -here to ensure that node-http-proxy works just fine with all kinds of -different situations, which are covered in core tests, but are not covered in -our tests. - -All these tests require little modifications to make them test node-http-proxy, -but we try to keep them as vanilla as possible. - diff --git a/test/core/common.js b/test/core/common.js deleted file mode 100644 index 3f584a543..000000000 --- a/test/core/common.js +++ /dev/null @@ -1,190 +0,0 @@ -// Copyright Joyent, Inc. and other Node contributors. -// -// 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 path = require('path'); -var assert = require('assert'); - -exports.testDir = path.dirname(__filename); -exports.fixturesDir = path.join(exports.testDir, 'fixtures'); -exports.libDir = path.join(exports.testDir, '../lib'); -exports.tmpDir = path.join(exports.testDir, 'tmp'); -exports.PORT = 12346; -exports.PROXY_PORT = 1234567; - -if (process.platform === 'win32') { - exports.PIPE = '\\\\.\\pipe\\libuv-test'; -} else { - exports.PIPE = exports.tmpDir + '/test.sock'; -} - -var util = require('util'); -for (var i in util) exports[i] = util[i]; -//for (var i in exports) global[i] = exports[i]; - -function protoCtrChain(o) { - var result = []; - for (; o; o = o.__proto__) { result.push(o.constructor); } - return result.join(); -} - -exports.indirectInstanceOf = function (obj, cls) { - if (obj instanceof cls) { return true; } - var clsChain = protoCtrChain(cls.prototype); - var objChain = protoCtrChain(obj); - return objChain.slice(-clsChain.length) === clsChain; -}; - - -exports.ddCommand = function (filename, kilobytes) { - if (process.platform === 'win32') { - var p = path.resolve(exports.fixturesDir, 'create-file.js'); - return '"' + process.argv[0] + '" "' + p + '" "' + - filename + '" ' + (kilobytes * 1024); - } else { - return 'dd if=/dev/zero of="' + filename + '" bs=1024 count=' + kilobytes; - } -}; - - -exports.spawnPwd = function (options) { - var spawn = require('child_process').spawn; - - if (process.platform === 'win32') { - return spawn('cmd.exe', ['/c', 'cd'], options); - } else { - return spawn('pwd', [], options); - } -}; - - -// Turn this off if the test should not check for global leaks. -exports.globalCheck = true; - -process.on('exit', function () { - if (!exports.globalCheck) return; - var knownGlobals = [setTimeout, - setInterval, - clearTimeout, - clearInterval, - console, - Buffer, - process, - global]; - - if (global.setImmediate) { - knownGlobals.push(setImmediate); - knownGlobals.push(clearImmediate); - } - - if (global.errno) { - knownGlobals.push(errno); - } - - if (global.gc) { - knownGlobals.push(gc); - } - - if (global.DTRACE_HTTP_SERVER_RESPONSE) { - knownGlobals.push(DTRACE_HTTP_SERVER_RESPONSE); - knownGlobals.push(DTRACE_HTTP_SERVER_REQUEST); - knownGlobals.push(DTRACE_HTTP_CLIENT_RESPONSE); - knownGlobals.push(DTRACE_HTTP_CLIENT_REQUEST); - knownGlobals.push(DTRACE_NET_STREAM_END); - knownGlobals.push(DTRACE_NET_SERVER_CONNECTION); - knownGlobals.push(DTRACE_NET_SOCKET_READ); - knownGlobals.push(DTRACE_NET_SOCKET_WRITE); - } - - if (global.ArrayBuffer) { - knownGlobals.push(ArrayBuffer); - knownGlobals.push(Int8Array); - knownGlobals.push(Uint8Array); - knownGlobals.push(Int16Array); - knownGlobals.push(Uint16Array); - knownGlobals.push(Int32Array); - knownGlobals.push(Uint32Array); - knownGlobals.push(Float32Array); - knownGlobals.push(Float64Array); - knownGlobals.push(DataView); - - if (global.Uint8ClampedArray) { - knownGlobals.push(Uint8ClampedArray); - } - } - - for (var x in global) { - var found = false; - - for (var y in knownGlobals) { - if (global[x] === knownGlobals[y]) { - found = true; - break; - } - } - - if (!found) { - console.error('Unknown global: %s', x); - assert.ok(false, 'Unknown global found'); - } - } -}); - - -var mustCallChecks = []; - - -function runCallChecks() { - var failed = mustCallChecks.filter(function (context) { - return context.actual !== context.expected; - }); - - failed.forEach(function (context) { - console.log('Mismatched %s function calls. Expected %d, actual %d.', - context.name, - context.expected, - context.actual); - console.log(context.stack.split('\n').slice(2).join('\n')); - }); - - if (failed.length) process.exit(1); -} - - -exports.mustCall = function (fn, expected) { - if (typeof expected !== 'number') expected = 1; - - var context = { - expected: expected, - actual: 0, - stack: (new Error).stack, - name: fn.name || '' - }; - - // add the exit listener only once to avoid listener leak warnings - if (mustCallChecks.length === 0) process.on('exit', runCallChecks); - - mustCallChecks.push(context); - - return function () { - context.actual++; - return fn.apply(this, arguments); - }; -}; diff --git a/test/core/pummel/test-http-upload-timeout.js b/test/core/pummel/test-http-upload-timeout.js deleted file mode 100644 index 74b458f8b..000000000 --- a/test/core/pummel/test-http-upload-timeout.js +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright Joyent, Inc. and other Node contributors. -// -// 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. - -// This tests setTimeout() by having multiple clients connecting and sending -// data in random intervals. Clients are also randomly disconnecting until there -// are no more clients left. If no false timeout occurs, this test has passed. -var common = require('../common'), - assert = require('assert'), - http = require('http'), - server = http.createServer(), - connections = 0; - -server.on('request', function (req, res) { - req.socket.setTimeout(1000); - req.socket.on('timeout', function () { - throw new Error('Unexpected timeout'); - }); - req.on('end', function () { - connections--; - res.writeHead(200); - res.end('done\n'); - if (connections == 0) { - server.close(); - } - }); -}); - -server.listen(common.PORT, '127.0.0.1', function () { - for (var i = 0; i < 10; i++) { - connections++; - - setTimeout(function () { - var request = http.request({ - port: common.PROXY_PORT, - method: 'POST', - path: '/' - }); - - function ping() { - var nextPing = (Math.random() * 900).toFixed(); - if (nextPing > 600) { - request.end(); - return; - } - request.write('ping'); - setTimeout(ping, nextPing); - } - ping(); - }, i * 50); - } -}); diff --git a/test/core/run b/test/core/run deleted file mode 100755 index adec53bab..000000000 --- a/test/core/run +++ /dev/null @@ -1,90 +0,0 @@ -#!/usr/bin/env node -/* - run.js: test runner for core tests - - Copyright (c) 2011 Nodejitsu - - 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 fs = require('fs'), - path = require('path'), - spawn = require('child_process').spawn, - async = require('async'), - colors = require('colors'), - optimist = require('optimist'); - -optimist.argv.color && (colors.mode = optimist.argv.color); - -var testTimeout = 15000; -var results = {}; - -function runTest(test, callback) { - var child = spawn(path.join(__dirname, 'run-single'), [ test ]); - - var killTimeout = setTimeout(function () { - child.kill(); - console.log(' ' + path.basename(test).yellow + ' timed out'.red); - }, testTimeout); - - child.on('exit', function (exitCode) { - clearTimeout(killTimeout); - - console.log(' ' + ((exitCode) ? '✘'.red : '✔'.green) + ' ' + - path.basename(test) + - (exitCode ? (' (exit code: ' + exitCode + ')') : '')); - results[test] = { exitCode: exitCode }; - callback(); - // - // We don't want tests to be stopped after first failure, and that's what - // async does when it receives truthy value in callback. - // - }); -}; - -var tests = process.argv.slice(2).filter(function (test) { - return test.substr(0, 2) != '--'; -}); - -if (!tests.length) { - var pathPrefix = path.join(__dirname, 'simple'); - tests = fs.readdirSync(pathPrefix).map(function (test) { - return path.join(pathPrefix, test); - }); - // - // We only run simple tests by default. - // -} - -console.log('Running tests:'.bold); -async.forEachSeries(tests, runTest, function () { - var failed = [], ok = []; - for (var test in results) { - (results[test].exitCode != 0 ? failed : ok).push(test); - } - - console.log('\nSummary:'.bold); - console.log((' ' + ok.length + '\tpassed tests').green); - console.log((' ' + failed.length + '\tfailed tests').red); -}); - -// vim:filetype=javascript - diff --git a/test/core/run-single b/test/core/run-single deleted file mode 100755 index ae53588af..000000000 --- a/test/core/run-single +++ /dev/null @@ -1,70 +0,0 @@ -#!/usr/bin/env node -/* - run-single.js: test runner for core tests - - Copyright (c) 2011 Nodejitsu - - 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. - -*/ - -// -// Basic idea behind core test runner is to modify core tests as little as -// possible. That's why we start up node-http-proxy here instead of embeeding -// this code in tests. -// -// In most cases only modification to core tests you'll need is changing port -// of http client to common.PROXY_PORT. -// - -var path = require('path'), - spawn = require('child_process').spawn, - httpProxy = require('../../'), - common = require('./common'); - -var test = process.argv[2], - testProcess; - -if (!test) { - return console.error('Need test to run'); -} - -console.log('Running test ' + test); - -var proxy = httpProxy.createServer(common.PORT, 'localhost'); -proxy.listen(common.PROXY_PORT); - -proxy.on('listening', function () { - console.log('Proxy server listening on ' + common.PROXY_PORT); - testProcess = spawn(process.argv[0], [ process.argv[2] ]); - testProcess.stdout.pipe(process.stdout); - testProcess.stderr.pipe(process.stderr); - - testProcess.on('exit', function (code) { - process.exit(code); - }); -}); - -process.on('SIGTERM', function () { - testProcess.kill(); - process.exit(1); -}); - -// vim:filetype=javascript diff --git a/test/core/simple/test-http-chunked.js b/test/core/simple/test-http-chunked.js deleted file mode 100644 index b1cbf0dbb..000000000 --- a/test/core/simple/test-http-chunked.js +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright Joyent, Inc. and other Node contributors. -// -// 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 common = require('../common'); -var assert = require('assert'); -var http = require('http'); - -var UTF8_STRING = '南越国是前203年至前111年存在于岭南地区的一个国家,' + - '国都位于番禺,疆域包括今天中国的广东、广西两省区的大部份地区,福建省、湖南、' + - '贵州、云南的一小部份地区和越南的北部。南越国是秦朝灭亡后,' + - '由南海郡尉赵佗于前203年起兵兼并桂林郡和象郡后建立。前196年和前179年,' + - '南越国曾先后两次名义上臣属于西汉,成为西汉的“外臣”。前112年,' + - '南越国末代君主赵建德与西汉发生战争,被汉武帝于前111年所灭。' + - '南越国共存在93年,历经五代君主。南越国是岭南地区的第一个有记载的政权国家,' + - '采用封建制和郡县制并存的制度,它的建立保证了秦末乱世岭南地区社会秩序的稳定,' + - '有效的改善了岭南地区落后的政治、经济现状。'; - -var server = http.createServer(function (req, res) { - res.writeHead(200, {'Content-Type': 'text/plain; charset=utf8'}); - res.end(UTF8_STRING, 'utf8'); -}); -server.listen(common.PORT, function () { - var data = ''; - var get = http.get({ - path: '/', - host: 'localhost', - port: common.PROXY_PORT - }, function (x) { - x.setEncoding('utf8'); - x.on('data', function (c) {data += c}); - x.on('error', function (e) { - throw e; - }); - x.on('end', function () { - assert.equal('string', typeof data); - console.log('here is the response:'); - assert.equal(UTF8_STRING, data); - console.log(data); - server.close(); - }); - }); - get.on('error', function (e) {throw e}); - get.end(); - -}); diff --git a/test/core/simple/test-http-client-abort.js b/test/core/simple/test-http-client-abort.js deleted file mode 100644 index 78d0a6f93..000000000 --- a/test/core/simple/test-http-client-abort.js +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright Joyent, Inc. and other Node contributors. -// -// 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 common = require('../common'); -var assert = require('assert'); -var http = require('http'); - -var clientAborts = 0; - -var server = http.Server(function (req, res) { - console.log('Got connection'); - res.writeHead(200); - res.write('Working on it...'); - - // I would expect an error event from req or res that the client aborted - // before completing the HTTP request / response cycle, or maybe a new - // event like "aborted" or something. - req.on('aborted', function () { - clientAborts++; - console.log('Got abort ' + clientAborts); - if (clientAborts === N) { - console.log('All aborts detected, you win.'); - server.close(); - } - }); - - // since there is already clientError, maybe that would be appropriate, - // since "error" is magical - req.on('clientError', function () { - console.log('Got clientError'); - }); -}); - -var responses = 0; -var N = http.Agent.defaultMaxSockets - 1; -var requests = []; - -server.listen(common.PORT, function () { - console.log('Server listening.'); - - for (var i = 0; i < N; i++) { - console.log('Making client ' + i); - var options = { port: common.PROXY_PORT, path: '/?id=' + i }; - var req = http.get(options, function (res) { - console.log('Client response code ' + res.statusCode); - - if (++responses == N) { - console.log('All clients connected, destroying.'); - requests.forEach(function (outReq) { - console.log('abort'); - outReq.abort(); - }); - } - }); - - requests.push(req); - } -}); - -process.on('exit', function () { - assert.equal(N, clientAborts); -}); diff --git a/test/core/simple/test-http-client-abort2.js b/test/core/simple/test-http-client-abort2.js deleted file mode 100644 index eef05fb46..000000000 --- a/test/core/simple/test-http-client-abort2.js +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright Joyent, Inc. and other Node contributors. -// -// 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. - -// libuv-broken - - -var common = require('../common'); -var assert = require('assert'); -var http = require('http'); - -var server = http.createServer(function (req, res) { - res.end('Hello'); -}); - -server.listen(common.PORT, function () { - var req = http.get({port: common.PROXY_PORT}, function (res) { - res.on('data', function (data) { - req.abort(); - server.close(); - }); - }); -}); - diff --git a/test/core/simple/test-http-client-upload-buf.js b/test/core/simple/test-http-client-upload-buf.js deleted file mode 100644 index 3b8e9abce..000000000 --- a/test/core/simple/test-http-client-upload-buf.js +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright Joyent, Inc. and other Node contributors. -// -// 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 common = require('../common'); -var assert = require('assert'); -var http = require('http'); - -var N = 1024; -var bytesRecieved = 0; -var server_req_complete = false; -var client_res_complete = false; - -var server = http.createServer(function (req, res) { - assert.equal('POST', req.method); - - req.on('data', function (chunk) { - bytesRecieved += chunk.length; - }); - - req.on('end', function () { - server_req_complete = true; - console.log('request complete from server'); - res.writeHead(200, {'Content-Type': 'text/plain'}); - res.write('hello\n'); - res.end(); - }); -}); -server.listen(common.PORT); - -server.on('listening', function () { - var req = http.request({ - port: common.PROXY_PORT, - method: 'POST', - path: '/' - }, function (res) { - res.setEncoding('utf8'); - res.on('data', function (chunk) { - console.log(chunk); - }); - res.on('end', function () { - client_res_complete = true; - server.close(); - }); - }); - - req.write(new Buffer(N)); - req.end(); - - common.error('client finished sending request'); -}); - -process.on('exit', function () { - assert.equal(N, bytesRecieved); - assert.equal(true, server_req_complete); - assert.equal(true, client_res_complete); -}); diff --git a/test/core/simple/test-http-client-upload.js b/test/core/simple/test-http-client-upload.js deleted file mode 100644 index fef706f2a..000000000 --- a/test/core/simple/test-http-client-upload.js +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright Joyent, Inc. and other Node contributors. -// -// 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 common = require('../common'); -var assert = require('assert'); -var http = require('http'); - -var sent_body = ''; -var server_req_complete = false; -var client_res_complete = false; - -var server = http.createServer(function (req, res) { - assert.equal('POST', req.method); - req.setEncoding('utf8'); - - req.on('data', function (chunk) { - console.log('server got: ' + JSON.stringify(chunk)); - sent_body += chunk; - }); - - req.on('end', function () { - server_req_complete = true; - console.log('request complete from server'); - res.writeHead(200, {'Content-Type': 'text/plain'}); - res.write('hello\n'); - res.end(); - }); -}); -server.listen(common.PORT); - -server.on('listening', function () { - var req = http.request({ - port: common.PROXY_PORT, - method: 'POST', - path: '/' - }, function (res) { - res.setEncoding('utf8'); - res.on('data', function (chunk) { - console.log(chunk); - }); - res.on('end', function () { - client_res_complete = true; - server.close(); - }); - }); - - req.write('1\n'); - req.write('2\n'); - req.write('3\n'); - req.end(); - - common.error('client finished sending request'); -}); - -process.on('exit', function () { - assert.equal('1\n2\n3\n', sent_body); - assert.equal(true, server_req_complete); - assert.equal(true, client_res_complete); -}); diff --git a/test/core/simple/test-http-contentLength0.js b/test/core/simple/test-http-contentLength0.js deleted file mode 100644 index abc3747e1..000000000 --- a/test/core/simple/test-http-contentLength0.js +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright Joyent, Inc. and other Node contributors. -// -// 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 common = require('../common'); -var http = require('http'); - -// Simple test of Node's HTTP Client choking on a response -// with a 'Content-Length: 0 ' response header. -// I.E. a space character after the 'Content-Length' throws an `error` event. - - -var s = http.createServer(function (req, res) { - res.writeHead(200, {'Content-Length': '0 '}); - res.end(); -}); -s.listen(common.PORT, function () { - - var request = http.request({ port: common.PROXY_PORT }, function (response) { - console.log('STATUS: ' + response.statusCode); - s.close(); - }); - - request.end(); -}); diff --git a/test/core/simple/test-http-eof-on-connect.js b/test/core/simple/test-http-eof-on-connect.js deleted file mode 100644 index d02ee6d16..000000000 --- a/test/core/simple/test-http-eof-on-connect.js +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright Joyent, Inc. and other Node contributors. -// -// 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 common = require('../common'); -var assert = require('assert'); -var net = require('net'); -var http = require('http'); - -// This is a regression test for http://github.com/ry/node/issues/#issue/44 -// It is separate from test-http-malformed-request.js because it is only -// reproduceable on the first packet on the first connection to a server. - -var server = http.createServer(function (req, res) {}); -server.listen(common.PORT); - -server.on('listening', function () { - net.createConnection(common.PROXY_PORT).on('connect', function () { - this.destroy(); - }).on('close', function () { - server.close(); - }); -}); diff --git a/test/core/simple/test-http-extra-response.js b/test/core/simple/test-http-extra-response.js deleted file mode 100644 index a3d27462f..000000000 --- a/test/core/simple/test-http-extra-response.js +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright Joyent, Inc. and other Node contributors. -// -// 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 common = require('../common'); -var assert = require('assert'); -var http = require('http'); -var net = require('net'); - -// If an HTTP server is broken and sends data after the end of the response, -// node should ignore it and drop the connection. -// Demos this bug: https://github.com/ry/node/issues/680 - -var body = 'hello world\r\n'; -var fullResponse = - 'HTTP/1.1 500 Internal Server Error\r\n' + - 'Content-Length: ' + body.length + '\r\n' + - 'Content-Type: text/plain\r\n' + - 'Date: Fri + 18 Feb 2011 06:22:45 GMT\r\n' + - 'Host: 10.20.149.2\r\n' + - 'Access-Control-Allow-Credentials: true\r\n' + - 'Server: badly broken/0.1 (OS NAME)\r\n' + - '\r\n' + - body; - -var gotResponse = false; - - -var server = net.createServer(function (socket) { - var postBody = ''; - - socket.setEncoding('utf8'); - - socket.on('data', function (chunk) { - postBody += chunk; - - if (postBody.indexOf('\r\n') > -1) { - socket.write(fullResponse); - // omg, I wrote the response twice, what a terrible HTTP server I am. - socket.end(fullResponse); - } - }); -}); - - -server.listen(common.PORT, function () { - http.get({ port: common.PROXY_PORT }, function (res) { - var buffer = ''; - console.log('Got res code: ' + res.statusCode); - - res.setEncoding('utf8'); - res.on('data', function (chunk) { - buffer += chunk; - }); - - res.on('end', function () { - console.log('Response ended, read ' + buffer.length + ' bytes'); - assert.equal(body, buffer); - server.close(); - gotResponse = true; - }); - }); -}); - - -process.on('exit', function () { - assert.ok(gotResponse); -}); - diff --git a/test/core/simple/test-http-head-request.js b/test/core/simple/test-http-head-request.js deleted file mode 100644 index e8c203ef9..000000000 --- a/test/core/simple/test-http-head-request.js +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright Joyent, Inc. and other Node contributors. -// -// 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 common = require('../common'); -var assert = require('assert'); -var http = require('http'); -var util = require('util'); - - -var body = 'hello world\n'; - -var server = http.createServer(function (req, res) { - common.error('req: ' + req.method); - res.writeHead(200, {'Content-Length': body.length}); - res.end(); - server.close(); -}); - -var gotEnd = false; - -server.listen(common.PORT, function () { - var request = http.request({ - port: common.PROXY_PORT, - method: 'HEAD', - path: '/' - }, function (response) { - common.error('response start'); - response.on('end', function () { - common.error('response end'); - gotEnd = true; - }); - }); - request.end(); -}); - -process.on('exit', function () { - assert.ok(gotEnd); -}); diff --git a/test/core/simple/test-http-head-response-has-no-body-end.js b/test/core/simple/test-http-head-response-has-no-body-end.js deleted file mode 100644 index 6d89bee9f..000000000 --- a/test/core/simple/test-http-head-response-has-no-body-end.js +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright Joyent, Inc. and other Node contributors. -// -// 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. - -// libuv-broken - - -var common = require('../common'); -var assert = require('assert'); - -var http = require('http'); - -// This test is to make sure that when the HTTP server -// responds to a HEAD request with data to res.end, -// it does not send any body. - -var server = http.createServer(function (req, res) { - res.writeHead(200); - res.end('FAIL'); // broken: sends FAIL from hot path. -}); -server.listen(common.PORT); - -var responseComplete = false; - -server.on('listening', function () { - var req = http.request({ - port: common.PROXY_PORT, - method: 'HEAD', - path: '/' - }, function (res) { - common.error('response'); - res.on('end', function () { - common.error('response end'); - server.close(); - responseComplete = true; - }); - }); - common.error('req'); - req.end(); -}); - -process.on('exit', function () { - assert.ok(responseComplete); -}); diff --git a/test/core/simple/test-http-head-response-has-no-body.js b/test/core/simple/test-http-head-response-has-no-body.js deleted file mode 100644 index aa7a281b6..000000000 --- a/test/core/simple/test-http-head-response-has-no-body.js +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright Joyent, Inc. and other Node contributors. -// -// 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 common = require('../common'); -var assert = require('assert'); - -var http = require('http'); - -// This test is to make sure that when the HTTP server -// responds to a HEAD request, it does not send any body. -// In this case it was sending '0\r\n\r\n' - -var server = http.createServer(function (req, res) { - res.writeHead(200); // broken: defaults to TE chunked - res.end(); -}); -server.listen(common.PORT); - -var responseComplete = false; - -server.on('listening', function () { - var req = http.request({ - port: common.PROXY_PORT, - method: 'HEAD', - path: '/' - }, function (res) { - common.error('response'); - res.on('end', function () { - common.error('response end'); - server.close(); - responseComplete = true; - }); - }); - common.error('req'); - req.end(); -}); - -process.on('exit', function () { - assert.ok(responseComplete); -}); diff --git a/test/core/simple/test-http-host-headers.js b/test/core/simple/test-http-host-headers.js deleted file mode 100644 index 2dae1182e..000000000 --- a/test/core/simple/test-http-host-headers.js +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright Joyent, Inc. and other Node contributors. -// -// 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. - -// libuv-broken - - -var http = require('http'), - common = require('../common'), - assert = require('assert'), - httpServer = http.createServer(reqHandler); - -function reqHandler(req, res) { - console.log('Got request: ' + req.headers.host + ' ' + req.url); - if (req.url === '/setHostFalse5') { - assert.equal(req.headers.host, undefined); - } else { - assert.equal(req.headers.host, 'localhost:' + common.PROXY_PORT, - 'Wrong host header for req[' + req.url + ']: ' + - req.headers.host); - } - res.writeHead(200, {}); - //process.nextTick(function () { res.end('ok'); }); - res.end('ok'); -} - -function thrower(er) { - throw er; -} - -testHttp(); - -function testHttp() { - - console.log('testing http on port ' + common.PROXY_PORT + ' (proxied to ' + - common.PORT + ')'); - - var counter = 0; - - function cb() { - counter--; - console.log('back from http request. counter = ' + counter); - if (counter === 0) { - httpServer.close(); - } - } - - httpServer.listen(common.PORT, function (er) { - console.error('listening on ' + common.PORT); - - if (er) throw er; - - http.get({ method: 'GET', - path: '/' + (counter++), - host: 'localhost', - //agent: false, - port: common.PROXY_PORT }, cb).on('error', thrower); - - http.request({ method: 'GET', - path: '/' + (counter++), - host: 'localhost', - //agent: false, - port: common.PROXY_PORT }, cb).on('error', thrower).end(); - - http.request({ method: 'POST', - path: '/' + (counter++), - host: 'localhost', - //agent: false, - port: common.PROXY_PORT }, cb).on('error', thrower).end(); - - http.request({ method: 'PUT', - path: '/' + (counter++), - host: 'localhost', - //agent: false, - port: common.PROXY_PORT }, cb).on('error', thrower).end(); - - http.request({ method: 'DELETE', - path: '/' + (counter++), - host: 'localhost', - //agent: false, - port: common.PROXY_PORT }, cb).on('error', thrower).end(); - }); -} - diff --git a/test/core/simple/test-http-many-keep-alive-connections.js b/test/core/simple/test-http-many-keep-alive-connections.js deleted file mode 100644 index 6b79619bb..000000000 --- a/test/core/simple/test-http-many-keep-alive-connections.js +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright Joyent, Inc. and other Node contributors. -// -// 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 common = require('../common'); -var assert = require('assert'); -var http = require('http'); - -var expected = 10000; -var responses = 0; -var requests = 0; -var connection; - -var server = http.Server(function (req, res) { - requests++; - assert.equal(req.connection, connection); - res.writeHead(200); - res.end('hello world\n'); -}); - -server.once('connection', function (c) { - connection = c; -}); - -server.listen(common.PORT, function () { - var callee = arguments.callee; - var request = http.get({ - port: common.PROXY_PORT, - path: '/', - headers: { - 'Connection': 'Keep-alive' - } - }, function (res) { - res.on('end', function () { - if (++responses < expected) { - callee(); - } else { - process.exit(); - } - }); - }).on('error', function (e) { - console.log(e.message); - process.exit(1); - }); - request.agent.maxSockets = 1; -}); - -process.on('exit', function () { - assert.equal(expected, responses); - assert.equal(expected, requests); -}); diff --git a/test/core/simple/test-http-multi-line-headers.js b/test/core/simple/test-http-multi-line-headers.js deleted file mode 100644 index e0eeb2c18..000000000 --- a/test/core/simple/test-http-multi-line-headers.js +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright Joyent, Inc. and other Node contributors. -// -// 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 common = require('../common'); -var assert = require('assert'); - -var http = require('http'); -var net = require('net'); - -var gotResponse = false; - -var server = net.createServer(function (conn) { - var body = 'Yet another node.js server.'; - - var response = - 'HTTP/1.1 200 OK\r\n' + - 'Connection: close\r\n' + - 'Content-Length: ' + body.length + '\r\n' + - 'Content-Type: text/plain;\r\n' + - ' x-unix-mode=0600;\r\n' + - ' name=\"hello.txt\"\r\n' + - '\r\n' + - body; - - conn.write(response, function () { - conn.destroy(); - server.close(); - }); -}); - -server.listen(common.PORT, function () { - http.get({host: '127.0.0.1', port: common.PROXY_PORT}, function (res) { - assert.equal(res.headers['content-type'], - 'text/plain;x-unix-mode=0600;name="hello.txt"'); - gotResponse = true; - }); -}); - -process.on('exit', function () { - assert.ok(gotResponse); -}); diff --git a/test/core/simple/test-http-proxy.js b/test/core/simple/test-http-proxy.js deleted file mode 100644 index fdddb3cdc..000000000 --- a/test/core/simple/test-http-proxy.js +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright Joyent, Inc. and other Node contributors. -// -// 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 common = require('../common'); -var assert = require('assert'); -var http = require('http'); -var url = require('url'); - -var PROXY_PORT = common.PORT; -var BACKEND_PORT = common.PORT + 1; - -var cookies = [ - 'session_token=; path=/; expires=Sun, 15-Sep-2030 13:48:52 GMT', - 'prefers_open_id=; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT' -]; - -var headers = {'content-type': 'text/plain', - 'set-cookie': cookies, - 'hello': 'world' }; - -var backend = http.createServer(function (req, res) { - common.debug('backend request'); - res.writeHead(200, headers); - res.write('hello world\n'); - res.end(); -}); - -var proxy = http.createServer(function (req, res) { - common.debug('proxy req headers: ' + JSON.stringify(req.headers)); - var proxy_req = http.get({ - port: BACKEND_PORT, - path: url.parse(req.url).pathname - }, function (proxy_res) { - - common.debug('proxy res headers: ' + JSON.stringify(proxy_res.headers)); - - assert.equal('world', proxy_res.headers['hello']); - assert.equal('text/plain', proxy_res.headers['content-type']); - assert.deepEqual(cookies, proxy_res.headers['set-cookie']); - - res.writeHead(proxy_res.statusCode, proxy_res.headers); - - proxy_res.on('data', function (chunk) { - res.write(chunk); - }); - - proxy_res.on('end', function () { - res.end(); - common.debug('proxy res'); - }); - }); -}); - -var body = ''; - -var nlistening = 0; -function startReq() { - nlistening++; - if (nlistening < 2) return; - - var client = http.get({ - port: common.PROXY_PORT, - path: '/test' - }, function (res) { - common.debug('got res'); - assert.equal(200, res.statusCode); - - assert.equal('world', res.headers['hello']); - assert.equal('text/plain', res.headers['content-type']); - assert.deepEqual(cookies, res.headers['set-cookie']); - - res.setEncoding('utf8'); - res.on('data', function (chunk) { body += chunk; }); - res.on('end', function () { - proxy.close(); - backend.close(); - common.debug('closed both'); - }); - }); - common.debug('client req'); -} - -common.debug('listen proxy'); -proxy.listen(PROXY_PORT, startReq); - -common.debug('listen backend'); -backend.listen(BACKEND_PORT, startReq); - -process.on('exit', function () { - assert.equal(body, 'hello world\n'); -}); diff --git a/test/core/simple/test-http-response-close.js b/test/core/simple/test-http-response-close.js deleted file mode 100644 index b92abb011..000000000 --- a/test/core/simple/test-http-response-close.js +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright Joyent, Inc. and other Node contributors. -// -// 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 common = require('../common'); -var assert = require('assert'); -var http = require('http'); - -var gotEnd = false; - -var server = http.createServer(function (req, res) { - res.writeHead(200); - res.write('a'); - - req.on('close', function () { - console.error('aborted'); - gotEnd = true; - }); -}); -server.listen(common.PORT); - -server.on('listening', function () { - console.error('make req'); - http.get({ - port: common.PROXY_PORT - }, function (res) { - console.error('got res'); - res.on('data', function (data) { - console.error('destroy res'); - res.destroy(); - server.close(); - }); - }); -}); - -process.on('exit', function () { - assert.ok(gotEnd); -}); diff --git a/test/core/simple/test-http-server-multiheaders.js b/test/core/simple/test-http-server-multiheaders.js deleted file mode 100644 index 6a5b8be72..000000000 --- a/test/core/simple/test-http-server-multiheaders.js +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright Joyent, Inc. and other Node contributors. -// -// 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. - -// Verify that the HTTP server implementation handles multiple instances -// of the same header as per RFC2616: joining the handful of fields by ', ' -// that support it, and dropping duplicates for other fields. - -var common = require('../common'); -var assert = require('assert'); -var http = require('http'); - -var srv = http.createServer(function (req, res) { - assert.equal(req.headers.accept, 'abc, def, ghijklmnopqrst'); - assert.equal(req.headers.host, 'foo'); - assert.equal(req.headers['x-foo'], 'bingo'); - assert.equal(req.headers['x-bar'], 'banjo, bango'); - - res.writeHead(200, {'Content-Type' : 'text/plain'}); - res.end('EOF'); - - srv.close(); -}); - -srv.listen(common.PORT, function () { - http.get({ - host: 'localhost', - port: common.PROXY_PORT, - path: '/', - headers: [ - ['accept', 'abc'], - ['accept', 'def'], - ['Accept', 'ghijklmnopqrst'], - ['host', 'foo'], - ['Host', 'bar'], - ['hOst', 'baz'], - ['x-foo', 'bingo'], - ['x-bar', 'banjo'], - ['x-bar', 'bango'] - ] - }); -}); diff --git a/test/core/simple/test-http-set-cookies.js b/test/core/simple/test-http-set-cookies.js deleted file mode 100644 index aac29940e..000000000 --- a/test/core/simple/test-http-set-cookies.js +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright Joyent, Inc. and other Node contributors. -// -// 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 common = require('../common'); -var assert = require('assert'); -var http = require('http'); - -var nresponses = 0; - -var server = http.createServer(function (req, res) { - if (req.url == '/one') { - res.writeHead(200, [['set-cookie', 'A'], - ['content-type', 'text/plain']]); - res.end('one\n'); - } else { - res.writeHead(200, [['set-cookie', 'A'], - ['set-cookie', 'B'], - ['content-type', 'text/plain']]); - res.end('two\n'); - } -}); -server.listen(common.PORT); - -server.on('listening', function () { - // - // one set-cookie header - // - http.get({ port: common.PROXY_PORT, path: '/one' }, function (res) { - // set-cookie headers are always return in an array. - // even if there is only one. - assert.deepEqual(['A'], res.headers['set-cookie']); - assert.equal('text/plain', res.headers['content-type']); - - res.on('data', function (chunk) { - console.log(chunk.toString()); - }); - - res.on('end', function () { - if (++nresponses == 2) { - server.close(); - } - }); - }); - - // two set-cookie headers - - http.get({ port: common.PROXY_PORT, path: '/two' }, function (res) { - assert.deepEqual(['A', 'B'], res.headers['set-cookie']); - assert.equal('text/plain', res.headers['content-type']); - - res.on('data', function (chunk) { - console.log(chunk.toString()); - }); - - res.on('end', function () { - if (++nresponses == 2) { - server.close(); - } - }); - }); - -}); - -process.on('exit', function () { - assert.equal(2, nresponses); -}); diff --git a/test/core/simple/test-http-status-code.js b/test/core/simple/test-http-status-code.js deleted file mode 100644 index dffbaf7c9..000000000 --- a/test/core/simple/test-http-status-code.js +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright Joyent, Inc. and other Node contributors. -// -// 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. - -// libuv-broken - - -var common = require('../common'); -var assert = require('assert'); -var http = require('http'); - -// Simple test of Node's HTTP ServerResponse.statusCode -// ServerResponse.prototype.statusCode - -var testsComplete = 0; -var tests = [200, 202, 300, 404, 500]; -var testIdx = 0; - -var s = http.createServer(function (req, res) { - var t = tests[testIdx]; - res.writeHead(t, {'Content-Type': 'text/plain'}); - console.log('--\nserver: statusCode after writeHead: ' + res.statusCode); - assert.equal(res.statusCode, t); - res.end('hello world\n'); -}); - -s.listen(common.PORT, nextTest); - - -function nextTest() { - if (testIdx + 1 === tests.length) { - return s.close(); - } - var test = tests[testIdx]; - - http.get({ port: common.PROXY_PORT }, function (response) { - console.log('client: expected status: ' + test); - console.log('client: statusCode: ' + response.statusCode); - assert.equal(response.statusCode, test); - response.on('end', function () { - testsComplete++; - testIdx += 1; - nextTest(); - }); - }); -} - - -process.on('exit', function () { - assert.equal(4, testsComplete); -}); - diff --git a/test/core/simple/test-http-upgrade-server2.js b/test/core/simple/test-http-upgrade-server2.js deleted file mode 100644 index a52737618..000000000 --- a/test/core/simple/test-http-upgrade-server2.js +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright Joyent, Inc. and other Node contributors. -// -// 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 common = require('../common'); -var assert = require('assert'); -var http = require('http'); -var net = require('net'); - -var server = http.createServer(function (req, res) { - common.error('got req'); - throw new Error('This shouldn\'t happen.'); -}); - -server.on('upgrade', function (req, socket, upgradeHead) { - common.error('got upgrade event'); - // test that throwing an error from upgrade gets - // is uncaught - throw new Error('upgrade error'); -}); - -var gotError = false; - -process.on('uncaughtException', function (e) { - common.error('got \'clientError\' event'); - assert.equal('upgrade error', e.message); - gotError = true; - process.exit(0); -}); - - -server.listen(common.PORT, function () { - var c = net.createConnection(common.PROXY_PORT); - - c.on('connect', function () { - common.error('client wrote message'); - c.write('GET /blah HTTP/1.1\r\n' + - 'Upgrade: WebSocket\r\n' + - 'Connection: Upgrade\r\n' + - '\r\n\r\nhello world'); - }); - - c.on('end', function () { - c.end(); - }); - - c.on('close', function () { - common.error('client close'); - server.close(); - }); -}); - -process.on('exit', function () { - assert.ok(gotError); -}); diff --git a/test/core/simple/test-http.js b/test/core/simple/test-http.js deleted file mode 100644 index e082eb2ce..000000000 --- a/test/core/simple/test-http.js +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright Joyent, Inc. and other Node contributors. -// -// 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 common = require('../common'); -var assert = require('assert'); -var http = require('http'); -var url = require('url'); - -function p(x) { - common.error(common.inspect(x)); -} - -var responses_sent = 0; -var responses_recvd = 0; -var body0 = ''; -var body1 = ''; - -var server = http.Server(function (req, res) { - if (responses_sent == 0) { - assert.equal('GET', req.method); - assert.equal('/hello', url.parse(req.url).pathname); - - console.dir(req.headers); - assert.equal(true, 'accept' in req.headers); - assert.equal('*/*', req.headers['accept']); - - assert.equal(true, 'foo' in req.headers); - assert.equal('bar', req.headers['foo']); - } - - if (responses_sent == 1) { - assert.equal('POST', req.method); - assert.equal('/world', url.parse(req.url).pathname); - this.close(); - } - - req.on('end', function () { - res.writeHead(200, {'Content-Type': 'text/plain'}); - res.write('The path was ' + url.parse(req.url).pathname); - res.end(); - responses_sent += 1; - }); - - //assert.equal('127.0.0.1', res.connection.remoteAddress); -}); -server.listen(common.PORT); - -server.on('listening', function () { - var agent = new http.Agent({ port: common.PROXY_PORT, maxSockets: 1 }); - http.get({ - port: common.PROXY_PORT, - path: '/hello', - headers: {'Accept': '*/*', 'Foo': 'bar'}, - agent: agent - }, function (res) { - assert.equal(200, res.statusCode); - responses_recvd += 1; - res.setEncoding('utf8'); - res.on('data', function (chunk) { body0 += chunk; }); - common.debug('Got /hello response'); - }); - - setTimeout(function () { - var req = http.request({ - port: common.PROXY_PORT, - method: 'POST', - path: '/world', - agent: agent - }, function (res) { - assert.equal(200, res.statusCode); - responses_recvd += 1; - res.setEncoding('utf8'); - res.on('data', function (chunk) { body1 += chunk; }); - common.debug('Got /world response'); - }); - req.end(); - }, 1); -}); - -process.on('exit', function () { - common.debug('responses_recvd: ' + responses_recvd); - assert.equal(2, responses_recvd); - - common.debug('responses_sent: ' + responses_sent); - assert.equal(2, responses_sent); - - assert.equal('The path was /hello', body0); - assert.equal('The path was /world', body1); -}); - diff --git a/test/examples-test.js b/test/examples-test.js index 36beb89a9..8464e3fe5 100644 --- a/test/examples-test.js +++ b/test/examples-test.js @@ -1,26 +1,71 @@ /* - * examples.js: Tests which ensure all examples do not throw errors. - * - * (C) 2010, Charlie Robbins - * - */ - -var vows = require('vows') - macros = require('./macros'), - examples = macros.examples; - -// -// Suppress `EADDRINUSE` errors since -// we are just checking for require-time errors -// -process.on('uncaughtException', function (err) { - if (err.code !== 'EADDRINUSE') { - throw err; - } -}); - -vows.describe('node-http-proxy/examples').addBatch( - examples.shouldHaveDeps() -).addBatch( - examples.shouldHaveNoErrors() -).export(module); \ No newline at end of file + 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/helpers/http.js b/test/helpers/http.js deleted file mode 100644 index aaf7a8042..000000000 --- a/test/helpers/http.js +++ /dev/null @@ -1,182 +0,0 @@ -/* - * http.js: Top level include for node-http-proxy http helpers - * - * (C) 2010 Nodejitsu Inc. - * MIT LICENCE - * - */ - -var assert = require('assert'), - http = require('http'), - https = require('https'), - url = require('url'), - async = require('async'), - helpers = require('./index'), - protocols = helpers.protocols, - httpProxy = require('../../lib/node-http-proxy'); - -// -// ### function createServerPair (options, callback) -// #### @options {Object} Options to create target and proxy server. -// #### @callback {function} Continuation to respond to when complete. -// -// Creates http target and proxy servers -// -exports.createServerPair = function (options, callback) { - async.series([ - // - // 1. Create the target server - // - function createTarget(next) { - exports.createServer(options.target, next); - }, - // - // 2. Create the proxy server - // - function createTarget(next) { - exports.createProxyServer(options.proxy, next); - } - ], callback); -}; - -// -// ### function createServer (options, callback) -// #### @options {Object} Options for creatig an http server. -// #### @port {number} Port to listen on -// #### @output {string} String to write to each HTTP response -// #### @headers {Object} Headers to assert are sent by `node-http-proxy`. -// #### @callback {function} Continuation to respond to when complete. -// -// Creates a target server that the tests will proxy to. -// -exports.createServer = function (options, callback) { - // - // Request handler to use in either `http` - // or `https` server. - // - function requestHandler(req, res) { - if (options.headers) { - Object.keys(options.headers).forEach(function (key) { - assert.equal(req.headers[key], options.headers[key]); - }); - } - - if (options.outputHeaders) { - Object.keys(options.outputHeaders).forEach(function (header) { - res.setHeader(header, options.outputHeaders[header]); - }); - } - - setTimeout(function() { - res.writeHead(200, { 'Content-Type': 'text/plain' }); - res.write(options.output || 'hello proxy'); - res.end(); - }, options.latency || 1); - } - - var server = protocols.target === 'https' - ? https.createServer(helpers.https, requestHandler) - : http.createServer(requestHandler); - - server.listen(options.port, function () { - callback(null, this); - }); -}; - -// -// ### function createProxyServer (options, callback) -// #### @options {Object} Options for creatig an http server. -// #### @port {number} Port to listen on -// #### @latency {number} Latency of this server in milliseconds -// #### @proxy {Object} Options to pass to the HttpProxy. -// #### @routing {boolean} Enables `httpProxy.RoutingProxy` -// #### @callback {function} Continuation to respond to when complete. -// -// Creates a proxy server that the tests will request against. -// -exports.createProxyServer = function (options, callback) { - if (!options.latency) { - if (protocols.proxy === 'https') { - options.proxy.https = helpers.https; - } - options.proxy.rejectUnauthorized = false; - - return httpProxy - .createServer(options.proxy) - .listen(options.port, function () { - callback(null, this); - }); - } - - var server, - proxy; - - proxy = options.routing - ? new httpProxy.RoutingProxy(options.proxy) - : new httpProxy.HttpProxy(options.proxy); - - // - // Request handler to use in either `http` - // or `https` server. - // - function requestHandler(req, res) { - var buffer = httpProxy.buffer(req); - - if (options.outputHeaders) { - Object.keys(options.outputHeaders).forEach(function (header) { - res.setHeader(header, options.outputHeaders[header]); - }); - } - setTimeout(function () { - // - // Setup options dynamically for `RoutingProxy.prototype.proxyRequest` - // or `HttpProxy.prototype.proxyRequest`. - // - buffer = options.routing ? { buffer: buffer } : buffer; - proxy.proxyRequest(req, res, buffer); - }, options.latency); - } - - server = protocols.proxy === 'https' - ? https.createServer(helpers.https, requestHandler) - : http.createServer(requestHandler); - - server.listen(options.port, function () { - callback(null, this); - }); -}; - -// -// ### function assignPortsToRoutes (routes) -// #### @routes {Object} Routing table to assign ports to -// -// Assigns dynamic ports to the `routes` for runtime testing. -// -exports.assignPortsToRoutes = function (routes) { - Object.keys(routes).forEach(function (source) { - routes[source] = routes[source].replace('{PORT}', helpers.nextPort); - }); - - return routes; -}; - -// -// ### function parseRoutes (options) -// #### @options {Object} Options to use when parsing routes -// #### @protocol {string} Protocol to use in the routes -// #### @routes {Object} Routes to parse. -// -// Returns an Array of fully-parsed URLs for the source and -// target of `options.routes`. -// -exports.parseRoutes = function (options) { - var protocol = options.protocol || 'http', - routes = options.routes; - - return Object.keys(routes).map(function (source) { - return { - source: url.parse(protocol + '://' + source), - target: url.parse(protocol + '://' + routes[source]) - }; - }); -}; diff --git a/test/helpers/index.js b/test/helpers/index.js deleted file mode 100644 index 7e3c3f488..000000000 --- a/test/helpers/index.js +++ /dev/null @@ -1,105 +0,0 @@ -/* - * index.js: Top level include for node-http-proxy helpers - * - * (C) 2010 Nodejitsu Inc. - * MIT LICENCE - * - */ - -var fs = require('fs'), - path = require('path'); - -var fixturesDir = path.join(__dirname, '..', 'fixtures'); - -// -// @https {Object} -// Returns the necessary `https` credentials. -// -Object.defineProperty(exports, 'https', { - get: function () { - delete this.https; - return this.https = { - key: fs.readFileSync(path.join(fixturesDir, 'agent2-key.pem'), 'utf8'), - cert: fs.readFileSync(path.join(fixturesDir, 'agent2-cert.pem'), 'utf8') - }; - } -}); - -// -// @protocols {Object} -// Returns an object representing the desired protocols -// for the `proxy` and `target` server. -// -Object.defineProperty(exports, 'protocols', { - get: function () { - delete this.protocols; - return this.protocols = { - target: exports.argv.target || 'http', - proxy: exports.argv.proxy || 'http' - }; - } -}); - -// -// @nextPort {number} -// Returns an auto-incrementing port for tests. -// -Object.defineProperty(exports, 'nextPort', { - get: function () { - var current = this.port || 9050; - this.port = current + 1; - return current; - } -}); - -// -// @nextPortPair {Object} -// Returns an auto-incrementing pair of ports for tests. -// -Object.defineProperty(exports, 'nextPortPair', { - get: function () { - return { - target: this.nextPort, - proxy: this.nextPort - }; - } -}); - -// -// ### function describe(prefix) -// #### @prefix {string} Prefix to use before the description -// -// Returns a string representing the protocols that this suite -// is testing based on CLI arguments. -// -exports.describe = function (prefix, base) { - prefix = prefix || ''; - base = base || 'http'; - - function protocol(endpoint) { - return exports.protocols[endpoint] === 'https' - ? base + 's' - : base; - } - - return [ - 'node-http-proxy', - prefix, - [ - protocol('proxy'), - '-to-', - protocol('target') - ].join('') - ].filter(Boolean).join('/'); -}; - -// -// Expose the CLI arguments -// -exports.argv = require('optimist').argv; - -// -// Export additional helpers for `http` and `websockets`. -// -exports.http = require('./http'); -exports.ws = require('./ws'); \ No newline at end of file diff --git a/test/helpers/ws.js b/test/helpers/ws.js deleted file mode 100644 index a4905227d..000000000 --- a/test/helpers/ws.js +++ /dev/null @@ -1,112 +0,0 @@ -/* - * ws.js: Top level include for node-http-proxy websocket helpers - * - * (C) 2010 Nodejitsu Inc. - * MIT LICENCE - * - */ - -var assert = require('assert'), - https = require('https'), - async = require('async'), - io = require('socket.io'), - ws = require('ws'), - helpers = require('./index'), - protocols = helpers.protocols, - http = require('./http'); - -// -// ### function createServerPair (options, callback) -// #### @options {Object} Options to create target and proxy server. -// #### @target {Object} Options for the target server. -// #### @proxy {Object} Options for the proxy server. -// #### @callback {function} Continuation to respond to when complete. -// -// Creates http target and proxy servers -// -exports.createServerPair = function (options, callback) { - async.series([ - // - // 1. Create the target server - // - function createTarget(next) { - exports.createServer(options.target, next); - }, - // - // 2. Create the proxy server - // - function createTarget(next) { - http.createProxyServer(options.proxy, next); - } - ], callback); -}; - -// -// ### function createServer (options, callback) -// #### @options {Object} Options for creating the socket.io or ws server. -// #### @raw {boolean} Enables ws.Websocket server. -// -// Creates a socket.io or ws server using the specified `options`. -// -exports.createServer = function (options, callback) { - return options.raw - ? exports.createWsServer(options, callback) - : exports.createSocketIoServer(options, callback); -}; - -// -// ### function createSocketIoServer (options, callback) -// #### @options {Object} Options for creating the socket.io server -// #### @port {number} Port to listen on -// #### @input {string} Input to expect from the only socket -// #### @output {string} Output to send the only socket -// -// Creates a socket.io server on the specified `options.port` which -// will expect `options.input` and then send `options.output`. -// -exports.createSocketIoServer = function (options, callback) { - var server = protocols.target === 'https' - ? io.listen(options.port, helpers.https, callback) - : io.listen(options.port, callback); - - server.sockets.on('connection', function (socket) { - socket.on('incoming', function (data) { - assert.equal(data, options.input); - socket.emit('outgoing', options.output); - }); - }); -}; - -// -// ### function createWsServer (options, callback) -// #### @options {Object} Options for creating the ws.Server instance -// #### @port {number} Port to listen on -// #### @input {string} Input to expect from the only socket -// #### @output {string} Output to send the only socket -// -// Creates a ws.Server instance on the specified `options.port` which -// will expect `options.input` and then send `options.output`. -// -exports.createWsServer = function (options, callback) { - var server, - wss; - - if (protocols.target === 'https') { - server = https.createServer(helpers.https, function (req, res) { - req.writeHead(200); - req.end(); - }).listen(options.port, callback); - - wss = new ws.Server({ server: server }); - } - else { - wss = new ws.Server({ port: options.port }, callback); - } - - wss.on('connection', function (socket) { - socket.on('message', function (data) { - assert.equal(data, options.input); - socket.send(options.output); - }); - }); -}; \ No newline at end of file diff --git a/test/http/http-test.js b/test/http/http-test.js deleted file mode 100644 index 81f8726a7..000000000 --- a/test/http/http-test.js +++ /dev/null @@ -1,102 +0,0 @@ -/* - node-http-proxy-test.js: http proxy for node.js - - Copyright (c) 2010 Charlie Robbins, Marak Squires and Fedor Indutny - - 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 assert = require('assert'), - fs = require('fs'), - path = require('path'), - async = require('async'), - request = require('request'), - vows = require('vows'), - macros = require('../macros'), - helpers = require('../helpers'); - -var routeFile = path.join(__dirname, 'config.json'); - -vows.describe(helpers.describe()).addBatch({ - "With a valid target server": { - "and no latency": { - "and no headers": macros.http.assertProxied(), - "and headers": macros.http.assertProxied({ - request: { headers: { host: 'unknown.com' } } - }), - "and request close connection header": macros.http.assertProxied({ - request: { headers: { connection: "close" } }, - outputHeaders: { connection: "close" } - }), - "and request keep alive connection header": macros.http.assertProxied({ - request: { headers: { connection: "keep-alive" } }, - outputHeaders: { connection: "keep-alive" } - }), - "and response close connection header": macros.http.assertProxied({ - request: { headers: { connection: "" } }, // Must explicitly set to "" because otherwise node will automatically add a "connection: keep-alive" header - targetHeaders: { connection: "close" }, - outputHeaders: { connection: "close" } - }), - "and response keep-alive connection header": macros.http.assertProxied({ - request: { headers: { connection: "" } }, // Must explicitly set to "" because otherwise node will automatically add a "connection: keep-alive" header - targetHeaders: { connection: "keep-alive" }, - outputHeaders: { connection: "keep-alive" } - }), - "and response keep-alive connection header from http 1.0 client": macros.http.assertRawHttpProxied({ - rawRequest: "GET / HTTP/1.0\r\n\r\n", - targetHeaders: { connection: "keep-alive" }, - match: /connection: close/i - }), - "and request keep alive from http 1.0 client": macros.http.assertRawHttpProxied({ - rawRequest: "GET / HTTP/1.0\r\nConnection: Keep-Alive\r\n\r\n", - targetHeaders: { connection: "keep-alive" }, - match: /connection: keep-alive/i - }), - "and no connection header": macros.http.assertProxied({ - request: { headers: { connection: "" } }, // Must explicitly set to "" because otherwise node will automatically add a "connection: keep-alive" header - outputHeaders: { connection: "keep-alive" } - }), - "and forwarding enabled": macros.http.assertForwardProxied() - }, - "and latency": { - "and no headers": macros.http.assertProxied({ - latency: 2000 - }), - "and response headers": macros.http.assertProxied({ - targetHeaders: { "x-testheader": "target" }, - proxyHeaders: { "X-TestHeader": "proxy" }, - outputHeaders: { "x-testheader": "target" }, - latency: 1000 - }) - }, - "and timeout set": macros.http.assertProxied({ - shouldFail: true, - timeout: 2000, - requestLatency: 4000 - }) - }, - "With a no valid target server": { - "and no latency": macros.http.assertInvalidProxy(), - "and latency": macros.http.assertInvalidProxy({ - latency: 2000 - }) - } -}).export(module); diff --git a/test/http/routing-table-test.js b/test/http/routing-table-test.js deleted file mode 100644 index f3dcf31e7..000000000 --- a/test/http/routing-table-test.js +++ /dev/null @@ -1,107 +0,0 @@ -/* - * routing-table-test.js: Tests for the proxying using the ProxyTable object. - * - * (C) 2010, Charlie Robbins - * - */ - -var assert = require('assert'), - fs = require('fs'), - path = require('path'), - async = require('async'), - request = require('request'), - vows = require('vows'), - macros = require('../macros'), - helpers = require('../helpers'); - -var routeFile = path.join(__dirname, 'config.json'); - -vows.describe(helpers.describe('routing-table')).addBatch({ - "With a routing table": { - "with latency": macros.http.assertProxiedToRoutes({ - latency: 2000, - routes: { - "icanhaz.com": "127.0.0.1:{PORT}", - "latency.com": "127.0.0.1:{PORT}" - } - }), - "addHost() / removeHost()": macros.http.assertDynamicProxy({ - hostnameOnly: true, - routes: { - "static.com": "127.0.0.1:{PORT}", - "removed.com": "127.0.0.1:{PORT}" - } - }, { - add: [{ host: 'dynamic1.com', target: '127.0.0.1:' }], - drop: ['removed.com'] - }), - "using RegExp": macros.http.assertProxiedToRoutes({ - routes: { - "foo.com": "127.0.0.1:{PORT}", - "bar.com": "127.0.0.1:{PORT}", - "baz.com/taco": "127.0.0.1:{PORT}", - "pizza.com/taco/muffins": "127.0.0.1:{PORT}", - "blah.com/me": "127.0.0.1:{PORT}/remapped", - "bleh.com/remap/this": "127.0.0.1:{PORT}/remap/remapped", - "test.com/double/tap": "127.0.0.1:{PORT}/remap" - } - }), - "using hostnameOnly": macros.http.assertProxiedToRoutes({ - hostnameOnly: true, - routes: { - "foo.com": "127.0.0.1:{PORT}", - "bar.com": "127.0.0.1:{PORT}" - } - }), - "using pathnameOnly": macros.http.assertProxiedToRoutes({ - pathnameOnly: true, - routes: { - "/foo": "127.0.0.1:{PORT}", - "/bar": "127.0.0.1:{PORT}", - "/pizza": "127.0.0.1:{PORT}" - } - }), - "using a routing file": macros.http.assertProxiedToRoutes({ - filename: routeFile, - routes: { - "foo.com": "127.0.0.1:{PORT}", - "bar.com": "127.0.0.1:{PORT}" - } - }, { - "after the file has been modified": { - topic: function () { - var config = JSON.parse(fs.readFileSync(routeFile, 'utf8')), - protocol = helpers.protocols.proxy, - port = helpers.nextPort, - that = this; - - config.router['dynamic.com'] = "127.0.0.1:" + port; - fs.writeFileSync(routeFile, JSON.stringify(config)); - - async.parallel([ - function waitForRoutes(next) { - that.proxyServer.on('routes', next); - }, - async.apply( - helpers.http.createServer, - { - port: port, - output: 'hello from dynamic.com' - } - ) - ], function () { - request({ - uri: protocol + '://127.0.0.1:' + that.port, - headers: { - host: 'dynamic.com' - } - }, that.callback); - }); - }, - "should receive 'hello from dynamic.com'": function (err, res, body) { - assert.equal(body, 'hello from dynamic.com'); - } - } - }) - } -}).export(module); diff --git a/test/lib-http-proxy-common-test.js b/test/lib-http-proxy-common-test.js new file mode 100644 index 000000000..7398a0ebe --- /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 000000000..b25ad6e12 --- /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 000000000..0ae0bda6f --- /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 000000000..bfed888c0 --- /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 000000000..6b2ce035e --- /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 000000000..192504921 --- /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 diff --git a/test/macros/examples.js b/test/macros/examples.js deleted file mode 100644 index 9d4202e6a..000000000 --- a/test/macros/examples.js +++ /dev/null @@ -1,101 +0,0 @@ -/* - * examples.js: Macros for testing code in examples/ - * - * (C) 2010 Nodejitsu Inc. - * MIT LICENCE - * - */ - -var assert = require('assert'), - fs = require('fs'), - path = require('path'), - spawn = require('child_process').spawn, - async = require('async'); - -var rootDir = path.join(__dirname, '..', '..'), - examplesDir = path.join(rootDir, 'examples'); - -// -// ### function shouldHaveDeps () -// -// Ensures that all `npm` dependencies are installed in `/examples`. -// -exports.shouldHaveDeps = function () { - return { - "Before testing examples": { - topic: function () { - 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(); - } - - 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), - ], this.callback); - }, - "examples/node_modules should exist": function (err, files) { - assert.notEqual(files.indexOf('node_modules'), -1); - } - } - } -}; - -// -// ### function shouldRequire (file) -// #### @file {string} File to attempt to require -// -// Returns a test which attempts to require `file`. -// -exports.shouldRequire = function (file) { - return { - "should have no errors": function () { - try { assert.isObject(require(file)) } - catch (ex) { assert.isNull(ex) } - } - }; -}; - -// -// ### function shouldHaveNoErrors () -// -// Returns a vows context that attempts to require -// every relevant example file in `examples`. -// -exports.shouldHaveNoErrors = function () { - var context = {}; - - ['balancer', 'http', 'middleware', 'websocket'].forEach(function (dir) { - var name = 'examples/' + dir, - files = fs.readdirSync(path.join(rootDir, 'examples', dir)); - - files.forEach(function (file) { - context[name + '/' + file] = exports.shouldRequire(path.join( - examplesDir, dir, file - )); - }); - }); - - return context; -}; \ No newline at end of file diff --git a/test/macros/http.js b/test/macros/http.js deleted file mode 100644 index d3d83426a..000000000 --- a/test/macros/http.js +++ /dev/null @@ -1,531 +0,0 @@ -/* - * http.js: Macros for proxying HTTP requests - * - * (C) 2010 Nodejitsu Inc. - * MIT LICENCE - * - */ - -var assert = require('assert'), - fs = require('fs'), - async = require('async'), - net = require('net'), - request = require('request'), - helpers = require('../helpers/index'); - -// -// ### function assertRequest (options) -// #### @options {Object} Options for this request assertion. -// #### @request {Object} Options to use for `request`. -// #### @assert {Object} Test assertions against the response. -// -// Makes a request using `options.request` and then asserts the response -// and body against anything in `options.assert`. -// -exports.assertRequest = function (options) { - return { - topic: function () { - // - // Now make the HTTP request and assert. - // - options.request.rejectUnauthorized = false; - request(options.request, this.callback); - }, - "should succeed": function (err, res, body) { - assert.isNull(err); - if (options.assert.headers) { - Object.keys(options.assert.headers).forEach(function(header){ - assert.equal(res.headers[header], options.assert.headers[header]); - }); - } - - if (options.assert.body) { - assert.equal(body, options.assert.body); - } - - if (options.assert.statusCode) { - assert.equal(res.statusCode, options.assert.statusCode); - } - } - }; -}; - -// -// ### function assertFailedRequest (options) -// #### @options {Object} Options for this failed request assertion. -// #### @request {Object} Options to use for `request`. -// #### @assert {Object} Test assertions against the response. -// -// Makes a request using `options.request` and then asserts the response -// and body against anything in `options.assert`. -// -exports.assertFailedRequest = function (options) { - return { - topic: function () { - // - // Now make the HTTP request and assert. - // - options.request.rejectUnauthorized = false; - request(options.request, this.callback); - }, - "should not succeed": function (err, res, body) { - assert.notStrictEqual(err,null); - } - }; -}; - -// -// ### function assertProxied (options) -// #### @options {Object} Options for this test -// #### @latency {number} Latency in milliseconds for the proxy server. -// #### @ports {Object} Ports for the request (target, proxy). -// #### @output {string} Output to assert from. -// #### @forward {Object} Options for forward proxying. -// -// Creates a complete end-to-end test for requesting against an -// http proxy. -// -exports.assertProxied = function (options) { - options = options || {}; - - var ports = options.ports || helpers.nextPortPair, - output = options.output || 'hello world from ' + ports.target, - outputHeaders = options.outputHeaders, - targetHeaders = options.targetHeaders, - proxyHeaders = options.proxyHeaders, - protocol = helpers.protocols.proxy, - req = options.request || {}, - timeout = options.timeout || null, - assertFn = options.shouldFail - ? exports.assertFailedRequest - : exports.assertRequest; - - req.uri = req.uri || protocol + '://127.0.0.1:' + ports.proxy; - - return { - topic: function () { - // - // Create a target server and a proxy server - // using the `options` supplied. - // - helpers.http.createServerPair({ - target: { - output: output, - outputHeaders: targetHeaders, - port: ports.target, - headers: req.headers, - latency: options.requestLatency - }, - proxy: { - latency: options.latency, - port: ports.proxy, - outputHeaders: proxyHeaders, - proxy: { - forward: options.forward, - target: { - https: helpers.protocols.target === 'https', - host: '127.0.0.1', - port: ports.target - }, - timeout: timeout - } - } - }, this.callback); - }, - "the proxy request": assertFn({ - request: req, - assert: { - headers: outputHeaders, - body: output - } - }) - }; -}; - -// -// ### function assertRawHttpProxied (options) -// #### @options {Object} Options for this test -// #### @rawRequest {string} Raw HTTP request to perform. -// #### @match {RegExp} Output to match in the response. -// #### @latency {number} Latency in milliseconds for the proxy server. -// #### @ports {Object} Ports for the request (target, proxy). -// #### @output {string} Output to assert from. -// #### @forward {Object} Options for forward proxying. -// -// Creates a complete end-to-end test for requesting against an -// http proxy. -// -exports.assertRawHttpProxied = function (options) { - // Don't test raw requests over HTTPS since options.rawRequest won't be - // encrypted. - if(helpers.protocols.proxy == 'https') { - return true; - } - - options = options || {}; - - var ports = options.ports || helpers.nextPortPair, - output = options.output || 'hello world from ' + ports.target, - outputHeaders = options.outputHeaders, - targetHeaders = options.targetHeaders, - proxyHeaders = options.proxyHeaders, - protocol = helpers.protocols.proxy, - timeout = options.timeout || null, - assertFn = options.shouldFail - ? exports.assertFailedRequest - : exports.assertRequest; - - return { - topic: function () { - var topicCallback = this.callback; - - // - // Create a target server and a proxy server - // using the `options` supplied. - // - helpers.http.createServerPair({ - target: { - output: output, - outputHeaders: targetHeaders, - port: ports.target, - latency: options.requestLatency - }, - proxy: { - latency: options.latency, - port: ports.proxy, - outputHeaders: proxyHeaders, - proxy: { - forward: options.forward, - target: { - https: helpers.protocols.target === 'https', - host: '127.0.0.1', - port: ports.target - }, - timeout: timeout - } - } - }, function() { - var response = ''; - var client = net.connect(ports.proxy, '127.0.0.1', function() { - client.write(options.rawRequest); - }); - - client.on('data', function(data) { - response += data.toString(); - }); - - client.on('end', function() { - topicCallback(null, options.match, response); - }); - }); - }, - "should succeed": function(err, match, response) { - assert.match(response, match); - } - }; -}; - -// -// ### function assertInvalidProxy (options) -// #### @options {Object} Options for this test -// #### @latency {number} Latency in milliseconds for the proxy server -// #### @ports {Object} Ports for the request (target, proxy) -// -// Creates a complete end-to-end test for requesting against an -// http proxy with no target server. -// -exports.assertInvalidProxy = function (options) { - options = options || {}; - - var ports = options.ports || helpers.nextPortPair, - req = options.request || {}, - protocol = helpers.protocols.proxy; - - - req.uri = req.uri || protocol + '://127.0.0.1:' + ports.proxy; - - return { - topic: function () { - // - // Only create the proxy server, simulating a reverse-proxy - // to an invalid location. - // - helpers.http.createProxyServer({ - latency: options.latency, - port: ports.proxy, - proxy: { - target: { - host: '127.0.0.1', - port: ports.target - } - } - }, this.callback); - }, - "the proxy request": exports.assertRequest({ - request: req, - assert: { - statusCode: 500 - } - }) - }; -}; - -// -// ### function assertForwardProxied (options) -// #### @options {Object} Options for this test. -// -// Creates a complete end-to-end test for requesting against an -// http proxy with both a valid and invalid forward target. -// -exports.assertForwardProxied = function (options) { - var forwardPort = helpers.nextPort; - - return { - topic: function () { - helpers.http.createServer({ - output: 'hello from forward', - port: forwardPort - }, this.callback); - }, - "and a valid forward target": exports.assertProxied({ - forward: { - port: forwardPort, - host: '127.0.0.1' - } - }), - "and an invalid forward target": exports.assertProxied({ - forward: { - port: 9898, - host: '127.0.0.1' - } - }) - }; -}; - -// -// ### function assertProxiedtoRoutes (options, nested) -// #### @options {Object} Options for this ProxyTable-based test -// #### @routes {Object|string} Routes to use for the proxy. -// #### @hostnameOnly {boolean} Enables hostnameOnly routing. -// #### @nested {Object} Nested vows to add to the returned context. -// -// Creates a complete end-to-end test for requesting against an -// http proxy using `options.routes`: -// -// 1. Creates target servers for all routes in `options.routes.` -// 2. Creates a proxy server. -// 3. Ensure requests to the proxy server for all route targets -// returns the unique expected output. -// -exports.assertProxiedToRoutes = function (options, nested) { - // - // Assign dynamic ports to the routes to use. - // - options.routes = helpers.http.assignPortsToRoutes(options.routes); - - // - // Parse locations from routes for making assertion requests. - // - var locations = helpers.http.parseRoutes(options), - port = options.pport || helpers.nextPort, - protocol = helpers.protocols.proxy, - context, - proxy; - - if (options.filename) { - // - // If we've been passed a filename write the routes to it - // and setup the proxy options to use that file. - // - fs.writeFileSync(options.filename, JSON.stringify({ router: options.routes })); - proxy = { router: options.filename }; - } - else { - // - // Otherwise just use the routes themselves. - // - proxy = { - hostnameOnly: options.hostnameOnly, - pathnameOnly: options.pathnameOnly, - router: options.routes - }; - } - - // - // Set the https options if necessary - // - if (helpers.protocols.target === 'https') { - proxy.target = { https: true }; - } - - // - // Create the test context which creates all target - // servers for all routes and a proxy server. - // - context = { - topic: function () { - var that = this; - - async.waterfall([ - // - // 1. Create all the target servers - // - async.apply( - async.forEach, - locations, - function createRouteTarget(location, next) { - helpers.http.createServer({ - port: location.target.port, - output: 'hello from ' + location.source.href - }, next); - } - ), - // - // 2. Create the proxy server - // - async.apply( - helpers.http.createProxyServer, - { - port: port, - latency: options.latency, - routing: true, - proxy: proxy - } - ) - ], function (_, server) { - // - // 3. Set the proxy server for later use - // - that.proxyServer = server; - that.callback(); - }); - - // - // 4. Assign the port to the context for later use - // - this.port = port; - }, - // - // Add an extra assertion to a route which - // should respond with 404 - // - "a request to unknown.com": exports.assertRequest({ - assert: { statusCode: 404 }, - request: { - uri: protocol + '://127.0.0.1:' + port, - headers: { - host: 'unknown.com' - } - } - }) - }; - - // - // Add test assertions for each of the route locations. - // - locations.forEach(function (location) { - context[location.source.href] = exports.assertRequest({ - request: { - uri: protocol + '://127.0.0.1:' + port + location.source.path, - headers: { - host: location.source.hostname - } - }, - assert: { - body: 'hello from ' + location.source.href - } - }); - }); - - // - // If there are any nested vows to add to the context - // add them before returning the full context. - // - if (nested) { - Object.keys(nested).forEach(function (key) { - context[key] = nested[key]; - }); - } - - return context; -}; - -// -// ### function assertDynamicProxy (static, dynamic) -// Asserts that after the `static` routes have been tested -// and the `dynamic` routes are added / removed the appropriate -// proxy responses are received. -// -exports.assertDynamicProxy = function (static, dynamic) { - var proxyPort = helpers.nextPort, - protocol = helpers.protocols.proxy, - context; - - if (dynamic.add) { - dynamic.add = dynamic.add.map(function (dyn) { - dyn.port = helpers.nextPort; - dyn.target = dyn.target + dyn.port; - return dyn; - }); - } - - context = { - topic: function () { - var that = this; - - setTimeout(function () { - if (dynamic.drop) { - dynamic.drop.forEach(function (dropHost) { - that.proxyServer.proxy.removeHost(dropHost); - }); - } - - if (dynamic.add) { - async.forEachSeries(dynamic.add, function addOne (dyn, next) { - that.proxyServer.proxy.addHost(dyn.host, dyn.target); - helpers.http.createServer({ - port: dyn.port, - output: 'hello ' + dyn.host - }, next); - }, that.callback); - } - else { - that.callback(); - } - }, 200); - } - }; - - if (dynamic.drop) { - dynamic.drop.forEach(function (dropHost) { - context[dropHost] = exports.assertRequest({ - assert: { statusCode: 404 }, - request: { - uri: protocol + '://127.0.0.1:' + proxyPort, - headers: { - host: dropHost - } - } - }); - }); - } - - if (dynamic.add) { - dynamic.add.forEach(function (dyn) { - context[dyn.host] = exports.assertRequest({ - assert: { body: 'hello ' + dyn.host }, - request: { - uri: protocol + '://127.0.0.1:' + proxyPort, - headers: { - host: dyn.host - } - } - }); - }); - } - - static.pport = proxyPort; - return exports.assertProxiedToRoutes(static, { - "once the server has started": context - }); -}; diff --git a/test/macros/index.js b/test/macros/index.js deleted file mode 100644 index c01f962b0..000000000 --- a/test/macros/index.js +++ /dev/null @@ -1,11 +0,0 @@ -/* - * index.js: Top level include for node-http-proxy macros - * - * (C) 2010 Nodejitsu Inc. - * MIT LICENCE - * - */ - -exports.examples = require('./examples'); -exports.http = require('./http'); -exports.ws = require('./ws'); \ No newline at end of file diff --git a/test/macros/ws.js b/test/macros/ws.js deleted file mode 100644 index 508725a49..000000000 --- a/test/macros/ws.js +++ /dev/null @@ -1,232 +0,0 @@ -/* - * ws.js: Macros for proxying Websocket requests - * - * (C) 2010 Nodejitsu Inc. - * MIT LICENCE - * - */ - -var assert = require('assert'), - fs = require('fs'), - async = require('async'), - io = require('socket.io-client'), - WebSocket = require('ws'), - helpers = require('../helpers/index'); - -// -// ### function assertSendRecieve (options) -// #### @options {Object} Options for creating this assertion. -// #### @raw {boolean} Enables raw `ws.WebSocket`. -// #### @uri {string} URI of the proxy server. -// #### @input {string} Input to assert sent to the target ws server. -// #### @output {string} Output to assert from the taget ws server. -// -// Creates a `socket.io` or raw `WebSocket` connection and asserts that -// `options.input` is sent to and `options.output` is received from the -// connection. -// -exports.assertSendReceive = function (options) { - if (!options.raw) { - return { - topic: function () { - var socket = io.connect(options.uri); - socket.on('outgoing', this.callback.bind(this, null)); - socket.emit('incoming', options.input); - }, - "should send input and receive output": function (_, data) { - assert.equal(data, options.output); - } - }; - } - - return { - topic: function () { - var socket = new WebSocket(options.uri); - socket.on('message', this.callback.bind(this, null)); - socket.on('open', function () { - socket.send(options.input); - }); - }, - "should send input and recieve output": function (_, data, flags) { - assert.equal(data, options.output); - } - }; -}; - -// -// ### function assertProxied (options) -// #### @options {Object} Options for this test -// #### @latency {number} Latency in milliseconds for the proxy server. -// #### @ports {Object} Ports for the request (target, proxy). -// #### @input {string} Input to assert sent to the target ws server. -// #### @output {string} Output to assert from the taget ws server. -// #### @raw {boolean} Enables raw `ws.Server` usage. -// -// Creates a complete end-to-end test for requesting against an -// http proxy. -// -exports.assertProxied = function (options) { - options = options || {}; - - var ports = options.ports || helpers.nextPortPair, - input = options.input || 'hello world to ' + ports.target, - output = options.output || 'hello world from ' + ports.target, - protocol = helpers.protocols.proxy; - - if (options.raw) { - protocol = helpers.protocols.proxy === 'https' - ? 'wss' - : 'ws'; - } - - return { - topic: function () { - helpers.ws.createServerPair({ - target: { - input: input, - output: output, - port: ports.target, - raw: options.raw - }, - proxy: { - latency: options.latency, - port: ports.proxy, - proxy: { - target: { - https: helpers.protocols.target === 'https', - host: '127.0.0.1', - port: ports.target - } - } - } - }, this.callback); - }, - "the proxy Websocket connection": exports.assertSendReceive({ - uri: protocol + '://127.0.0.1:' + ports.proxy, - input: input, - output: output, - raw: options.raw - }) - }; -}; - -// -// ### function assertProxiedtoRoutes (options, nested) -// #### @options {Object} Options for this ProxyTable-based test -// #### @raw {boolean} Enables ws.Server usage. -// #### @routes {Object|string} Routes to use for the proxy. -// #### @hostnameOnly {boolean} Enables hostnameOnly routing. -// #### @nested {Object} Nested vows to add to the returned context. -// -// Creates a complete end-to-end test for requesting against an -// http proxy using `options.routes`: -// -// 1. Creates target servers for all routes in `options.routes.` -// 2. Creates a proxy server. -// 3. Ensure Websocket connections to the proxy server for all route targets -// can send input and recieve output. -// -exports.assertProxiedToRoutes = function (options, nested) { - // - // Assign dynamic ports to the routes to use. - // - options.routes = helpers.http.assignPortsToRoutes(options.routes); - - // - // Parse locations from routes for making assertion requests. - // - var locations = helpers.http.parseRoutes(options), - protocol = helpers.protocols.proxy, - port = helpers.nextPort, - context, - proxy; - - if (options.raw) { - protocol = helpers.protocols.proxy === 'https' - ? 'wss' - : 'ws'; - } - - if (options.filename) { - // - // If we've been passed a filename write the routes to it - // and setup the proxy options to use that file. - // - fs.writeFileSync(options.filename, JSON.stringify({ router: options.routes })); - proxy = { router: options.filename }; - } - else { - // - // Otherwise just use the routes themselves. - // - proxy = { - hostnameOnly: options.hostnameOnly, - router: options.routes - }; - } - - // - // Create the test context which creates all target - // servers for all routes and a proxy server. - // - context = { - topic: function () { - var that = this; - - async.waterfall([ - // - // 1. Create all the target servers - // - async.apply( - async.forEach, - locations, - function createRouteTarget(location, next) { - helpers.ws.createServer({ - raw: options.raw, - port: location.target.port, - output: 'hello from ' + location.source.href, - input: 'hello to ' + location.source.href - }, next); - } - ), - // - // 2. Create the proxy server - // - async.apply( - helpers.http.createProxyServer, - { - port: port, - latency: options.latency, - routing: true, - proxy: proxy - } - ) - ], function (_, server) { - // - // 3. Set the proxy server for later use - // - that.proxyServer = server; - that.callback(); - }); - - // - // 4. Assign the port to the context for later use - // - this.port = port; - } - }; - - // - // Add test assertions for each of the route locations. - // - locations.forEach(function (location) { - context[location.source.href] = exports.assertSendRecieve({ - uri: protocol + '://127.0.0.1:' + port + location.source.path, - output: 'hello from ' + location.source.href, - input: 'hello to ' + location.source.href, - raw: options.raw - }); - }); - - return context; -}; \ No newline at end of file diff --git a/test/ws/routing-table-test.js b/test/ws/routing-table-test.js deleted file mode 100644 index e04d64752..000000000 --- a/test/ws/routing-table-test.js +++ /dev/null @@ -1,25 +0,0 @@ -/* - * routing-tabletest.js: Test for proxying `socket.io` and raw `WebSocket` requests using a ProxyTable. - * - * (C) 2010 Nodejitsu Inc. - * MIT LICENCE - * - */ - -var vows = require('vows'), - macros = require('../macros'), - helpers = require('../helpers/index'); - -vows.describe(helpers.describe('routing-proxy', 'ws')).addBatch({ - "With a valid target server": { - "and no latency": { - "using ws": macros.ws.assertProxied(), - "using socket.io": macros.ws.assertProxied({ - raw: true - }), - }, - // "and latency": macros.websocket.assertProxied({ - // latency: 2000 - // }) - } -}).export(module); \ No newline at end of file diff --git a/test/ws/socket.io-test.js b/test/ws/socket.io-test.js deleted file mode 100644 index d833109e6..000000000 --- a/test/ws/socket.io-test.js +++ /dev/null @@ -1,20 +0,0 @@ -/* - * socket.io-test.js: Test for proxying `socket.io` requests. - * - * (C) 2010 Nodejitsu Inc. - * MIT LICENCE - * - */ - -var vows = require('vows'), - macros = require('../macros'), - helpers = require('../helpers/index'); - -vows.describe(helpers.describe('socket.io', 'ws')).addBatch({ - "With a valid target server": { - "and no latency": macros.ws.assertProxied(), - // "and latency": macros.ws.assertProxied({ - // latency: 2000 - // }) - } -}).export(module); \ No newline at end of file diff --git a/test/ws/ws-test.js b/test/ws/ws-test.js deleted file mode 100644 index f3549152f..000000000 --- a/test/ws/ws-test.js +++ /dev/null @@ -1,23 +0,0 @@ -/* - * ws-test.js: Tests for proxying raw Websocket requests. - * - * (C) 2010 Nodejitsu Inc. - * MIT LICENCE - * - */ - -var vows = require('vows'), - macros = require('../macros'), - helpers = require('../helpers/index'); - -vows.describe(helpers.describe('websocket', 'ws')).addBatch({ - "With a valid target server": { - "and no latency": macros.ws.assertProxied({ - raw: true - }), - // "and latency": macros.ws.assertProxied({ - // raw: true, - // latency: 2000 - // }) - } -}).export(module); \ No newline at end of file