| /* |
| * 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 |
| }); |
| }; |