├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── index.js ├── package.json └── test ├── server.crt ├── server.csr ├── server.key └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /test 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | - "0.8" 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (Expat) 2 | 3 | Copyright (c) 2014 Andrew Kelley 4 | 5 | Permission is hereby granted, free of charge, to any person 6 | obtaining a copy of this software and associated documentation files 7 | (the "Software"), to deal in the Software without restriction, 8 | including without limitation the rights to use, copy, modify, merge, 9 | publish, distribute, sublicense, and/or sell copies of the Software, 10 | and to permit persons to whom the Software is furnished to do so, 11 | subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 20 | BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 21 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 22 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://secure.travis-ci.org/andrewrk/node-proxy-middleware.png)](http://travis-ci.org/andrewrk/node-proxy-middleware) 2 | 3 | ### Usage: 4 | 5 | ```js 6 | var connect = require('connect'); 7 | var url = require('url'); 8 | var proxy = require('proxy-middleware'); 9 | 10 | var app = connect(); 11 | app.use('/api', proxy(url.parse('https://example.com/endpoint'))); 12 | // now requests to '/api/x/y/z' are proxied to 'https://example.com/endpoint/x/y/z' 13 | 14 | //same as example above but also uses a short hand string only parameter 15 | app.use('/api-string-only', proxy('https://example.com/endpoint')); 16 | ``` 17 | 18 | ### Documentation: 19 | 20 | `proxyMiddleware(options)` 21 | 22 | `options` allows any options that are permitted on the [`http`](http://nodejs.org/api/http.html#http_http_request_options_callback) or [`https`](http://nodejs.org/api/https.html#https_https_request_options_callback) request options. 23 | 24 | Other options: 25 | - `route`: you can pass the route for connect middleware within the options, as well. 26 | - `via`: by default no [via header](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.45) is added. If you pass `true` for this option the local hostname will be used for the via header. You can also pass a string for this option in which case that will be used for the via header. 27 | - `cookieRewrite`: this option can be used to support cookies via the proxy by rewriting the cookie domain to that of the proxy server. By default cookie domains are not rewritten. The `cookieRewrite` option works as the `via` option - if you pass `true` the local hostname will be used, and if you pass a string that will be used as the rewritten cookie domain. 28 | - `preserveHost`: When enabled, this option will pass the Host: line from the incoming request to the proxied host. Default: `false`. 29 | 30 | ### Usage with route: 31 | 32 | ```js 33 | var proxyOptions = url.parse('https://example.com/endpoint'); 34 | proxyOptions.route = '/api'; 35 | 36 | var middleWares = [proxy(proxyOptions) /*, ...*/]; 37 | 38 | // Grunt connect uses this method 39 | connect(middleWares); 40 | ``` 41 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var os = require('os'); 2 | var http = require('http'); 3 | var https = require('https'); 4 | var owns = {}.hasOwnProperty; 5 | 6 | module.exports = function proxyMiddleware(options) { 7 | //enable ability to quickly pass a url for shorthand setup 8 | if(typeof options === 'string'){ 9 | options = require('url').parse(options); 10 | } 11 | 12 | var httpLib = options.protocol === 'https:' ? https : http; 13 | var request = httpLib.request; 14 | 15 | options = options || {}; 16 | options.hostname = options.hostname; 17 | options.port = options.port; 18 | options.pathname = options.pathname || '/'; 19 | 20 | return function (req, resp, next) { 21 | var url = req.url; 22 | // You can pass the route within the options, as well 23 | if (typeof options.route === 'string') { 24 | if (url === options.route) { 25 | url = ''; 26 | } else if (url.slice(0, options.route.length) === options.route) { 27 | url = url.slice(options.route.length); 28 | } else { 29 | return next(); 30 | } 31 | } 32 | 33 | //options for this request 34 | var opts = extend({}, options); 35 | if (url && url.charAt(0) === '?') { // prevent /api/resource/?offset=0 36 | if (options.pathname.length > 1 && options.pathname.charAt(options.pathname.length - 1) === '/') { 37 | opts.path = options.pathname.substring(0, options.pathname.length - 1) + url; 38 | } else { 39 | opts.path = options.pathname + url; 40 | } 41 | } else if (url) { 42 | opts.path = slashJoin(options.pathname, url); 43 | } else { 44 | opts.path = options.pathname; 45 | } 46 | opts.method = req.method; 47 | opts.headers = options.headers ? merge(req.headers, options.headers) : req.headers; 48 | 49 | applyViaHeader(req.headers, opts, opts.headers); 50 | 51 | if (!options.preserveHost) { 52 | // Forwarding the host breaks dotcloud 53 | delete opts.headers.host; 54 | } 55 | 56 | var myReq = request(opts, function (myRes) { 57 | var statusCode = myRes.statusCode 58 | , headers = myRes.headers 59 | , location = headers.location; 60 | // Fix the location 61 | if (((statusCode > 300 && statusCode < 304) || statusCode === 201) && location && location.indexOf(options.href) > -1) { 62 | // absoulte path 63 | headers.location = location.replace(options.href, slashJoin('/', slashJoin((options.route || ''), ''))); 64 | } 65 | applyViaHeader(myRes.headers, opts, myRes.headers); 66 | rewriteCookieHosts(myRes.headers, opts, myRes.headers, req); 67 | resp.writeHead(myRes.statusCode, myRes.headers); 68 | myRes.on('error', function (err) { 69 | next(err); 70 | }); 71 | myRes.pipe(resp); 72 | }); 73 | myReq.on('error', function (err) { 74 | next(err); 75 | }); 76 | if (!req.readable) { 77 | myReq.end(); 78 | } else { 79 | req.pipe(myReq); 80 | } 81 | }; 82 | }; 83 | 84 | function applyViaHeader(existingHeaders, opts, applyTo) { 85 | if (!opts.via) return; 86 | 87 | var viaName = (true === opts.via) ? os.hostname() : opts.via; 88 | var viaHeader = '1.1 ' + viaName; 89 | if(existingHeaders.via) { 90 | viaHeader = existingHeaders.via + ', ' + viaHeader; 91 | } 92 | 93 | applyTo.via = viaHeader; 94 | } 95 | 96 | function rewriteCookieHosts(existingHeaders, opts, applyTo, req) { 97 | if (!opts.cookieRewrite || !owns.call(existingHeaders, 'set-cookie')) { 98 | return; 99 | } 100 | 101 | var existingCookies = existingHeaders['set-cookie'], 102 | rewrittenCookies = [], 103 | rewriteHostname = (true === opts.cookieRewrite) ? os.hostname() : opts.cookieRewrite; 104 | 105 | if (!Array.isArray(existingCookies)) { 106 | existingCookies = [ existingCookies ]; 107 | } 108 | 109 | for (var i = 0; i < existingCookies.length; i++) { 110 | var rewrittenCookie = existingCookies[i].replace(/(Domain)=[a-z\.-_]*?(;|$)/gi, '$1=' + rewriteHostname + '$2'); 111 | 112 | if (!req.connection.encrypted) { 113 | rewrittenCookie = rewrittenCookie.replace(/;\s*?(Secure)/i, ''); 114 | } 115 | rewrittenCookies.push(rewrittenCookie); 116 | } 117 | 118 | applyTo['set-cookie'] = rewrittenCookies; 119 | } 120 | 121 | function slashJoin(p1, p2) { 122 | var trailing_slash = false; 123 | 124 | if (p1.length && p1[p1.length - 1] === '/') { trailing_slash = true; } 125 | if (trailing_slash && p2.length && p2[0] === '/') {p2 = p2.substring(1); } 126 | 127 | return p1 + p2; 128 | } 129 | 130 | function extend(obj, src) { 131 | for (var key in src) if (owns.call(src, key)) obj[key] = src[key]; 132 | return obj; 133 | } 134 | 135 | //merges data without changing state in either argument 136 | function merge(src1, src2) { 137 | var merged = {}; 138 | extend(merged, src1); 139 | extend(merged, src2); 140 | return merged; 141 | } 142 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "proxy-middleware", 3 | "version": "0.15.0", 4 | "description": "http(s) proxy as connect middleware", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/andrewrk/connect-proxy" 12 | }, 13 | "keywords": [ 14 | "connect", 15 | "proxy", 16 | "middleware", 17 | "https", 18 | "http", 19 | "ssl" 20 | ], 21 | "author": "Andrew Kelley", 22 | "license": "MIT", 23 | "engines": { 24 | "node": ">=0.8.0" 25 | }, 26 | "devDependencies": { 27 | "connect": "~3.3.5", 28 | "mocha": "~2.2.5", 29 | "serve-static": "~1.9.3" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICATCCAWoCCQCmqsEOtNq7DzANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJB 3 | VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0 4 | cyBQdHkgTHRkMB4XDTEyMTAyMzEzMjEyNloXDTQwMDMwOTEzMjEyNlowRTELMAkG 5 | A1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0 6 | IFdpZGdpdHMgUHR5IEx0ZDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAzlDu 7 | frvJv31gdjECi87Em1mjC7ARK38OdPJd1xdL+y46WrHuNGkA8zge8hXPLDHauXN3 8 | jYMWC9wtdUXdvFFvYjDcVT5VyTLZ1+QUb/0kLVWDBdWkWdmgEN/iRgVXK2kAiMCq 9 | Q0qnNik+0WnC5F87+DWlDQ4pVmRplVWXGZzTO50CAwEAATANBgkqhkiG9w0BAQUF 10 | AAOBgQC33nT+S9kCbpTqMGvdqUdioYdk2V1CbOZ2dKbswPeLhLdNGdcs5V00c6ub 11 | kM7j90BnXBPcjD+2doSOAav4PBHTD+POz9mzMyJXyEtDnrCBQOL0x29OGyjMEA+Y 12 | s6bMXIFjSaUAgcc8nulQ7FI5m64SY6UoDV0y3EZYWMvBiT6gmw== 13 | -----END CERTIFICATE----- 14 | -------------------------------------------------------------------------------- /test/server.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIIBhDCB7gIBADBFMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEh 3 | MB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEB 4 | AQUAA4GNADCBiQKBgQDOUO5+u8m/fWB2MQKLzsSbWaMLsBErfw508l3XF0v7Ljpa 5 | se40aQDzOB7yFc8sMdq5c3eNgxYL3C11Rd28UW9iMNxVPlXJMtnX5BRv/SQtVYMF 6 | 1aRZ2aAQ3+JGBVcraQCIwKpDSqc2KT7RacLkXzv4NaUNDilWZGmVVZcZnNM7nQID 7 | AQABoAAwDQYJKoZIhvcNAQEFBQADgYEAZKfQs9HaFRXgn+ERQVe1JgllBXpab6OU 8 | 71QcNwbnAyUrBIf6G4Y2d347d6CTKm8uLwYelOdJPgM3cr8w1ODw+IyqYZx6oDyC 9 | xtushtU/yq8qXvb4G0m9v80liESbX+cCIJNHjIt0MpLHyDdyxxTmKwWRGrJW/kce 10 | 9AqcbRe7wIY= 11 | -----END CERTIFICATE REQUEST----- 12 | -------------------------------------------------------------------------------- /test/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXQIBAAKBgQDOUO5+u8m/fWB2MQKLzsSbWaMLsBErfw508l3XF0v7Ljpase40 3 | aQDzOB7yFc8sMdq5c3eNgxYL3C11Rd28UW9iMNxVPlXJMtnX5BRv/SQtVYMF1aRZ 4 | 2aAQ3+JGBVcraQCIwKpDSqc2KT7RacLkXzv4NaUNDilWZGmVVZcZnNM7nQIDAQAB 5 | AoGAT5IvKXnLqar1VSA51yn78d50X8GcHaZ3MOt4/cfiahG+N9SABn6o+yUd6kg3 6 | Cth/fx7lcRKpohFdBunXRxYxWn9xXy9oPV+b61ptuube4evUxUFLL4/kOForamud 7 | s0h952zacSO6v1akq2xxo2x2OEIV3yb7Z48tTmTpu3UaVBECQQD72L8yRzFiBj6R 8 | vTKXvanoMxKFM/tWoLH+bK/qgvz02ALd8s66sAt1ET/UftApAl1sZfVVYjjCj+Y9 9 | qDsRhcSjAkEA0bf2daOXSmRm5mdkFGW8UeX/plJM0SYZLLGeyMoQfhzGb1uiHgEa 10 | FFrsCWe7Q9x1Y8fuohrzjcmqXxzY9xbCvwJAGgcDc3nCE8RbcQphkScLyTCGCHgf 11 | 0IsY+hdXcuAIrnEokrGGRerttDKNKzPT5XkWGJ7M5P8aqJjgjZy2RhBSCwJBAJIj 12 | QxbOnEo2lI3RVifwjL31K6RWjTjg94Hxc9gvFjcLDivjCqEl22p7wSb8pb2wDg0t 13 | 8Nf9N+KBuLr2eyoTLjkCQQD4bFqOpkakNsVJ9E5aS7oF8Ayf21PQbGHraSk4Mo5m 14 | eu4Wt8jVCTk59gJY0MUCR4ADSbBW32tHvWhN4Aqv4d7w 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | var connect = require('connect'); 2 | var assert = require('assert'); 3 | var proxy = require('../'); 4 | var fs = require('fs'); 5 | var url = require('url'); 6 | var path = require('path'); 7 | var http = require('http'); 8 | var exec = require('child_process').exec; 9 | var serveStatic = require('serve-static'); 10 | var key = fs.readFileSync(path.join(__dirname, "server.key")); 11 | var cert = fs.readFileSync(path.join(__dirname, "server.crt")); 12 | var describe = global.describe; 13 | var it = global.it; 14 | 15 | describe("proxy", function() { 16 | it("http -> https", function(done) { 17 | testWith('http', 'https', done); 18 | }); 19 | 20 | it("https -> http", function(done) { 21 | testWith('https', 'http', done); 22 | }); 23 | 24 | it("http -> http", function(done) { 25 | testWith('http', 'http', done); 26 | }); 27 | 28 | it("https -> https", function(done) { 29 | testWith('https', 'https', done); 30 | }); 31 | 32 | it("Can still proxy empty requests if the request stream has ended.", function(done) { 33 | var destServer = createServerWithLibName('http', function(req, resp) { 34 | resp.statusCode = 200; 35 | resp.write(req.url); 36 | resp.end(); 37 | }); 38 | 39 | var app = connect(); 40 | //serveStatic causes the incoming request stream to be ended for GETs. 41 | app.use(serveStatic(path.resolve('.'))); 42 | app.use('/foo', proxy(url.parse('http://localhost:8001/'))); 43 | 44 | destServer.listen(8001, 'localhost', function() { 45 | app.listen(8000); 46 | http.get('http://localhost:8000/foo/test/', function(res) { 47 | var data = ''; 48 | res.on('data', function (chunk) { 49 | data += chunk; 50 | }); 51 | res.on('end', function () { 52 | assert.strictEqual(data, '/test/'); 53 | destServer.close(); 54 | done(); 55 | }); 56 | }).on('error', function () { 57 | assert.fail('Request proxy failed'); 58 | }); 59 | }); 60 | }); 61 | 62 | it("can proxy just the given route.", function(done) { 63 | var destServer = createServerWithLibName('http', function(req, resp) { 64 | resp.statusCode = 200; 65 | resp.write(req.url); 66 | resp.end(); 67 | }); 68 | 69 | var proxyOptions = url.parse('http://localhost:8003/'); 70 | proxyOptions.route = '/foo'; 71 | 72 | var app = connect(); 73 | app.use(serveStatic(path.resolve('.'))); 74 | app.use(proxy(proxyOptions)); 75 | 76 | destServer.listen(8003, 'localhost', function() { 77 | app.listen(8002); 78 | http.get('http://localhost:8002/foo/test/', function(res) { 79 | var data = ''; 80 | res.on('data', function (chunk) { 81 | data += chunk; 82 | }); 83 | res.on('end', function () { 84 | assert.strictEqual(data, '/test/'); 85 | destServer.close(); 86 | done(); 87 | }); 88 | }).on('error', function () { 89 | assert.fail('Request proxy failed'); 90 | }); 91 | }); 92 | }); 93 | 94 | it("Can proxy just the given route with query.", function(done) { 95 | var destServer = createServerWithLibName('http', function(req, resp) { 96 | resp.statusCode = 200; 97 | resp.write(req.url); 98 | resp.end(); 99 | }); 100 | 101 | var proxyOptions = url.parse('http://localhost:8021'); 102 | proxyOptions.route = '/foo'; 103 | 104 | var app = connect(); 105 | app.use(serveStatic(path.resolve('.'))); 106 | app.use(proxy(proxyOptions)); 107 | 108 | destServer.listen(8021, 'localhost', function() { 109 | app.listen(8022); 110 | http.get('http://localhost:8022/foo?baz=true', function(res) { 111 | var data = ''; 112 | res.on('data', function (chunk) { 113 | data += chunk; 114 | }); 115 | res.on('end', function () { 116 | assert.strictEqual(data, '/?baz=true'); 117 | destServer.close(); 118 | done(); 119 | }); 120 | }).on('error', function () { 121 | assert.fail('Request proxy failed'); 122 | }); 123 | }); 124 | }); 125 | 126 | it("can proxy an exact url.", function(done) { 127 | var destServer = createServerWithLibName('http', function(req, resp) { 128 | resp.statusCode = 200; 129 | resp.write(req.url); 130 | resp.end(); 131 | }); 132 | 133 | var proxyOptions = url.parse('http://localhost:8074/foo'); 134 | proxyOptions.route = '/foo'; 135 | 136 | var app = connect(); 137 | app.use(serveStatic(path.resolve('.'))); 138 | app.use(proxy(proxyOptions)); 139 | 140 | destServer.listen(8074, 'localhost', function() { 141 | app.listen(8075); 142 | http.get('http://localhost:8075/foo', function(res) { 143 | var data = ''; 144 | res.on('data', function (chunk) { 145 | data += chunk; 146 | }); 147 | res.on('end', function () { 148 | assert.strictEqual(data, '/foo'); 149 | destServer.close(); 150 | done(); 151 | }); 152 | }).on('error', function () { 153 | assert.fail('Request proxy failed'); 154 | }); 155 | }); 156 | }); 157 | 158 | it("Can proxy url with query.", function(done) { 159 | var destServer = createServerWithLibName('http', function(req, resp) { 160 | resp.statusCode = 200; 161 | resp.write(req.url); 162 | resp.end(); 163 | }); 164 | 165 | var proxyOptions = url.parse('http://localhost:8028/foo-bar'); 166 | proxyOptions.route = '/foo-bar'; 167 | 168 | var app = connect(); 169 | app.use(serveStatic(path.resolve('.'))); 170 | app.use(proxy(proxyOptions)); 171 | 172 | destServer.listen(8028, 'localhost', function() { 173 | app.listen(8029); 174 | http.get('http://localhost:8029/foo-bar?baz=true', function(res) { 175 | 176 | var data = ''; 177 | res.on('data', function (chunk) { 178 | data += chunk; 179 | }); 180 | res.on('end', function () { 181 | assert.strictEqual(data, '/foo-bar?baz=true'); 182 | destServer.close(); 183 | done(); 184 | }); 185 | }).on('error', function () { 186 | assert.fail('Request proxy failed'); 187 | }); 188 | }); 189 | }); 190 | 191 | it("Does not keep header data across requests.", function(done) { 192 | var headerValues = ['foo', 'bar']; 193 | var reqIdx = 0; 194 | 195 | var destServer = createServerWithLibName('http', function(req, resp) { 196 | assert.strictEqual(req.headers['some-header'], headerValues[reqIdx]); 197 | reqIdx++; 198 | resp.statusCode = 200; 199 | resp.write(req.url); 200 | resp.end(); 201 | }); 202 | 203 | var app = connect(); 204 | app.use(proxy(url.parse('http://localhost:8005/'))); 205 | 206 | destServer.listen(8005, 'localhost', function() { 207 | app.listen(8004); 208 | 209 | var options = url.parse('http://localhost:8004/foo/test/'); 210 | 211 | //Get with 0 content length, then 56; 212 | options.headers = {'some-header': headerValues[0]}; 213 | http.get(options, function () { 214 | 215 | options.headers['some-header'] = headerValues[1]; 216 | http.get(options, function () { 217 | destServer.close(); 218 | done(); 219 | }).on('error', function () { 220 | assert.fail('Request proxy failed'); 221 | }); 222 | 223 | }).on('error', function () { 224 | assert.fail('Request proxy failed'); 225 | }); 226 | }); 227 | }); 228 | 229 | it("correctly applies the via header to the request", function(done) { 230 | 231 | var destServer = createServerWithLibName('http', function(req, resp) { 232 | assert.strictEqual(req.headers.via, '1.1 my-proxy-name'); 233 | resp.statusCode = 200; 234 | resp.write(req.url); 235 | resp.end(); 236 | }); 237 | 238 | var proxyOptions = url.parse('http://localhost:8015/'); 239 | proxyOptions.via = 'my-proxy-name'; 240 | var app = connect(); 241 | app.use(proxy(proxyOptions)); 242 | 243 | destServer.listen(8015, 'localhost', function() { 244 | app.listen(8014); 245 | 246 | var options = url.parse('http://localhost:8014/foo/test/'); 247 | 248 | http.get(options, function () { 249 | // ok... 250 | done(); 251 | }).on('error', function () { 252 | assert.fail('Request proxy failed'); 253 | }); 254 | }); 255 | }); 256 | 257 | it("correctly applies the via header to the request where the request has an existing via header", function(done) { 258 | 259 | var destServer = createServerWithLibName('http', function(req, resp) { 260 | assert.strictEqual(req.headers.via, '1.0 other-proxy-name, 1.1 my-proxy-name'); 261 | resp.statusCode = 200; 262 | resp.write(req.url); 263 | resp.end(); 264 | }); 265 | 266 | var proxyOptions = url.parse('http://localhost:8025/'); 267 | proxyOptions.via = 'my-proxy-name'; 268 | var app = connect(); 269 | app.use(proxy(proxyOptions)); 270 | 271 | destServer.listen(8025, 'localhost', function() { 272 | app.listen(8024); 273 | 274 | var options = url.parse('http://localhost:8024/foo/test/'); 275 | options.headers = {via: '1.0 other-proxy-name'}; 276 | 277 | http.get(options, function () { 278 | // ok... 279 | done(); 280 | }).on('error', function () { 281 | assert.fail('Request proxy failed'); 282 | }); 283 | }); 284 | }); 285 | 286 | it("correctly applies the via header to the response", function(done) { 287 | 288 | var destServer = createServerWithLibName('http', function(req, resp) { 289 | resp.statusCode = 200; 290 | resp.write(req.url); 291 | resp.end(); 292 | }); 293 | 294 | var proxyOptions = url.parse('http://localhost:8035/'); 295 | proxyOptions.via = 'my-proxy-name'; 296 | var app = connect(); 297 | app.use(proxy(proxyOptions)); 298 | 299 | destServer.listen(8035, 'localhost', function() { 300 | app.listen(8034); 301 | 302 | var options = url.parse('http://localhost:8034/foo/test/'); 303 | 304 | http.get(options, function (res) { 305 | assert.strictEqual('1.1 my-proxy-name', res.headers.via); 306 | done(); 307 | }).on('error', function () { 308 | assert.fail('Request proxy failed'); 309 | }); 310 | }); 311 | }); 312 | 313 | it("correctly applies the via header to the response where the response has an existing via header", function(done) { 314 | 315 | var destServer = createServerWithLibName('http', function(req, resp) { 316 | resp.statusCode = 200; 317 | resp.setHeader('via', '1.0 other-proxy-name'); 318 | resp.write(req.url); 319 | resp.end(); 320 | }); 321 | 322 | var proxyOptions = url.parse('http://localhost:8045/'); 323 | proxyOptions.via = 'my-proxy-name'; 324 | var app = connect(); 325 | app.use(proxy(proxyOptions)); 326 | 327 | destServer.listen(8045, 'localhost', function() { 328 | app.listen(8044); 329 | 330 | var options = url.parse('http://localhost:8044/foo/test/'); 331 | 332 | http.get(options, function (res) { 333 | assert.strictEqual('1.0 other-proxy-name, 1.1 my-proxy-name', res.headers.via); 334 | done(); 335 | }).on('error', function () { 336 | assert.fail('Request proxy failed'); 337 | }); 338 | }); 339 | }); 340 | 341 | it("correctly applies the location header to the response when the response status code is 3xx", function(done) { 342 | var destServer = createServerWithLibName('http', function(req, resp) { 343 | resp.statusCode = 302; 344 | resp.setHeader('location', 'http://localhost:8055/foo/redirect/'); 345 | resp.write(req.url); 346 | resp.end(); 347 | }); 348 | 349 | var proxyOptions = url.parse('http://localhost:8055/'); 350 | var app = connect(); 351 | app.use(proxy(proxyOptions)); 352 | 353 | destServer.listen(8055, 'localhost', function() { 354 | app.listen(8054); 355 | 356 | var options = url.parse('http://localhost:8054/foo/test/'); 357 | 358 | http.get(options, function (res) { 359 | assert.strictEqual(res.headers.location, '/foo/redirect/'); 360 | done(); 361 | }).on('error', function () { 362 | assert.fail('Request proxy failed'); 363 | }); 364 | }) 365 | }); 366 | 367 | 368 | it("correctly rewrites the cookie domain for set-cookie headers", function(done) { 369 | var cookie1 = function(host) { return 'cookie1=value1; Expires=Fri, 01-Mar-2019 00:00:01 GMT; Path=/; Domain=' + host + '; HttpOnly'; }; 370 | var cookie2 = function(host) { return 'cookie2=value2; Expires=Fri, 01-Mar-2019 00:00:01 GMT; Domain=' + host + '; Path=/test/'; }; 371 | var cookie3 = function(host) { return 'cookie3=value3'; }; 372 | var cookie4 = function(host) { return 'cookie4=value4; Expires=Fri, 01-Mar-2019 00:00:01 GMT; Domain=' + host; }; 373 | var destServer = createServerWithLibName('http', function(req, resp) { 374 | resp.statusCode = 200; 375 | resp.setHeader('set-cookie', [ 376 | cookie1('.server.com'), 377 | cookie2('.server.com'), 378 | cookie3('.server.com'), 379 | cookie4('.server.com') 380 | ]); 381 | resp.write(req.url); 382 | resp.end(); 383 | }); 384 | 385 | var proxyOptions = url.parse('http://localhost:8065/'); 386 | proxyOptions.cookieRewrite = ".proxy.com"; 387 | var app = connect(); 388 | app.use(proxy(proxyOptions)); 389 | 390 | destServer.listen(8065, 'localhost', function() { 391 | app.listen(8064); 392 | 393 | var options = url.parse('http://localhost:8064/foo/test/'); 394 | 395 | http.get(options, function (res) { 396 | var cookies = res.headers['set-cookie']; 397 | assert.strictEqual(cookies[0], cookie1(proxyOptions.cookieRewrite)); 398 | assert.strictEqual(cookies[1], cookie2(proxyOptions.cookieRewrite)); 399 | assert.strictEqual(cookies[2], cookie3(proxyOptions.cookieRewrite)); 400 | assert.strictEqual(cookies[3], cookie4(proxyOptions.cookieRewrite)); 401 | done(); 402 | }).on('error', function () { 403 | assert.fail('Request proxy failed'); 404 | }); 405 | }) 406 | }); 407 | 408 | it("removes the Secure directive when proxying from https to http", function(done) { 409 | var cookie1 = function(host, after) { 410 | if (after) { 411 | return 'cookie1=value1; Expires=Fri, 01-Mar-2019 00:00:01 GMT; Domain=' + host; 412 | } else { 413 | return 'cookie1=value1; Expires=Fri, 01-Mar-2019 00:00:01 GMT; Domain=' + host+';Secure'; 414 | } 415 | }; 416 | 417 | var destServer = createServerWithLibName('https', function(req, resp) { 418 | resp.statusCode = 200; 419 | resp.setHeader('set-cookie', [ 420 | cookie1('.server.com') 421 | ]); 422 | resp.write(req.url); 423 | resp.end(); 424 | }); 425 | 426 | var proxyOptions = url.parse('https://localhost:8066/'); 427 | proxyOptions.cookieRewrite = ".proxy.com"; 428 | proxyOptions.rejectUnauthorized = false; 429 | var app = connect(); 430 | app.use(proxy(proxyOptions)); 431 | 432 | destServer.listen(8066, 'localhost', function() { 433 | app.listen(8067); 434 | 435 | var options = url.parse('http://localhost:8067/foo/test/'); 436 | 437 | http.get(options, function (res) { 438 | var cookies = res.headers['set-cookie']; 439 | assert.strictEqual(cookies[0], cookie1(proxyOptions.cookieRewrite, true)); 440 | done(); 441 | }).on('error', function () { 442 | assert.fail('Request proxy failed'); 443 | }); 444 | }) 445 | }); 446 | 447 | it("does not forward the Host header with default options", function(done) { 448 | var destServer = createServerWithLibName('http', function(req, resp) { 449 | assert.strictEqual(req.headers.host, 'localhost:8068'); 450 | resp.statusCode = 200; 451 | resp.write(req.url); 452 | resp.end(); 453 | }); 454 | 455 | var proxyOptions = url.parse('http://localhost:8068/'); 456 | var app = connect(); 457 | app.use(proxy(proxyOptions)); 458 | 459 | destServer.listen(8068, 'localhost', function() { 460 | app.listen(8069); 461 | 462 | var options = url.parse('http://localhost:8069/foo/test/'); 463 | http.get(options, function () { 464 | // ok... 465 | done(); 466 | }).on('error', function () { 467 | assert.fail('Request proxy failed'); 468 | }); 469 | }); 470 | }); 471 | 472 | it("does not forward the Host header with options.preserveHost = false", function(done) { 473 | var destServer = createServerWithLibName('http', function(req, resp) { 474 | assert.strictEqual(req.headers.host, 'localhost:8070'); 475 | resp.statusCode = 200; 476 | resp.write(req.url); 477 | resp.end(); 478 | }); 479 | 480 | var proxyOptions = url.parse('http://localhost:8070/'); 481 | proxyOptions.preserveHost = false; 482 | var app = connect(); 483 | app.use(proxy(proxyOptions)); 484 | 485 | destServer.listen(8070, 'localhost', function() { 486 | app.listen(8071); 487 | 488 | var options = url.parse('http://localhost:8071/foo/test/'); 489 | http.get(options, function () { 490 | // ok... 491 | done(); 492 | }).on('error', function () { 493 | assert.fail('Request proxy failed'); 494 | }); 495 | }); 496 | }); 497 | 498 | it("forwards the Host header with options.preserveHost = true", function(done) { 499 | var destServer = createServerWithLibName('http', function(req, resp) { 500 | assert.strictEqual(req.headers.host, 'localhost:8073'); 501 | resp.statusCode = 200; 502 | resp.write(req.url); 503 | resp.end(); 504 | }); 505 | 506 | var proxyOptions = url.parse('http://localhost:8072/'); 507 | proxyOptions.preserveHost = true; 508 | var app = connect(); 509 | app.use(proxy(proxyOptions)); 510 | 511 | destServer.listen(8072, 'localhost', function() { 512 | app.listen(8073); 513 | 514 | var options = url.parse('http://localhost:8073/foo/test/'); 515 | http.get(options, function () { 516 | // ok... 517 | done(); 518 | }).on('error', function () { 519 | assert.fail('Request proxy failed'); 520 | }); 521 | }); 522 | }); 523 | 524 | it("correctly applies the location header to the response when the response status code is 201", function(done) { 525 | var destServer = createServerWithLibName('http', function(req, resp) { 526 | resp.statusCode = 201; 527 | resp.setHeader('location', 'http://localhost:8085/foo/redirect/'); 528 | resp.write(req.url); 529 | resp.end(); 530 | }); 531 | 532 | var proxyOptions = url.parse('http://localhost:8085/'); 533 | var app = connect(); 534 | app.use(proxy(proxyOptions)); 535 | 536 | destServer.listen(8085, 'localhost', function() { 537 | app.listen(8084); 538 | 539 | var options = url.parse('http://localhost:8084/foo/test/'); 540 | 541 | http.get(options, function (res) { 542 | assert.strictEqual(res.headers.location, '/foo/redirect/'); 543 | done(); 544 | }).on('error', function () { 545 | assert.fail('Request proxy failed'); 546 | }); 547 | }) 548 | }); 549 | 550 | }); 551 | 552 | function createServerWithLibName(libName, requestListener) { 553 | var httpLib = require(libName); 554 | if (libName === "http") { 555 | return httpLib.createServer(requestListener); 556 | } else { 557 | return httpLib.createServer({key: key, cert: cert}, requestListener); 558 | } 559 | } 560 | 561 | function testWith (srcLibName, destLibName, cb) { 562 | var srcHttp = require(srcLibName); 563 | var destHttp = require(destLibName); 564 | 565 | var destServer = createServerWithLibName(destLibName, function(req, resp) { 566 | assert.strictEqual(req.method, 'GET'); 567 | assert.strictEqual(req.headers['x-custom-header'], 'hello'); 568 | assert.strictEqual(req.url, '/api/a/b/c/d'); 569 | resp.statusCode = 200; 570 | resp.setHeader('x-custom-reply', "la la la"); 571 | resp.write('this is your body.'); 572 | resp.end(); 573 | }); 574 | destServer.listen(0, 'localhost', function() { 575 | var app = connect(); 576 | var destEndpoint = destLibName + "://localhost:" + destServer.address().port + "/api"; 577 | var reqOpts = url.parse(destEndpoint); 578 | reqOpts.rejectUnauthorized = false; // because we're self-signing for tests 579 | app.use(proxy(reqOpts)); 580 | var srcServer = createServerWithLibName(srcLibName, app); 581 | srcServer.listen(0, 'localhost', function() { 582 | // make client request to proxy server 583 | var srcRequest = srcHttp.request({ 584 | port: srcServer.address().port, 585 | method: "GET", 586 | path: "/a/b/c/d", 587 | headers: { 588 | "x-custom-header": "hello" 589 | }, 590 | rejectUnauthorized: false 591 | }, function (resp) { 592 | var buffer = ""; 593 | assert.strictEqual(resp.statusCode, 200); 594 | assert.strictEqual(resp.headers['x-custom-reply'], 'la la la'); 595 | resp.setEncoding('utf8'); 596 | resp.on('data', function(data) { 597 | buffer += data; 598 | }); 599 | resp.on('end', function() { 600 | assert.strictEqual(buffer, 'this is your body.'); 601 | srcServer.close(); 602 | destServer.close(); 603 | cb(); 604 | }); 605 | }); 606 | srcRequest.end(); 607 | }); 608 | }); 609 | } 610 | --------------------------------------------------------------------------------