diff --git a/README.md b/README.md index 77fc3f3..47e857b 100644 --- a/README.md +++ b/README.md @@ -363,6 +363,12 @@ By default, `node-http-proxy` will set a 100 socket limit for all `host:port` pr 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. +## POST requests and buffering + +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. + ## 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: @@ -426,9 +432,9 @@ 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/web-socket-proxy.js -[2]: https://github.com/nodejitsu/node-http-proxy/blob/master/examples/basic-proxy-https.js -[3]: https://github.com/nodejitsu/node-http-proxy/tree/v0.5.0/examples +[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 diff --git a/examples/http/proxy-https-to-http.js b/examples/http/proxy-https-to-http.js index 75b986b..d6fce41 100644 --- a/examples/http/proxy-https-to-http.js +++ b/examples/http/proxy-https-to-http.js @@ -34,7 +34,7 @@ var https = require('https'), var opts = helpers.loadHttps(); // -// Crete the target HTTPS server +// Create the target HTTPS server // http.createServer(function (req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }); @@ -43,7 +43,7 @@ http.createServer(function (req, res) { }).listen(8000); // -// Create the proxy server listening on port 443. +// Create the proxy server listening on port 443 // httpProxy.createServer(8000, 'localhost', { https: opts diff --git a/examples/http/proxy-https-to-https.js b/examples/http/proxy-https-to-https.js index bd54e88..9acef1f 100644 --- a/examples/http/proxy-https-to-https.js +++ b/examples/http/proxy-https-to-https.js @@ -34,7 +34,7 @@ var https = require('https'), var opts = helpers.loadHttps(); // -// Crete the target HTTPS server +// Create the target HTTPS server // https.createServer(opts, function (req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }); @@ -43,7 +43,7 @@ https.createServer(opts, function (req, res) { }).listen(8000); // -// Create the proxy server listening on port 443. +// Create the proxy server listening on port 443 // httpProxy.createServer(8000, 'localhost', { https: opts, diff --git a/examples/websocket/latent-websocket-proxy.js b/examples/websocket/latent-websocket-proxy.js index 3112f6b..338d7ea 100644 --- a/examples/websocket/latent-websocket-proxy.js +++ b/examples/websocket/latent-websocket-proxy.js @@ -24,7 +24,7 @@ */ -var sys = require('sys'), +var util = require('util'), http = require('http'), colors = require('colors'), websocket = require('../../vendor/websocket'), @@ -55,10 +55,10 @@ server.listen(8080); // var socket = io.listen(server); socket.on('connection', function (client) { - sys.debug('Got websocket connection'); + util.debug('Got websocket connection'); client.on('message', function (msg) { - sys.debug('Got message from client: ' + msg); + util.debug('Got message from client: ' + msg); }); socket.broadcast('from server'); @@ -101,5 +101,5 @@ ws.on('open', function () { }); ws.on('message', function (msg) { - sys.debug('Got message: ' + utils.decode(msg)); + util.debug('Got message: ' + utils.decode(msg)); }); diff --git a/examples/websocket/standalone-websocket-proxy.js b/examples/websocket/standalone-websocket-proxy.js index bfbc252..cd4a855 100644 --- a/examples/websocket/standalone-websocket-proxy.js +++ b/examples/websocket/standalone-websocket-proxy.js @@ -24,7 +24,7 @@ */ -var sys = require('sys'), +var util = require('util'), http = require('http'), colors = require('colors'), websocket = require('../../vendor/websocket'), @@ -55,10 +55,10 @@ server.listen(8080); // var socket = io.listen(server); socket.on('connection', function (client) { - sys.debug('Got websocket connection'); + util.debug('Got websocket connection'); client.on('message', function (msg) { - sys.debug('Got message from client: ' + msg); + util.debug('Got message from client: ' + msg); }); socket.broadcast('from server'); @@ -97,5 +97,5 @@ ws.on('open', function () { }); ws.on('message', function (msg) { - sys.debug('Got message: ' + utils.decode(msg)); + util.debug('Got message: ' + utils.decode(msg)); }); diff --git a/examples/websocket/websocket-proxy.js b/examples/websocket/websocket-proxy.js index 975fab0..0b76b8a 100644 --- a/examples/websocket/websocket-proxy.js +++ b/examples/websocket/websocket-proxy.js @@ -24,7 +24,7 @@ */ -var sys = require('sys'), +var util = require('util'), http = require('http'), colors = require('colors'), websocket = require('../../vendor/websocket'), @@ -55,10 +55,10 @@ server.listen(8080); // var socket = io.listen(server); socket.on('connection', function (client) { - sys.debug('Got websocket connection'); + util.debug('Got websocket connection'); client.on('message', function (msg) { - sys.debug('Got message from client: ' + msg); + util.debug('Got message from client: ' + msg); }); socket.broadcast('from server'); @@ -80,5 +80,5 @@ ws.on('open', function () { }); ws.on('message', function (msg) { - sys.debug('Got message: ' + utils.decode(msg)); + util.debug('Got message: ' + utils.decode(msg)); }); diff --git a/lib/node-http-proxy/http-proxy.js b/lib/node-http-proxy/http-proxy.js index 127d65f..bd8434a 100644 --- a/lib/node-http-proxy/http-proxy.js +++ b/lib/node-http-proxy/http-proxy.js @@ -25,6 +25,7 @@ */ var events = require('events'), + http = require('http'), util = require('util'), httpProxy = require('../node-http-proxy'); @@ -122,17 +123,37 @@ HttpProxy.prototype.proxyRequest = function (req, res, buffer) { // // Add common proxy headers to the request so that they can - // be availible to the proxy target server: + // 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) { - req.headers['x-forwarded-for'] = req.connection.remoteAddress || req.socket.remoteAddress; - req.headers['x-forwarded-port'] = req.connection.remotePort || req.socket.remotePort; - req.headers['x-forwarded-proto'] = req.connection.pair ? 'https' : 'http'; + 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 = "," + req.connection.remotePort || req.socket.remotePort; + req.headers['x-forwarded-port'] += portToAppend; + } + else { + req.headers['x-forwarded-port'] = req.connection.remotePort || req.socket.remotePort; + } + + if (req.headers['x-forwarded-proto']){ + var protoToAppend = "," + req.connection.pair ? 'https' : 'http'; + req.headers['x-forwarded-proto'] += protoToAppend; + } + else { + req.headers['x-forwarded-proto'] = req.connection.pair ? 'https' : 'http'; + } } // @@ -213,6 +234,15 @@ HttpProxy.prototype.proxyRequest = function (req, res, buffer) { delete response.headers['transfer-encoding']; } + if ((response.statusCode === 301) || (response.statusCode === 302)) { + 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:'); + } + } + // Set the headers of the client response res.writeHead(response.statusCode, response.headers); @@ -271,11 +301,19 @@ HttpProxy.prototype.proxyRequest = function (req, res, buffer) { // reverseProxy.once('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) { @@ -352,16 +390,37 @@ HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, buffer) // // Add common proxy headers to the request so that they can - // be availible to the proxy target server: + // 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.connection.socket) { - req.headers['x-forwarded-for'] = req.connection.remoteAddress || req.connection.socket.remoteAddress; - req.headers['x-forwarded-port'] = req.connection.remotePort || req.connection.socket.remotePort; - req.headers['x-forwarded-proto'] = req.connection.pair ? 'https' : 'http'; + if (req.headers['x-forwarded-for']){ + var addressToAppend = "," + req.connection.remoteAddress || req.connection.socket.remoteAddress; + req.headers['x-forwarded-for'] += addressToAppend; + } + else { + req.headers['x-forwarded-for'] = req.connection.remoteAddress || req.connection.socket.remoteAddress; + } + + if (req.headers['x-forwarded-port']){ + var portToAppend = "," + req.connection.remotePort || req.connection.socket.remotePort; + req.headers['x-forwarded-port'] += portToAppend; + } + else { + req.headers['x-forwarded-port'] = req.connection.remotePort || req.connection.socket.remotePort; + } + + 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'; + } } // @@ -527,11 +586,13 @@ HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, buffer) // 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 = agent.appendMessage(outgoing); + var reverseProxy = this.target.protocol.request(outgoing); // // On any errors from the `reverseProxy` emit the @@ -553,7 +614,6 @@ HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, buffer) // available to the `upgrade` event. This bookkeeping is not tracked anywhere // in nodejs core and is **very** specific to proxying WebSockets. // - reverseProxy.agent = agent; reverseProxy.incoming = { request: req, socket: socket, @@ -568,24 +628,22 @@ HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, buffer) // 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. // - if (!agent._events || agent._events['upgrade'].length === 0) { - agent.on('upgrade', function (_, remoteSocket, head) { - // - // Prepare the socket for the reverseProxy request and begin to - // stream data between the two sockets. Here it is important to - // note that `remoteSocket._httpMessage === reverseProxy`. - // - _socket(remoteSocket, true); - onUpgrade(remoteSocket._httpMessage, remoteSocket); - }); - } + reverseProxy.on('upgrade', function (_, remoteSocket, head) { + // + // Prepare the socket for the reverseProxy request and begin to + // stream data between the two sockets. Here it is important to + // 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. // - if (typeof reverseProxy.socket !== 'undefined') { - reverseProxy.socket.on('data', function handshake (data) { + reverseProxy.once('socket', function (revSocket) { + revSocket.on('data', function handshake (data) { // // Ok, kind of harmfull part of code. Socket.IO sends a hash // at the end of handshake if protocol === 76, but we need @@ -618,12 +676,12 @@ HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, buffer) socket.write(sdata); var flushed = socket.write(data); if (!flushed) { - reverseProxy.socket.pause(); + revSocket.pause(); socket.once('drain', function () { - try { reverseProxy.socket.resume() } + 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. @@ -638,7 +696,7 @@ HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, buffer) // Remove data listener on socket error because the // 'handshake' has failed. // - reverseProxy.socket.removeListener('data', handshake); + revSocket.removeListener('data', handshake); return proxyError(ex); } @@ -648,9 +706,9 @@ HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, buffer) // // Remove data listener now that the 'handshake' is complete // - reverseProxy.socket.removeListener('data', handshake); + revSocket.removeListener('data', handshake); }); - } + }); reverseProxy.on('error', proxyError); @@ -689,9 +747,11 @@ HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, buffer) HttpProxy.prototype.close = function () { [this.forward, this.target].forEach(function (proxy) { if (proxy && proxy.agent) { - proxy.agent.sockets.forEach(function (socket) { - socket.end(); - }); + for (var host in proxy.agent.sockets) { + proxy.agent.sockets[host].forEach(function (socket) { + socket.end(); + }); + } } }); }; diff --git a/lib/node-http-proxy/proxy-table.js b/lib/node-http-proxy/proxy-table.js index 0cdce50..9035ab8 100644 --- a/lib/node-http-proxy/proxy-table.js +++ b/lib/node-http-proxy/proxy-table.js @@ -97,7 +97,7 @@ ProxyTable.prototype.setRoutes = function (router) { this.routes = []; Object.keys(router).forEach(function (path) { - var route = new RegExp(path, 'i'); + var route = new RegExp('^' + path, 'i'); self.routes.push({ route: route, @@ -137,7 +137,6 @@ ProxyTable.prototype.getProxyLocation = function (req) { for (var i in this.routes) { var route = this.routes[i]; if (target.match(route.route)) { - var pathSegments = route.path.split('/'); if (pathSegments.length > 1) { diff --git a/lib/node-http-proxy/routing-proxy.js b/lib/node-http-proxy/routing-proxy.js index 77b4a8d..4e4fc32 100644 --- a/lib/node-http-proxy/routing-proxy.js +++ b/lib/node-http-proxy/routing-proxy.js @@ -180,8 +180,8 @@ RoutingProxy.prototype.proxyRequest = function (req, res, options) { options.host = location.host; } - var key = options.host + ':' + options.port, - proxy; + var key = this._getKey(options), + proxy; if (!this.proxies[key]) { this.add(options); @@ -218,8 +218,8 @@ RoutingProxy.prototype.proxyWebSocketRequest = function (req, socket, head, opti options.host = location.host; } - var key = options.host + ':' + options.port, - proxy; + var key = this._getKey(options), + proxy; if (!this.proxies[key]) { this.add(options); diff --git a/package.json b/package.json index 775c029..b52eb01 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "http-proxy", - "version": "0.7.6", + "version": "0.8.0", "description": "A full-featured http reverse proxy for node.js", "author": "Charlie Robbins ", "contributors": [ @@ -22,14 +22,16 @@ "devDependencies": { "request": "1.9.x", "vows": "0.5.x", + "async": "0.1.x", "socket.io": "0.6.x" }, "main": "./lib/node-http-proxy", "bin": { "node-http-proxy": "./bin/node-http-proxy" }, "scripts": { - "test": "npm run-script test-http && npm run-script test-https", + "test": "npm run-script test-http && npm run-script test-https && npm run-script test-core", "test-http": "vows --spec && vows --spec --target=secure", - "test-https": "vows --spec --source=secure && vows --spec --source=secure --target=secure" + "test-https": "vows --spec --source=secure && vows --spec --source=secure --target=secure", + "test-core": "test/core/run" }, - "engines": { "node": "0.4.x || 0.5.x" } + "engines": { "node": ">= 0.6.6" } } diff --git a/test/core/README.md b/test/core/README.md new file mode 100644 index 0000000..152e5c6 --- /dev/null +++ b/test/core/README.md @@ -0,0 +1,10 @@ +# `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 new file mode 100644 index 0000000..9bd9dfc --- /dev/null +++ b/test/core/common.js @@ -0,0 +1,145 @@ +// 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') { + return '"' + process.argv[0] + '" "' + path.resolve(exports.fixturesDir, + 'create-file.js') + '" "' + 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.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); + } + + 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'); + } + } +}); + + +// This function allows one two run an HTTP test agaist both HTTPS and +// normal HTTP modules. This ensures they fit the same API. +exports.httpTest = function httpTest(cb) { +}; + diff --git a/test/core/pummel/test-http-upload-timeout.js b/test/core/pummel/test-http-upload-timeout.js new file mode 100644 index 0000000..f8efe41 --- /dev/null +++ b/test/core/pummel/test-http-upload-timeout.js @@ -0,0 +1,69 @@ +// 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 new file mode 100755 index 0000000..adec53b --- /dev/null +++ b/test/core/run @@ -0,0 +1,90 @@ +#!/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 new file mode 100755 index 0000000..ff1af48 --- /dev/null +++ b/test/core/run-single @@ -0,0 +1,62 @@ +#!/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]; + +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); + var testProcess = spawn(process.argv[0], [ process.argv[2] ]); + testProcess.stdout.pipe(process.stdout); + testProcess.stderr.pipe(process.stderr); + testProcess.on('exit', process.exit); +}); + +// vim:filetype=javascript + diff --git a/test/core/simple/test-http-chunked.js b/test/core/simple/test-http-chunked.js new file mode 100644 index 0000000..8a19f57 --- /dev/null +++ b/test/core/simple/test-http-chunked.js @@ -0,0 +1,63 @@ +// 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 new file mode 100644 index 0000000..c7a55e2 --- /dev/null +++ b/test/core/simple/test-http-client-abort.js @@ -0,0 +1,80 @@ +// 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 new file mode 100644 index 0000000..41c62d9 --- /dev/null +++ b/test/core/simple/test-http-client-abort2.js @@ -0,0 +1,41 @@ +// 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 new file mode 100644 index 0000000..125aa0c --- /dev/null +++ b/test/core/simple/test-http-client-upload-buf.js @@ -0,0 +1,74 @@ +// 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 new file mode 100644 index 0000000..fdd88ca --- /dev/null +++ b/test/core/simple/test-http-client-upload.js @@ -0,0 +1,77 @@ +// 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 new file mode 100644 index 0000000..1602c02 --- /dev/null +++ b/test/core/simple/test-http-contentLength0.js @@ -0,0 +1,42 @@ +// 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 new file mode 100644 index 0000000..b043a2d --- /dev/null +++ b/test/core/simple/test-http-eof-on-connect.js @@ -0,0 +1,40 @@ +// 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 new file mode 100644 index 0000000..29143d0 --- /dev/null +++ b/test/core/simple/test-http-extra-response.js @@ -0,0 +1,86 @@ +// 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 new file mode 100644 index 0000000..409698a --- /dev/null +++ b/test/core/simple/test-http-head-request.js @@ -0,0 +1,56 @@ +// 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 new file mode 100644 index 0000000..9a50e5a --- /dev/null +++ b/test/core/simple/test-http-head-response-has-no-body-end.js @@ -0,0 +1,61 @@ +// 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 new file mode 100644 index 0000000..614b648 --- /dev/null +++ b/test/core/simple/test-http-head-response-has-no-body.js @@ -0,0 +1,58 @@ +// 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 new file mode 100644 index 0000000..bb0957e --- /dev/null +++ b/test/core/simple/test-http-host-headers.js @@ -0,0 +1,101 @@ +// 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-malformed-request.js b/test/core/simple/test-http-malformed-request.js new file mode 100644 index 0000000..d453ddc --- /dev/null +++ b/test/core/simple/test-http-malformed-request.js @@ -0,0 +1,57 @@ +// 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'); +var url = require('url'); + +// Make sure no exceptions are thrown when receiving malformed HTTP +// requests. + +var nrequests_completed = 0; +var nrequests_expected = 1; + +var server = http.createServer(function(req, res) { + console.log('req: ' + JSON.stringify(url.parse(req.url))); + + res.writeHead(200, {'Content-Type': 'text/plain'}); + res.write('Hello World'); + res.end(); + + if (++nrequests_completed == nrequests_expected) server.close(); +}); +server.listen(common.PORT); + +server.on('listening', function() { + var c = net.createConnection(common.PROXY_PORT); + c.on('connect', function() { + c.write('GET /hello?foo=%99bar HTTP/1.1\r\n\r\n'); + c.end(); + }); + + // TODO add more! +}); + +process.on('exit', function() { + assert.equal(nrequests_expected, nrequests_completed); +}); diff --git a/test/core/simple/test-http-many-keep-alive-connections.js b/test/core/simple/test-http-many-keep-alive-connections.js new file mode 100644 index 0000000..7b14ff0 --- /dev/null +++ b/test/core/simple/test-http-many-keep-alive-connections.js @@ -0,0 +1,68 @@ +// 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 new file mode 100644 index 0000000..aa00a76 --- /dev/null +++ b/test/core/simple/test-http-multi-line-headers.js @@ -0,0 +1,59 @@ +// 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 new file mode 100644 index 0000000..2bbf9be --- /dev/null +++ b/test/core/simple/test-http-proxy.js @@ -0,0 +1,109 @@ +// 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 new file mode 100644 index 0000000..d4adf80 --- /dev/null +++ b/test/core/simple/test-http-response-close.js @@ -0,0 +1,55 @@ +// 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 new file mode 100644 index 0000000..52b932f --- /dev/null +++ b/test/core/simple/test-http-server-multiheaders.js @@ -0,0 +1,59 @@ +// 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 new file mode 100644 index 0000000..9510e49 --- /dev/null +++ b/test/core/simple/test-http-set-cookies.js @@ -0,0 +1,84 @@ +// 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 new file mode 100644 index 0000000..b79e4d7 --- /dev/null +++ b/test/core/simple/test-http-status-code.js @@ -0,0 +1,69 @@ +// 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 new file mode 100644 index 0000000..8825f97 --- /dev/null +++ b/test/core/simple/test-http-upgrade-server2.js @@ -0,0 +1,72 @@ +// 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 new file mode 100644 index 0000000..fcbec28 --- /dev/null +++ b/test/core/simple/test-http.js @@ -0,0 +1,108 @@ +// 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/vendor/websocket.js b/vendor/websocket.js index d8c5eeb..406023c 100644 --- a/vendor/websocket.js +++ b/vendor/websocket.js @@ -496,7 +496,7 @@ var WebSocket = function(url, proto, opts) { if (u.protocol === 'ws:' || u.protocol === 'wss:') { protocol = u.protocol === 'ws:' ? http : https; port = u.protocol === 'ws:' ? 80 : 443; - agent = u.protocol === 'ws:' ? protocol.getAgent(u.hostname, u.port || port) : protocol.getAgent({ + agent = u.protocol === new protocol.Agent({ host: u.hostname, port: u.port || port }); @@ -514,105 +514,6 @@ var WebSocket = function(url, proto, opts) { throw new Error('Invalid URL scheme \'' + urlScheme + '\' specified.'); } - if (!agent._events || agent._events['upgrade'].length === 0) { - agent.on('upgrade', (function() { - var data = undefined; - - return function(res, s, head) { - stream = s; - - // - // Emit the `wsupgrade` event to inspect the raw - // arguments returned from the websocket request. - // - self.emit('wsupgrade', httpHeaders, res, s, head); - - stream.on('data', function(d) { - if (d.length <= 0) { - return; - } - - if (!data) { - data = d; - } else { - var data2 = new buffer.Buffer(data.length + d.length); - - data.copy(data2, 0, 0, data.length); - d.copy(data2, data.length, 0, d.length); - - data = data2; - } - - if (data.length >= 16) { - var expected = computeSecretKeySignature(key1, key2, challenge); - var actual = data.slice(0, 16).toString('binary'); - - // Handshaking fails; we're donezo - if (actual != expected) { - debug( - 'expected=\'' + str2hex(expected) + '\'; ' + - 'actual=\'' + str2hex(actual) + '\'' - ); - - process.nextTick(function() { - // N.B. Emit 'wserror' here, as 'error' is a reserved word in the - // EventEmitter world, and gets thrown. - self.emit( - 'wserror', - new Error('Invalid handshake from server:' + - 'expected \'' + str2hex(expected) + '\', ' + - 'actual \'' + str2hex(actual) + '\'' - ) - ); - - if (self.onerror) { - self.onerror(); - } - - finishClose(); - }); - } - - // - // Un-register our data handler and add the one to be used - // for the normal, non-handshaking case. If we have extra - // data left over, manually fire off the handler on - // whatever remains. - // - stream.removeAllListeners('data'); - stream.on('data', dataListener); - - readyState = OPEN; - - process.nextTick(function() { - self.emit('open'); - - if (self.onopen) { - self.onopen(); - } - }); - - // Consume any leftover data - if (data.length > 16) { - stream.emit('data', data.slice(16, data.length)); - } - } - }); - stream.on('fd', fdListener); - stream.on('error', errorListener); - stream.on('close', function() { - errorListener(new Error('Stream closed unexpectedly.')); - }); - - stream.emit('data', head); - }; - })()); - } - - agent.on('error', function (e) { - errorListener(e); - }); - var httpReq = protocol.request({ host: u.hostname, method: 'GET', @@ -622,6 +523,103 @@ var WebSocket = function(url, proto, opts) { headers: httpHeaders }); + httpReq.on('error', function (e) { + errorListener(e); + }); + + httpReq.on('upgrade', (function() { + var data = undefined; + + return function(res, s, head) { + stream = s; + + // + // Emit the `wsupgrade` event to inspect the raw + // arguments returned from the websocket request. + // + self.emit('wsupgrade', httpHeaders, res, s, head); + + stream.on('data', function(d) { + if (d.length <= 0) { + return; + } + + if (!data) { + data = d; + } else { + var data2 = new buffer.Buffer(data.length + d.length); + + data.copy(data2, 0, 0, data.length); + d.copy(data2, data.length, 0, d.length); + + data = data2; + } + + if (data.length >= 16) { + var expected = computeSecretKeySignature(key1, key2, challenge); + var actual = data.slice(0, 16).toString('binary'); + + // Handshaking fails; we're donezo + if (actual != expected) { + debug( + 'expected=\'' + str2hex(expected) + '\'; ' + + 'actual=\'' + str2hex(actual) + '\'' + ); + + process.nextTick(function() { + // N.B. Emit 'wserror' here, as 'error' is a reserved word in the + // EventEmitter world, and gets thrown. + self.emit( + 'wserror', + new Error('Invalid handshake from server:' + + 'expected \'' + str2hex(expected) + '\', ' + + 'actual \'' + str2hex(actual) + '\'' + ) + ); + + if (self.onerror) { + self.onerror(); + } + + finishClose(); + }); + } + + // + // Un-register our data handler and add the one to be used + // for the normal, non-handshaking case. If we have extra + // data left over, manually fire off the handler on + // whatever remains. + // + stream.removeAllListeners('data'); + stream.on('data', dataListener); + + readyState = OPEN; + + process.nextTick(function() { + self.emit('open'); + + if (self.onopen) { + self.onopen(); + } + }); + + // Consume any leftover data + if (data.length > 16) { + stream.emit('data', data.slice(16, data.length)); + } + } + }); + stream.on('fd', fdListener); + stream.on('error', errorListener); + stream.on('close', function() { + errorListener(new Error('Stream closed unexpectedly.')); + }); + + stream.emit('data', head); + }; + })()); + httpReq.write(challenge, 'binary'); httpReq.end(); })();