├── .gitattributes ├── doc └── logo.png ├── benchmark ├── scripts │ ├── hello.js │ ├── proxy.js │ └── websockets-throughput.js └── README.md ├── .npmignore ├── .auto-changelog ├── .gitignore ├── codecov.yml ├── .travis.yml ├── examples ├── package.json ├── http │ ├── ntlm-authentication.js │ ├── proxy-http-to-https.js │ ├── custom-proxy-error.js │ ├── forward-proxy.js │ ├── latent-proxy.js │ ├── standalone-proxy.js │ ├── reverse-proxy.js │ ├── sse.js │ ├── proxy-https-to-http.js │ ├── error-handling.js │ ├── proxy-https-to-https.js │ ├── concurrent-proxy.js │ ├── basic-proxy.js │ └── forward-and-target-proxy.js ├── helpers │ └── store.js ├── balancer │ ├── simple-balancer.js │ └── simple-balancer-with-websockets.js ├── middleware │ ├── modifyResponse-middleware.js │ ├── gzip-middleware.js │ └── bodyDecoder-middleware.js └── websocket │ ├── websocket-proxy.js │ ├── standalone-websocket-proxy.js │ └── latent-websocket-proxy.js ├── index.js ├── renovate.json ├── package.json ├── LICENSE ├── test ├── fixtures │ ├── agent2-cert.pem │ └── agent2-key.pem ├── examples-test.js ├── lib-http-proxy-passes-ws-incoming-test.js ├── lib-https-proxy-test.js ├── lib-http-proxy-common-test.js ├── lib-http-proxy-passes-web-outgoing-test.js ├── lib-http-proxy-passes-web-incoming-test.js └── lib-http-proxy-test.js ├── lib ├── http-proxy.js └── http-proxy │ ├── passes │ ├── ws-incoming.js │ ├── web-outgoing.js │ └── web-incoming.js │ ├── index.js │ └── common.js ├── UPGRADING.md ├── CODE_OF_CONDUCT.md └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | package-lock.json binary 2 | -------------------------------------------------------------------------------- /doc/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/http-party/node-http-proxy/HEAD/doc/logo.png -------------------------------------------------------------------------------- /benchmark/scripts/hello.js: -------------------------------------------------------------------------------- 1 | require('http').createServer(function(req, res) { 2 | res.end('Hello world!'); 3 | }).listen(9000); -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test 2 | examples 3 | doc 4 | benchmark 5 | coverage 6 | .nyc_output 7 | .travis.yml 8 | CHANGELOG.md 9 | UPGRADING.md 10 | -------------------------------------------------------------------------------- /.auto-changelog: -------------------------------------------------------------------------------- 1 | { 2 | "output": "CHANGELOG.md", 3 | "template": "keepachangelog", 4 | "unreleased": true, 5 | "commitLimit": false 6 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.swp 3 | cov 4 | atest.js 5 | notes 6 | primus-proxy.js 7 | tes.js 8 | npm-debug.log 9 | .nyc_output 10 | coverage 11 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | parsers: 3 | javascript: 4 | enable_partials: yes 5 | status: 6 | project: 7 | default: 8 | target: "70%" 9 | patch: 10 | enabled: false 11 | -------------------------------------------------------------------------------- /benchmark/scripts/proxy.js: -------------------------------------------------------------------------------- 1 | var http = require('http'), 2 | httpProxy = require('../../'); 3 | // 4 | // Create your proxy server 5 | // 6 | httpProxy.createProxyServer({ target: 'http://localhost:9000' }).listen(8000); -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - "8" 5 | - "10" 6 | - "12" 7 | script: 8 | - npm test 9 | after_success: 10 | - bash <(curl -s https://codecov.io/bash) 11 | matrix: 12 | fast_finish: true 13 | -------------------------------------------------------------------------------- /examples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "http-proxy-examples", 3 | "description": "packages required to run the examples", 4 | "version": "0.0.0", 5 | "dependencies": { 6 | "agentkeepalive": "^4.0.0", 7 | "colors": "~1.3.0", 8 | "connect-restreamer": "~1.0.0", 9 | "request": "~2.88.0", 10 | "socket.io": "~0.9.16", 11 | "socket.io-client": "~0.9.16", 12 | "sse": "0.0.8" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Caron dimonio, con occhi di bragia 3 | * loro accennando, tutte le raccoglie; 4 | * batte col remo qualunque s’adagia 5 | * 6 | * Charon the demon, with the eyes of glede, 7 | * Beckoning to them, collects them all together, 8 | * Beats with his oar whoever lags behind 9 | * 10 | * Dante - The Divine Comedy (Canto III) 11 | */ 12 | 13 | module.exports = require('./lib/http-proxy'); -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "platform": "github", 3 | "autodiscover": false, 4 | "requireConfig": true, 5 | "ignoreNpmrcFile": true, 6 | "rangeStrategy": "replace", 7 | "packageRules": [ 8 | { 9 | "packagePatterns": [ 10 | "*" 11 | ], 12 | "minor": { 13 | "groupName": "all non-major dependencies", 14 | "groupSlug": "all-minor-patch" 15 | } 16 | } 17 | ], 18 | "commitMessagePrefix": "[dist]" 19 | } 20 | -------------------------------------------------------------------------------- /examples/http/ntlm-authentication.js: -------------------------------------------------------------------------------- 1 | var httpProxy = require('../../lib/http-proxy'); 2 | var Agent = require('agentkeepalive'); 3 | 4 | var agent = new Agent({ 5 | maxSockets: 100, 6 | keepAlive: true, 7 | maxFreeSockets: 10, 8 | keepAliveMsecs:1000, 9 | timeout: 60000, 10 | keepAliveTimeout: 30000 // free socket keepalive for 30 seconds 11 | }); 12 | 13 | var proxy = httpProxy.createProxy({ target: 'http://whatever.com', agent: agent }); 14 | 15 | // 16 | // Modify headers of the response before it gets sent 17 | // So that we handle the NLTM authentication response 18 | // 19 | proxy.on('proxyRes', function (proxyRes) { 20 | var key = 'www-authenticate'; 21 | proxyRes.headers[key] = proxyRes.headers[key] && proxyRes.headers[key].split(','); 22 | }); 23 | 24 | require('http').createServer(function (req, res) { 25 | proxy.web(req, res); 26 | }).listen(3000); 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "http-proxy", 3 | "version": "1.18.1", 4 | "repository": { 5 | "type": "git", 6 | "url": "https://github.com/http-party/node-http-proxy.git" 7 | }, 8 | "description": "HTTP proxying for the masses", 9 | "author": "Charlie Robbins ", 10 | "maintainers": [ 11 | "jcrugzz " 12 | ], 13 | "main": "index.js", 14 | "dependencies": { 15 | "eventemitter3": "^4.0.0", 16 | "requires-port": "^1.0.0", 17 | "follow-redirects": "^1.0.0" 18 | }, 19 | "devDependencies": { 20 | "async": "^3.0.0", 21 | "auto-changelog": "^1.15.0", 22 | "concat-stream": "^2.0.0", 23 | "expect.js": "~0.3.1", 24 | "mocha": "^3.5.3", 25 | "nyc": "^14.0.0", 26 | "semver": "^5.0.3", 27 | "socket.io": "^2.1.0", 28 | "socket.io-client": "^2.1.0", 29 | "sse": "0.0.8", 30 | "ws": "^3.0.0" 31 | }, 32 | "scripts": { 33 | "mocha": "mocha test/*-test.js", 34 | "test": "nyc --reporter=text --reporter=lcov npm run mocha", 35 | "version": "auto-changelog -p && git add CHANGELOG.md" 36 | }, 37 | "engines": { 38 | "node": ">=8.0.0" 39 | }, 40 | "license": "MIT" 41 | } 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | node-http-proxy 3 | 4 | Copyright (c) 2010-2016 Charlie Robbins, Jarrett Cruger & the Contributors. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /test/fixtures/agent2-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEIDCCAggCCQChRDh/XiBF+zANBgkqhkiG9w0BAQsFADBUMQswCQYDVQQGEwJ1 3 | czETMBEGA1UECAwKV2FzaGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEeMBwGA1UE 4 | AwwVRHVtbXkgSW50ZXJtZWRpYXRlIENBMB4XDTE4MDYyMjIwMzEwNFoXDTMyMDIy 5 | OTIwMzEwNFowUDELMAkGA1UEBhMCdXMxEzARBgNVBAgMCldhc2hpbmd0b24xEDAO 6 | BgNVBAcMB1NlYXR0bGUxGjAYBgNVBAMMEWR1bW15LmV4YW1wbGUuY29tMIIBIjAN 7 | BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvSQq3d8AeZMTvtqZ13jWCckikyXJ 8 | SACvkGCQUCJqOceESbg6IHdRzQdoccE4P3sbvNsf9BlbdJKM+neCxabqKaU1PPje 9 | 4P0tHT57t6yJrMuUh9NxEz3Bgh1srNHVS7saKvwHmcKm79jc+wxlioPmEQvQagjn 10 | y7oTkyLt0sn4LGxBjrcv2JoHOC9f1pxX7l47MaiN0/ctRau7Nr3PFn+pkB4Yf6Z0 11 | VyicVJbaUSz39Qo4HQWl1L2hiBP3CS1oKs2Yk0O1aOCMExWrhZQan+ZgHqL1rhgm 12 | kPpw2/zwwPt5Vf9CSakvHwg198EXuTTXtkzYduuIJAm8yp969iEIiG2xTwIDAQAB 13 | MA0GCSqGSIb3DQEBCwUAA4ICAQBnMSIo+kujkeXPh+iErFBmNtu/7EA+i/QnFPbN 14 | lSLngclYYBJAGQI+DhirJI8ghDi6vmlHB2THewDaOJXEKvC1czE8064wioIcA9HJ 15 | l3QJ3YYOFRctYdSHBU4TWdJbPgkLWDzYP5smjOfw8nDdr4WO/5jh9qRFcFpTFmQf 16 | DyU3xgWLsQnNK3qXLdJjWG75pEhHR+7TGo+Ob/RUho/1RX/P89Ux7/oVbzdKqqFu 17 | SErXAsjEIEFzWOM2uDOt6hrxDF6q+8/zudwQNEo422poEcTT9tDEFxMQ391CzZRi 18 | nozBm4igRn1f5S3YZzLI6VEUns0s76BNy2CzvFWn40DziTqNBExAMfFFj76wiMsX 19 | 6fTIdcvkaTBa0S9SZB0vN99qahBdcG17rt4RssMHVRH1Wn7NXMwe476L0yXZ6gO7 20 | Z4uNAPxgaI3BRP75EPfslLutCLZ+BC4Zzu6MY0Salbpfl0Go462EhsKCxvYhE2Dg 21 | T477pICLfETZfA499Fd1tOaIsoLCrILAia/+Yd76uf94MuXUIqykDng/4H7xCc47 22 | BZhNFJiPC6XHaXzN7NYSEUNX9VOwY8ncxKwtP6TXga96PdMUy/p98KIM8RZlDoxB 23 | Xy9dcZBFNn/zrqjW7R0CCWCUriDIFSmEP0wDZ91YYa6BVuJMb5uL/USkTLpjZS4/ 24 | HNGvug== 25 | -----END CERTIFICATE----- 26 | -------------------------------------------------------------------------------- /test/fixtures/agent2-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpQIBAAKCAQEAvSQq3d8AeZMTvtqZ13jWCckikyXJSACvkGCQUCJqOceESbg6 3 | IHdRzQdoccE4P3sbvNsf9BlbdJKM+neCxabqKaU1PPje4P0tHT57t6yJrMuUh9Nx 4 | Ez3Bgh1srNHVS7saKvwHmcKm79jc+wxlioPmEQvQagjny7oTkyLt0sn4LGxBjrcv 5 | 2JoHOC9f1pxX7l47MaiN0/ctRau7Nr3PFn+pkB4Yf6Z0VyicVJbaUSz39Qo4HQWl 6 | 1L2hiBP3CS1oKs2Yk0O1aOCMExWrhZQan+ZgHqL1rhgmkPpw2/zwwPt5Vf9CSakv 7 | Hwg198EXuTTXtkzYduuIJAm8yp969iEIiG2xTwIDAQABAoIBAGPIw/C/qJF7HYyv 8 | 6T+7GTiaa2o0IiehbP3/Y8NTFLWc49a8obXlHTvMr7Zr2I/tE+ojtIzkH9K1SjkN 9 | eelqsNj9tsOPDI6oIvftsflpxkxqLtclnt8m0oMhoObf4OaONDT/N8dP4SBiSdsM 10 | ZDmacnMFx5NZVWiup4sVf2CYexx7qks9FhyN2K5PArCQ4S9LHjFhSJVH4DSEpv7E 11 | Ykbp30rhpqV7wSwjgUsm8ZYvI2NOlmffzLSiPdt3vy2K5Q25S/MVEAicg83rfDgK 12 | 6EluHjeygRI1xU6DJ0hU7tnU7zE9KURoHPUycO3BKzZnzUH26AA36I58Pu4fXWw/ 13 | Cgmbv2ECgYEA+og9E4ziKCEi3p8gqjIfwTRgWZxDLjEzooB/K0UhEearn/xiX29A 14 | FiSzEHKfCB4uSrw5OENg2ckDs8uy08Qmxx7xFXL7AtufAl5fIYaWa0sNSqCaIk7p 15 | ebbUmPcaYhKiLzIEd1EYEL38sXVZ62wvSVMRSWvEMq44g1qnoRlDa/8CgYEAwUTt 16 | talYNwVmR9ZdkVEWm9ZxirdzoM6NaM6u4Tf34ygptpapdmIFSUhfq4iOiEnRGNg/ 17 | tuNqhNCIb3LNpJbhRPEzqN7E7qiF/mp7AcJgbuxLZBm12QuLuJdG3nrisKPFXcY1 18 | lA4A7CFmNgH3E4THFfgwzyDXsBOxVLXleTqn+rECgYEA9up1P6J3dtOJuV2d5P/3 19 | ugRz/X173LfTSxJXw36jZDAy8D/feG19/RT4gnplcKvGNhQiVOhbOOnbw0U8n2fQ 20 | TCmbs+cZqyxnH/+AxNsPvvk+RVHZ93xMsY/XIldP4l65B8jFDA+Zp06IESI2mEeM 21 | pzi+bd1Phh+dRSCA2865W2MCgYEAlxYsgmQ1WyX0dFpHYU+zzfXRYzDQyrhOYc2Z 22 | duVK+yCto1iad7pfCY/zgmRJkI+sT7DV9kJIRjXDQuTLkEyHJF8vFGe6KhxCS8aw 23 | DIsI2g4NTd6vg1J8UryoIUqNpqsQoqNNxUVBQVdG0ReuMGsPO8R/W50AIFz0txVP 24 | o/rP0LECgYEA7e/mOwCnR+ovmS/CAksmos3oIqvkRkXNKpKe513FVmp3TpTU38ex 25 | cBkFNU3hEO31FyrX1hGIKp3N5mHYSQ1lyODHM6teHW0OLWWTwIe8rIGvR2jfRLe0 26 | bbkdj40atYVkfeFmpz9uHHG24CUYxJdPc360jbXTVp4i3q8zqgL5aMY= 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /benchmark/README.md: -------------------------------------------------------------------------------- 1 | # Benchmarking `node-http-proxy` 2 | 3 | The long-term goal of these scripts and documentation is to provide a consistent and well understood benchmarking process for `node-http-proxy` so that performance does not degrade over time. They were initially created to compare the performance of `v0.10.3` and `v1.0.0` (which was a significant rewrite). 4 | 5 | ## Pre-requisites 6 | 7 | All benchmarking shall be done with [wrk](https://github.com/wg/wrk) which _is the same tool used for performance testing by the node.js core team._ **Make sure you have `wrk` installed before continuing**. 8 | 9 | ``` 10 | $ wrk 11 | Usage: wrk 12 | Options: 13 | -c, --connections Connections to keep open 14 | -d, --duration Duration of test 15 | -t, --threads Number of threads to use 16 | 17 | -s, --script Load Lua script file 18 | -H, --header Add header to request 19 | --latency Print latency statistics 20 | --timeout Socket/request timeout 21 | -v, --version Print version details 22 | 23 | Numeric arguments may include a SI unit (1k, 1M, 1G) 24 | Time arguments may include a time unit (2s, 2m, 2h) 25 | ``` 26 | 27 | ## Benchmarks 28 | 29 | 1. [Simple HTTP benchmark](#simple-http) 30 | 31 | ### Simple HTTP 32 | 33 | _This benchmark requires three terminals running:_ 34 | 35 | 1. **A proxy server:** `node benchmark/scripts/proxy.js` 36 | 2. **A target server:** `node benchmark/scripts/hello.js` 37 | 3. **A wrk process:** `wrk -c 20 -d5m -t 2 http://127.0.0.1:8000` 38 | -------------------------------------------------------------------------------- /examples/helpers/store.js: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // just to make these example a little bit interesting, 4 | // make a little key value store with an http interface 5 | // (see couchdb for a grown-up version of this) 6 | // 7 | // API: 8 | // GET / 9 | // retrive list of keys 10 | // 11 | // GET /[url] 12 | // retrive object stored at [url] 13 | // will respond with 404 if there is nothing stored at [url] 14 | // 15 | // POST /[url] 16 | // 17 | // JSON.parse the body and store it under [url] 18 | // will respond 400 (bad request) if body is not valid json. 19 | // 20 | // TODO: cached map-reduce views and auto-magic sharding. 21 | // 22 | var Store = module.exports = function Store () { 23 | this.store = {}; 24 | }; 25 | 26 | Store.prototype = { 27 | get: function (key) { 28 | return this.store[key] 29 | }, 30 | set: function (key, value) { 31 | return this.store[key] = value 32 | }, 33 | handler:function () { 34 | var store = this 35 | return function (req, res) { 36 | function send (obj, status) { 37 | res.writeHead(200 || status,{'Content-Type': 'application/json'}) 38 | res.write(JSON.stringify(obj) + '\n') 39 | res.end() 40 | } 41 | var url = req.url.split('?').shift() 42 | if (url === '/') { 43 | console.log('get index') 44 | return send(Object.keys(store.store)) 45 | } else if (req.method == 'GET') { 46 | var obj = store.get (url) 47 | send(obj || {error: 'not_found', url: url}, obj ? 200 : 404) 48 | } else { 49 | //post: buffer body, and parse. 50 | var body = '', obj 51 | req.on('data', function (c) { body += c}) 52 | req.on('end', function (c) { 53 | try { 54 | obj = JSON.parse(body) 55 | } catch (err) { 56 | return send (err, 400) 57 | } 58 | store.set(url, obj) 59 | send({ok: true}) 60 | }) 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /examples/http/proxy-http-to-https.js: -------------------------------------------------------------------------------- 1 | /* 2 | proxy-http-to-https.js: Basic example of proxying over HTTP to a target HTTPS server 3 | 4 | Copyright (c) 2013 - 2016 Charlie Robbins, Jarrett Cruger & the Contributors. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | */ 26 | 27 | var https = require('https'), 28 | http = require('http'), 29 | util = require('util'), 30 | path = require('path'), 31 | fs = require('fs'), 32 | colors = require('colors'), 33 | httpProxy = require('../../lib/http-proxy'); 34 | 35 | // 36 | // Create a HTTP Proxy server with a HTTPS target 37 | // 38 | httpProxy.createProxyServer({ 39 | target: 'https://google.com', 40 | agent : https.globalAgent, 41 | headers: { 42 | host: 'google.com' 43 | } 44 | }).listen(8011); 45 | 46 | util.puts('http proxy server'.blue + ' started '.green.bold + 'on port '.blue + '8011'.yellow); -------------------------------------------------------------------------------- /examples/http/custom-proxy-error.js: -------------------------------------------------------------------------------- 1 | /* 2 | custom-proxy-error.js: Example of using the custom `proxyError` event. 3 | 4 | Copyright (c) 2013 - 2016 Charlie Robbins, Jarrett Cruger & the Contributors. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | */ 26 | 27 | var util = require('util'), 28 | colors = require('colors'), 29 | http = require('http'), 30 | httpProxy = require('../../lib/http-proxy'); 31 | 32 | // 33 | // Http Proxy Server with bad target 34 | // 35 | var proxy = httpProxy.createServer({ 36 | target:'http://localhost:9005' 37 | }); 38 | 39 | // 40 | // Tell the proxy to listen on port 8000 41 | // 42 | proxy.listen(8005); 43 | 44 | // 45 | // Listen for the `error` event on `proxy`. 46 | proxy.on('error', function (err, req, res) { 47 | res.writeHead(500, { 48 | 'Content-Type': 'text/plain' 49 | }); 50 | 51 | res.end('Something went wrong. And we are reporting a custom error message.'); 52 | }); 53 | 54 | 55 | util.puts('http proxy server '.blue + 'started '.green.bold + 'on port '.blue + '8005 '.yellow + 'with custom error message'.magenta.underline); -------------------------------------------------------------------------------- /examples/http/forward-proxy.js: -------------------------------------------------------------------------------- 1 | /* 2 | forward-proxy.js: Example of proxying over HTTP with additional forward proxy 3 | 4 | Copyright (c) 2013 - 2016 Charlie Robbins, Jarrett Cruger & the Contributors. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | */ 26 | 27 | var util = require('util'), 28 | colors = require('colors'), 29 | http = require('http'), 30 | httpProxy = require('../../lib/http-proxy'); 31 | 32 | // 33 | // Setup proxy server with forwarding 34 | // 35 | httpProxy.createServer({ 36 | forward: { 37 | port: 9019, 38 | host: 'localhost' 39 | } 40 | }).listen(8019); 41 | 42 | // 43 | // Target Http Forwarding Server 44 | // 45 | http.createServer(function (req, res) { 46 | util.puts('Receiving forward for: ' + req.url); 47 | res.writeHead(200, { 'Content-Type': 'text/plain' }); 48 | res.write('request successfully forwarded to: ' + req.url + '\n' + JSON.stringify(req.headers, true, 2)); 49 | res.end(); 50 | }).listen(9019); 51 | 52 | util.puts('http proxy server '.blue + 'started '.green.bold + 'on port '.blue + '8019 '.yellow + 'with forward proxy'.magenta.underline); 53 | util.puts('http forward server '.blue + 'started '.green.bold + 'on port '.blue + '9019 '.yellow); -------------------------------------------------------------------------------- /examples/balancer/simple-balancer.js: -------------------------------------------------------------------------------- 1 | /* 2 | simple-balancer.js: Example of a simple round robin balancer 3 | 4 | Copyright (c) 2013 - 2016 Charlie Robbins, Jarrett Cruger & the Contributors. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | */ 26 | 27 | var http = require('http'), 28 | httpProxy = require('../../lib/http-proxy'); 29 | // 30 | // A simple round-robin load balancing strategy. 31 | // 32 | // First, list the servers you want to use in your rotation. 33 | // 34 | var addresses = [ 35 | { 36 | host: 'ws1.0.0.0', 37 | port: 80 38 | }, 39 | { 40 | host: 'ws2.0.0.0', 41 | port: 80 42 | } 43 | ]; 44 | var proxy = httpProxy.createServer(); 45 | 46 | http.createServer(function (req, res) { 47 | // 48 | // On each request, get the first location from the list... 49 | // 50 | var target = { target: addresses.shift() }; 51 | 52 | // 53 | // ...then proxy to the server whose 'turn' it is... 54 | // 55 | console.log('balancing request to: ', target); 56 | proxy.web(req, res, target); 57 | 58 | // 59 | // ...and then the server you just used becomes the last item in the list. 60 | // 61 | addresses.push(target.target); 62 | }).listen(8021); 63 | 64 | // Rinse; repeat; enjoy. -------------------------------------------------------------------------------- /examples/http/latent-proxy.js: -------------------------------------------------------------------------------- 1 | /* 2 | latent-proxy.js: Example of proxying over HTTP with latency 3 | 4 | Copyright (c) 2013 - 2016 Charlie Robbins, Jarrett Cruger & the Contributors. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | */ 26 | 27 | var util = require('util'), 28 | colors = require('colors'), 29 | http = require('http'), 30 | httpProxy = require('../../lib/http-proxy'); 31 | 32 | // 33 | // Http Proxy Server with Latency 34 | // 35 | var proxy = httpProxy.createProxyServer(); 36 | http.createServer(function (req, res) { 37 | setTimeout(function () { 38 | proxy.web(req, res, { 39 | target: 'http://localhost:9008' 40 | }); 41 | }, 500); 42 | }).listen(8008); 43 | 44 | // 45 | // Target Http Server 46 | // 47 | http.createServer(function (req, res) { 48 | res.writeHead(200, { 'Content-Type': 'text/plain' }); 49 | res.write('request successfully proxied to: ' + req.url + '\n' + JSON.stringify(req.headers, true, 2)); 50 | res.end(); 51 | }).listen(9008); 52 | 53 | util.puts('http proxy server '.blue + 'started '.green.bold + 'on port '.blue + '8008 '.yellow + 'with latency'.magenta.underline); 54 | util.puts('http server '.blue + 'started '.green.bold + 'on port '.blue + '9008 '.yellow); 55 | -------------------------------------------------------------------------------- /benchmark/scripts/websockets-throughput.js: -------------------------------------------------------------------------------- 1 | var crypto = require('crypto'), 2 | WebSocket = require('ws'), 3 | async = require('async'), 4 | httpProxy = require('../../'); 5 | 6 | var SERVER_PORT = 8415, 7 | PROXY_PORT = 8514; 8 | 9 | var testSets = [ 10 | { 11 | size: 1024 * 1024, // 1 MB 12 | count: 128 // 128 MB 13 | }, 14 | { 15 | size: 1024, // 1 KB, 16 | count: 1024 // 1 MB 17 | }, 18 | { 19 | size: 128, // 128 B 20 | count: 1024 * 8 // 1 MB 21 | } 22 | ]; 23 | 24 | testSets.forEach(function (set) { 25 | set.buffer = new Buffer(crypto.randomBytes(set.size)); 26 | 27 | set.buffers = []; 28 | for (var i = 0; i < set.count; i++) { 29 | set.buffers.push(set.buffer); 30 | } 31 | }); 32 | 33 | function runSet(set, callback) { 34 | function runAgainst(port, callback) { 35 | function send(sock) { 36 | sock.send(set.buffers[got++]); 37 | if (got === set.count) { 38 | t = new Date() - t; 39 | 40 | server.close(); 41 | proxy.close(); 42 | 43 | callback(null, t); 44 | } 45 | } 46 | 47 | var server = new WebSocket.Server({ port: SERVER_PORT }), 48 | proxy = httpProxy.createServer(SERVER_PORT, 'localhost').listen(PROXY_PORT), 49 | client = new WebSocket('ws://localhost:' + port), 50 | got = 0, 51 | t = new Date(); 52 | 53 | server.on('connection', function (ws) { 54 | send(ws); 55 | 56 | ws.on('message', function (msg) { 57 | send(ws); 58 | }); 59 | }); 60 | 61 | client.on('message', function () { 62 | send(client); 63 | }); 64 | } 65 | 66 | async.series({ 67 | server: async.apply(runAgainst, SERVER_PORT), 68 | proxy: async.apply(runAgainst, PROXY_PORT) 69 | }, function (err, results) { 70 | if (err) { 71 | throw err; 72 | } 73 | 74 | var mb = (set.size * set.count) / (1024 * 1024); 75 | console.log(set.size / (1024) + ' KB * ' + set.count + ' (' + mb + ' MB)'); 76 | 77 | Object.keys(results).forEach(function (key) { 78 | var t = results[key], 79 | throughput = mb / (t / 1000); 80 | 81 | console.log(' ' + key + ' took ' + t + ' ms (' + throughput + ' MB/s)'); 82 | }); 83 | 84 | callback(); 85 | }); 86 | } 87 | 88 | async.forEachLimit(testSets, 1, runSet); 89 | -------------------------------------------------------------------------------- /examples/http/standalone-proxy.js: -------------------------------------------------------------------------------- 1 | /* 2 | standalone-proxy.js: Example of proxying over HTTP with a standalone HTTP server. 3 | 4 | Copyright (c) 2013 - 2016 Charlie Robbins, Jarrett Cruger & the Contributors. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | */ 26 | 27 | var util = require('util'), 28 | colors = require('colors'), 29 | http = require('http'), 30 | httpProxy = require('../../lib/http-proxy'); 31 | 32 | // 33 | // Http Server with proxyRequest Handler and Latency 34 | // 35 | var proxy = new httpProxy.createProxyServer(); 36 | http.createServer(function (req, res) { 37 | setTimeout(function () { 38 | proxy.web(req, res, { 39 | target: 'http://localhost:9002' 40 | }); 41 | }, 200); 42 | }).listen(8002); 43 | 44 | // 45 | // Target Http Server 46 | // 47 | http.createServer(function (req, res) { 48 | res.writeHead(200, { 'Content-Type': 'text/plain' }); 49 | res.write('request successfully proxied to: ' + req.url + '\n' + JSON.stringify(req.headers, true, 2)); 50 | res.end(); 51 | }).listen(9002); 52 | 53 | util.puts('http server '.blue + 'started '.green.bold + 'on port '.blue + '8002 '.yellow + 'with proxy.web() handler'.cyan.underline + ' and latency'.magenta); 54 | util.puts('http server '.blue + 'started '.green.bold + 'on port '.blue + '9002 '.yellow); 55 | -------------------------------------------------------------------------------- /examples/http/reverse-proxy.js: -------------------------------------------------------------------------------- 1 | /* 2 | reverse-proxy.js: Example of reverse proxying (with HTTPS support) 3 | Copyright (c) 2015 Alberto Pose 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | 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 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | var http = require('http'), 25 | net = require('net'), 26 | httpProxy = require('../../lib/http-proxy'), 27 | url = require('url'), 28 | util = require('util'); 29 | 30 | var proxy = httpProxy.createServer(); 31 | 32 | var server = http.createServer(function (req, res) { 33 | util.puts('Receiving reverse proxy request for:' + req.url); 34 | var parsedUrl = url.parse(req.url); 35 | var target = parsedUrl.protocol + '//' + parsedUrl.hostname; 36 | proxy.web(req, res, {target: target, secure: false}); 37 | }).listen(8213); 38 | 39 | server.on('connect', function (req, socket) { 40 | util.puts('Receiving reverse proxy request for:' + req.url); 41 | 42 | var serverUrl = url.parse('https://' + req.url); 43 | 44 | var srvSocket = net.connect(serverUrl.port, serverUrl.hostname, function() { 45 | socket.write('HTTP/1.1 200 Connection Established\r\n' + 46 | 'Proxy-agent: Node-Proxy\r\n' + 47 | '\r\n'); 48 | srvSocket.pipe(socket); 49 | socket.pipe(srvSocket); 50 | }); 51 | }); 52 | 53 | // Test with: 54 | // curl -vv -x http://127.0.0.1:8213 https://www.google.com 55 | // curl -vv -x http://127.0.0.1:8213 http://www.google.com 56 | -------------------------------------------------------------------------------- /examples/http/sse.js: -------------------------------------------------------------------------------- 1 | /* 2 | sse.js: Basic example of proxying over HTTP 3 | 4 | Copyright (c) 2013 - 2016 Charlie Robbins, Jarrett Cruger & the Contributors. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | */ 26 | 27 | var util = require('util'), 28 | colors = require('colors'), 29 | http = require('http'), 30 | httpProxy = require('../../lib/http-proxy'), 31 | SSE = require('sse'); 32 | 33 | // 34 | // Basic Http Proxy Server 35 | // 36 | var proxy = new httpProxy.createProxyServer(); 37 | http.createServer(function (req, res) { 38 | proxy.web(req, res, { 39 | target: 'http://localhost:9003' 40 | }); 41 | }).listen(8003); 42 | 43 | // 44 | // Target Http Server 45 | // 46 | var server = http.createServer(function(req, res) { 47 | res.writeHead(200, {'Content-Type': 'text/html'}); 48 | res.write('request successfully proxied to: ' + req.url + '\n' + JSON.stringify(req.headers, true, 2)); 49 | res.end(); 50 | }); 51 | 52 | // 53 | // Use SSE 54 | // 55 | 56 | var sse = new SSE(server, {path: '/'}); 57 | sse.on('connection', function(client) { 58 | var count = 0; 59 | setInterval(function(){ 60 | client.send('message #' + count++); 61 | }, 1500); 62 | }); 63 | 64 | server.listen(9003); 65 | 66 | util.puts('http proxy server'.blue + ' started '.green.bold + 'on port '.blue + '8003'.yellow); 67 | util.puts('http server '.blue + 'started '.green.bold + 'on port '.blue + '9003 '.yellow); 68 | -------------------------------------------------------------------------------- /examples/http/proxy-https-to-http.js: -------------------------------------------------------------------------------- 1 | /* 2 | proxy-https-to-http.js: Basic example of proxying over HTTPS to a target HTTP server 3 | 4 | Copyright (c) 2013 - 2016 Charlie Robbins, Jarrett Cruger & the Contributors. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | */ 26 | 27 | var https = require('https'), 28 | http = require('http'), 29 | util = require('util'), 30 | path = require('path'), 31 | fs = require('fs'), 32 | colors = require('colors'), 33 | httpProxy = require('../../lib/http-proxy'), 34 | fixturesDir = path.join(__dirname, '..', '..', 'test', 'fixtures'); 35 | 36 | // 37 | // Create the target HTTP server 38 | // 39 | http.createServer(function (req, res) { 40 | res.writeHead(200, { 'Content-Type': 'text/plain' }); 41 | res.write('hello http over https\n'); 42 | res.end(); 43 | }).listen(9009); 44 | 45 | // 46 | // Create the HTTPS proxy server listening on port 8000 47 | // 48 | httpProxy.createServer({ 49 | target: { 50 | host: 'localhost', 51 | port: 9009 52 | }, 53 | ssl: { 54 | key: fs.readFileSync(path.join(fixturesDir, 'agent2-key.pem'), 'utf8'), 55 | cert: fs.readFileSync(path.join(fixturesDir, 'agent2-cert.pem'), 'utf8') 56 | } 57 | }).listen(8009); 58 | 59 | util.puts('https proxy server'.blue + ' started '.green.bold + 'on port '.blue + '8009'.yellow); 60 | util.puts('http server '.blue + 'started '.green.bold + 'on port '.blue + '9009 '.yellow); 61 | -------------------------------------------------------------------------------- /examples/http/error-handling.js: -------------------------------------------------------------------------------- 1 | /* 2 | error-handling.js: Example of handle erros for HTTP and WebSockets 3 | 4 | Copyright (c) 2013 - 2016 Charlie Robbins, Jarrett Cruger & the Contributors. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | */ 26 | 27 | var util = require('util'), 28 | colors = require('colors'), 29 | http = require('http'), 30 | httpProxy = require('../../lib/http-proxy'); 31 | 32 | // 33 | // HTTP Proxy Server 34 | // 35 | var proxy = httpProxy.createProxyServer({target:'http://localhost:9000', ws:true}); 36 | 37 | // 38 | // Example of error handling 39 | // 40 | function requestHandler(req, res) { 41 | // Pass a callback to the web proxy method 42 | // and catch the error there. 43 | proxy.web(req, res, function (err) { 44 | // Now you can get the err 45 | // and handle it by your self 46 | // if (err) throw err; 47 | res.writeHead(502); 48 | res.end("There was an error proxying your request"); 49 | }); 50 | 51 | // In a websocket request case 52 | req.on('upgrade', function (req, socket, head) { 53 | proxy.ws(req, socket, head, function (err) { 54 | // Now you can get the err 55 | // and handle it by your self 56 | // if (err) throw err; 57 | socket.close(); 58 | }) 59 | }) 60 | } 61 | 62 | http.createServer(requestHandler).listen(8000); 63 | util.puts('http proxy server'.blue + ' started '.green.bold + 'on port '.blue + '8000'.yellow); 64 | -------------------------------------------------------------------------------- /examples/middleware/modifyResponse-middleware.js: -------------------------------------------------------------------------------- 1 | /* 2 | modifyBody-middleware.js: Example of middleware which modifies response 3 | 4 | Copyright (c) 2013 - 2016 Charlie Robbins, Jarrett Cruger & the Contributors. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | */ 26 | 27 | var util = require('util'), 28 | colors = require('colors'), 29 | http = require('http'), 30 | connect = require('connect'), 31 | app = connect(), 32 | httpProxy = require('../../lib/http-proxy'); 33 | 34 | // 35 | // Basic Connect App 36 | // 37 | app.use(function (req, res, next) { 38 | var _write = res.write; 39 | 40 | res.write = function (data) { 41 | _write.call(res, data.toString().replace("Ruby", "http-party")); 42 | } 43 | next(); 44 | }); 45 | 46 | app.use(function (req, res) { 47 | proxy.web(req, res) 48 | }); 49 | 50 | http.createServer(app).listen(8013); 51 | 52 | // 53 | // Basic Http Proxy Server 54 | // 55 | var proxy = httpProxy.createProxyServer({ 56 | target: 'http://localhost:9013' 57 | }); 58 | 59 | // 60 | // Target Http Server 61 | // 62 | http.createServer(function (req, res) { 63 | res.writeHead(200, { 'Content-Type': 'text/plain' }); 64 | res.end('Hello, I love Ruby\n'); 65 | }).listen(9013); 66 | 67 | util.puts('http proxy server'.blue + ' started '.green.bold + 'on port '.blue + '8013'.yellow); 68 | util.puts('http server '.blue + 'started '.green.bold + 'on port '.blue + '9013 '.yellow); 69 | 70 | -------------------------------------------------------------------------------- /test/examples-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | examples-test.js: Test to run all the examples 3 | 4 | Copyright (c) 2013 - 2016 Charlie Robbins, Jarrett Cruger & the Contributors. 5 | 6 | */ 7 | var path = require('path'), 8 | fs = require('fs'), 9 | spawn = require('child_process').spawn, 10 | expect = require('expect.js'), 11 | async = require('async'); 12 | 13 | var rootDir = path.join(__dirname, '..'), 14 | examplesDir = path.join(rootDir, 'examples'); 15 | 16 | describe.skip('http-proxy examples', function () { 17 | describe('Before testing examples', function () { 18 | // Set a timeout to avoid this error 19 | this.timeout(30 * 1000); 20 | it('should have installed dependencies', function (done) { 21 | async.waterfall([ 22 | // 23 | // 1. Read files in examples dir 24 | // 25 | async.apply(fs.readdir, examplesDir), 26 | // 27 | // 2. If node_modules exists, continue. Otherwise 28 | // exec `npm` to install them 29 | // 30 | function checkNodeModules(files, next) { 31 | if (files.indexOf('node_modules') !== -1) { 32 | return next(); 33 | } 34 | 35 | console.log('Warning: installing dependencies, this operation could take a while'); 36 | 37 | var child = spawn('npm', ['install', '-f'], { 38 | cwd: examplesDir 39 | }); 40 | 41 | child.on('exit', function (code) { 42 | return code 43 | ? next(new Error('npm install exited with non-zero exit code')) 44 | : next(); 45 | }); 46 | }, 47 | // 48 | // 3. Read files in examples dir again to ensure the install 49 | // worked as expected. 50 | // 51 | async.apply(fs.readdir, examplesDir), 52 | ], done); 53 | }) 54 | }); 55 | 56 | describe('Requiring all the examples', function () { 57 | it('should have no errors', function (done) { 58 | async.each(['balancer', 'http', 'middleware', 'websocket'], function (dir, cb) { 59 | var name = 'examples/' + dir, 60 | files = fs.readdirSync(path.join(rootDir, 'examples', dir)); 61 | 62 | async.each(files, function (file, callback) { 63 | var example; 64 | expect(function () { example = require(path.join(examplesDir, dir, file)); }).to.not.throwException(); 65 | expect(example).to.be.an('object'); 66 | callback(); 67 | }, cb); 68 | }, done); 69 | }) 70 | }) 71 | }) -------------------------------------------------------------------------------- /examples/http/proxy-https-to-https.js: -------------------------------------------------------------------------------- 1 | /* 2 | proxy-https-to-https.js: Basic example of proxying over HTTPS to a target HTTPS server 3 | 4 | Copyright (c) 2013 - 2016 Charlie Robbins, Jarrett Cruger & the Contributors. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | */ 26 | 27 | var https = require('https'), 28 | http = require('http'), 29 | util = require('util'), 30 | fs = require('fs'), 31 | path = require('path'), 32 | colors = require('colors'), 33 | httpProxy = require('../../lib/http-proxy'), 34 | fixturesDir = path.join(__dirname, '..', '..', 'test', 'fixtures'), 35 | httpsOpts = { 36 | key: fs.readFileSync(path.join(fixturesDir, 'agent2-key.pem'), 'utf8'), 37 | cert: fs.readFileSync(path.join(fixturesDir, 'agent2-cert.pem'), 'utf8') 38 | }; 39 | 40 | // 41 | // Create the target HTTPS server 42 | // 43 | https.createServer(httpsOpts, function (req, res) { 44 | res.writeHead(200, { 'Content-Type': 'text/plain' }); 45 | res.write('hello https\n'); 46 | res.end(); 47 | }).listen(9010); 48 | 49 | // 50 | // Create the proxy server listening on port 8010 51 | // 52 | httpProxy.createServer({ 53 | ssl: httpsOpts, 54 | target: 'https://localhost:9010', 55 | secure: false 56 | }).listen(8010); 57 | 58 | util.puts('https proxy server'.blue + ' started '.green.bold + 'on port '.blue + '8010'.yellow); 59 | util.puts('https server '.blue + 'started '.green.bold + 'on port '.blue + '9010 '.yellow); 60 | -------------------------------------------------------------------------------- /examples/middleware/gzip-middleware.js: -------------------------------------------------------------------------------- 1 | /* 2 | gzip-middleware.js: Basic example of `connect-gzip` middleware in node-http-proxy 3 | 4 | Copyright (c) 2013 - 2016 Charlie Robbins, Jarrett Cruger & the Contributors. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | */ 26 | 27 | var util = require('util'), 28 | colors = require('colors'), 29 | http = require('http'), 30 | connect = require('connect'), 31 | httpProxy = require('../../lib/http-proxy'); 32 | 33 | // 34 | // Basic Connect App 35 | // 36 | connect.createServer( 37 | connect.compress({ 38 | // Pass to connect.compress() the options 39 | // that you need, just for show the example 40 | // we use threshold to 1 41 | threshold: 1 42 | }), 43 | function (req, res) { 44 | proxy.web(req, res); 45 | } 46 | ).listen(8012); 47 | 48 | // 49 | // Basic Http Proxy Server 50 | // 51 | var proxy = httpProxy.createProxyServer({ 52 | target: 'http://localhost:9012' 53 | }); 54 | 55 | // 56 | // Target Http Server 57 | // 58 | http.createServer(function (req, res) { 59 | res.writeHead(200, { 'Content-Type': 'text/plain' }); 60 | res.write('request successfully proxied to: ' + req.url + '\n' + JSON.stringify(req.headers, true, 2)); 61 | res.end(); 62 | }).listen(9012); 63 | 64 | util.puts('http proxy server'.blue + ' started '.green.bold + 'on port '.blue + '8012'.yellow); 65 | util.puts('http server '.blue + 'started '.green.bold + 'on port '.blue + '9012 '.yellow); 66 | -------------------------------------------------------------------------------- /examples/websocket/websocket-proxy.js: -------------------------------------------------------------------------------- 1 | /* 2 | web-socket-proxy.js: Example of proxying over HTTP and WebSockets. 3 | 4 | Copyright (c) 2013 - 2016 Charlie Robbins, Jarrett Cruger & the Contributors. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | */ 26 | 27 | var util = require('util'), 28 | http = require('http'), 29 | colors = require('colors'), 30 | httpProxy = require('../../lib/http-proxy'); 31 | 32 | try { 33 | var io = require('socket.io'), 34 | client = require('socket.io-client'); 35 | } 36 | catch (ex) { 37 | console.error('Socket.io is required for this example:'); 38 | console.error('npm ' + 'install'.green); 39 | process.exit(1); 40 | } 41 | 42 | // 43 | // Create the target HTTP server and setup 44 | // socket.io on it. 45 | // 46 | var server = io.listen(9014); 47 | server.sockets.on('connection', function (client) { 48 | util.debug('Got websocket connection'); 49 | 50 | client.on('message', function (msg) { 51 | util.debug('Got message from client: ' + msg); 52 | }); 53 | 54 | client.send('from server'); 55 | }); 56 | 57 | // 58 | // Create a proxy server with node-http-proxy 59 | // 60 | httpProxy.createServer({ target: 'ws://localhost:9014', ws: true }).listen(8014); 61 | 62 | // 63 | // Setup the socket.io client against our proxy 64 | // 65 | var ws = client.connect('ws://localhost:8014'); 66 | 67 | ws.on('message', function (msg) { 68 | util.debug('Got message: ' + msg); 69 | ws.send('I am the client'); 70 | }); 71 | -------------------------------------------------------------------------------- /examples/http/concurrent-proxy.js: -------------------------------------------------------------------------------- 1 | /* 2 | concurrent-proxy.js: check levelof concurrency through proxy. 3 | 4 | Copyright (c) 2013 - 2016 Charlie Robbins, Jarrett Cruger & the Contributors. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | */ 26 | 27 | var util = require('util'), 28 | colors = require('colors'), 29 | http = require('http'), 30 | httpProxy = require('../../lib/http-proxy'); 31 | 32 | // 33 | // Basic Http Proxy Server 34 | // 35 | httpProxy.createServer({ 36 | target:'http://localhost:9004' 37 | }).listen(8004); 38 | 39 | // 40 | // Target Http Server 41 | // 42 | // to check apparent problems with concurrent connections 43 | // make a server which only responds when there is a given nubmer on connections 44 | // 45 | 46 | 47 | var connections = [], 48 | go; 49 | 50 | http.createServer(function (req, res) { 51 | connections.push(function () { 52 | res.writeHead(200, { 'Content-Type': 'text/plain' }); 53 | res.write('request successfully proxied to: ' + req.url + '\n' + JSON.stringify(req.headers, true, 2)); 54 | res.end(); 55 | }); 56 | 57 | process.stdout.write(connections.length + ', '); 58 | 59 | if (connections.length > 110 || go) { 60 | go = true; 61 | while (connections.length) { 62 | connections.shift()(); 63 | } 64 | } 65 | }).listen(9004); 66 | 67 | util.puts('http proxy server'.blue + ' started '.green.bold + 'on port '.blue + '8004'.yellow); 68 | util.puts('http server '.blue + 'started '.green.bold + 'on port '.blue + '9004 '.yellow); 69 | -------------------------------------------------------------------------------- /examples/http/basic-proxy.js: -------------------------------------------------------------------------------- 1 | /* 2 | basic-proxy.js: Basic example of proxying over HTTP 3 | 4 | Copyright (c) 2013 - 2016 Charlie Robbins, Jarrett Cruger & the Contributors. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | */ 26 | 27 | var util = require('util'), 28 | colors = require('colors'), 29 | http = require('http'), 30 | httpProxy = require('../../lib/http-proxy'); 31 | 32 | var welcome = [ 33 | '# # ##### ##### ##### ##### ##### #### # # # #', 34 | '# # # # # # # # # # # # # # # # ', 35 | '###### # # # # ##### # # # # # # ## # ', 36 | '# # # # ##### ##### ##### # # ## # ', 37 | '# # # # # # # # # # # # # ', 38 | '# # # # # # # # #### # # # ' 39 | ].join('\n'); 40 | 41 | util.puts(welcome.rainbow.bold); 42 | 43 | // 44 | // Basic Http Proxy Server 45 | // 46 | httpProxy.createServer({ 47 | target:'http://localhost:9003' 48 | }).listen(8003); 49 | 50 | // 51 | // Target Http Server 52 | // 53 | http.createServer(function (req, res) { 54 | res.writeHead(200, { 'Content-Type': 'text/plain' }); 55 | res.write('request successfully proxied to: ' + req.url + '\n' + JSON.stringify(req.headers, true, 2)); 56 | res.end(); 57 | }).listen(9003); 58 | 59 | util.puts('http proxy server'.blue + ' started '.green.bold + 'on port '.blue + '8003'.yellow); 60 | util.puts('http server '.blue + 'started '.green.bold + 'on port '.blue + '9003 '.yellow); 61 | -------------------------------------------------------------------------------- /examples/balancer/simple-balancer-with-websockets.js: -------------------------------------------------------------------------------- 1 | /* 2 | simple-balancer.js: Example of a simple round robin balancer for websockets 3 | 4 | Copyright (c) 2013 - 2016 Charlie Robbins, Jarrett Cruger & the Contributors. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | */ 26 | 27 | var http = require('http'), 28 | httpProxy = require('../../lib/http-proxy'); 29 | 30 | // 31 | // A simple round-robin load balancing strategy. 32 | // 33 | // First, list the servers you want to use in your rotation. 34 | // 35 | var addresses = [ 36 | { 37 | host: 'ws1.0.0.0', 38 | port: 80 39 | }, 40 | { 41 | host: 'ws2.0.0.0', 42 | port: 80 43 | } 44 | ]; 45 | 46 | // 47 | // Create a HttpProxy object for each target 48 | // 49 | 50 | var proxies = addresses.map(function (target) { 51 | return new httpProxy.createProxyServer({ 52 | target: target 53 | }); 54 | }); 55 | 56 | // 57 | // Get the proxy at the front of the array, put it at the end and return it 58 | // If you want a fancier balancer, put your code here 59 | // 60 | 61 | function nextProxy() { 62 | var proxy = proxies.shift(); 63 | proxies.push(proxy); 64 | return proxy; 65 | } 66 | 67 | // 68 | // Get the 'next' proxy and send the http request 69 | // 70 | 71 | var server = http.createServer(function (req, res) { 72 | nextProxy().web(req, res); 73 | }); 74 | 75 | // 76 | // Get the 'next' proxy and send the upgrade request 77 | // 78 | 79 | server.on('upgrade', function (req, socket, head) { 80 | nextProxy().ws(req, socket, head); 81 | }); 82 | 83 | server.listen(8001); 84 | -------------------------------------------------------------------------------- /examples/http/forward-and-target-proxy.js: -------------------------------------------------------------------------------- 1 | /* 2 | forward-and-target-proxy.js: Example of proxying over HTTP with additional forward proxy 3 | 4 | Copyright (c) 2013 - 2016 Charlie Robbins, Jarrett Cruger & the Contributors. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | */ 26 | 27 | var util = require('util'), 28 | colors = require('colors'), 29 | http = require('http'), 30 | httpProxy = require('../../lib/http-proxy'); 31 | 32 | // 33 | // Setup proxy server with forwarding 34 | // 35 | httpProxy.createServer({ 36 | target: { 37 | port: 9006, 38 | host: 'localhost' 39 | }, 40 | forward: { 41 | port: 9007, 42 | host: 'localhost' 43 | } 44 | }).listen(8006); 45 | 46 | // 47 | // Target Http Server 48 | // 49 | http.createServer(function (req, res) { 50 | res.writeHead(200, { 'Content-Type': 'text/plain' }); 51 | res.write('request successfully proxied to: ' + req.url + '\n' + JSON.stringify(req.headers, true, 2)); 52 | res.end(); 53 | }).listen(9006); 54 | 55 | // 56 | // Target Http Forwarding Server 57 | // 58 | http.createServer(function (req, res) { 59 | util.puts('Receiving forward for: ' + req.url); 60 | res.writeHead(200, { 'Content-Type': 'text/plain' }); 61 | res.write('request successfully forwarded to: ' + req.url + '\n' + JSON.stringify(req.headers, true, 2)); 62 | res.end(); 63 | }).listen(9007); 64 | 65 | util.puts('http proxy server '.blue + 'started '.green.bold + 'on port '.blue + '8006 '.yellow + 'with forward proxy'.magenta.underline); 66 | util.puts('http server '.blue + 'started '.green.bold + 'on port '.blue + '9006 '.yellow); 67 | util.puts('http forward server '.blue + 'started '.green.bold + 'on port '.blue + '9007 '.yellow); -------------------------------------------------------------------------------- /lib/http-proxy.js: -------------------------------------------------------------------------------- 1 | // Use explicit /index.js to help browserify negociation in require '/lib/http-proxy' (!) 2 | var ProxyServer = require('./http-proxy/index.js').Server; 3 | 4 | 5 | /** 6 | * Creates the proxy server. 7 | * 8 | * Examples: 9 | * 10 | * httpProxy.createProxyServer({ .. }, 8000) 11 | * // => '{ web: [Function], ws: [Function] ... }' 12 | * 13 | * @param {Object} Options Config object passed to the proxy 14 | * 15 | * @return {Object} Proxy Proxy object with handlers for `ws` and `web` requests 16 | * 17 | * @api public 18 | */ 19 | 20 | 21 | function createProxyServer(options) { 22 | /* 23 | * `options` is needed and it must have the following layout: 24 | * 25 | * { 26 | * target : 27 | * forward: 28 | * agent : 29 | * ssl : 30 | * ws : 31 | * xfwd : 32 | * secure : 33 | * toProxy: 34 | * prependPath: 35 | * ignorePath: 36 | * localAddress : 37 | * changeOrigin: 38 | * preserveHeaderKeyCase: 39 | * auth : Basic authentication i.e. 'user:password' to compute an Authorization header. 40 | * hostRewrite: rewrites the location hostname on (201/301/302/307/308) redirects, Default: null. 41 | * autoRewrite: rewrites the location host/port on (201/301/302/307/308) redirects based on requested host/port. Default: false. 42 | * protocolRewrite: rewrites the location protocol on (201/301/302/307/308) redirects to 'http' or 'https'. Default: null. 43 | * } 44 | * 45 | * NOTE: `options.ws` and `options.ssl` are optional. 46 | * `options.target and `options.forward` cannot be 47 | * both missing 48 | * } 49 | */ 50 | 51 | return new ProxyServer(options); 52 | } 53 | 54 | 55 | ProxyServer.createProxyServer = createProxyServer; 56 | ProxyServer.createServer = createProxyServer; 57 | ProxyServer.createProxy = createProxyServer; 58 | 59 | 60 | 61 | 62 | /** 63 | * Export the proxy "Server" as the main export. 64 | */ 65 | module.exports = ProxyServer; 66 | 67 | -------------------------------------------------------------------------------- /examples/websocket/standalone-websocket-proxy.js: -------------------------------------------------------------------------------- 1 | /* 2 | standalone-websocket-proxy.js: Example of proxying websockets over HTTP with a standalone HTTP server. 3 | 4 | Copyright (c) 2013 - 2016 Charlie Robbins, Jarrett Cruger & the Contributors. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | */ 26 | 27 | var util = require('util'), 28 | http = require('http'), 29 | colors = require('colors'), 30 | httpProxy = require('../../lib/http-proxy'); 31 | 32 | try { 33 | var io = require('socket.io'), 34 | client = require('socket.io-client'); 35 | } 36 | catch (ex) { 37 | console.error('Socket.io is required for this example:'); 38 | console.error('npm ' + 'install'.green); 39 | process.exit(1); 40 | } 41 | 42 | // 43 | // Create the target HTTP server and setup 44 | // socket.io on it. 45 | // 46 | var server = io.listen(9015); 47 | server.sockets.on('connection', function (client) { 48 | util.debug('Got websocket connection'); 49 | 50 | client.on('message', function (msg) { 51 | util.debug('Got message from client: ' + msg); 52 | }); 53 | 54 | client.send('from server'); 55 | }); 56 | 57 | // 58 | // Setup our server to proxy standard HTTP requests 59 | // 60 | var proxy = new httpProxy.createProxyServer({ 61 | target: { 62 | host: 'localhost', 63 | port: 9015 64 | } 65 | }); 66 | var proxyServer = http.createServer(function (req, res) { 67 | proxy.web(req, res); 68 | }); 69 | 70 | // 71 | // Listen to the `upgrade` event and proxy the 72 | // WebSocket requests as well. 73 | // 74 | proxyServer.on('upgrade', function (req, socket, head) { 75 | proxy.ws(req, socket, head); 76 | }); 77 | 78 | proxyServer.listen(8015); 79 | 80 | // 81 | // Setup the socket.io client against our proxy 82 | // 83 | var ws = client.connect('ws://localhost:8015'); 84 | 85 | ws.on('message', function (msg) { 86 | util.debug('Got message: ' + msg); 87 | ws.send('I am the client'); 88 | }); 89 | -------------------------------------------------------------------------------- /examples/websocket/latent-websocket-proxy.js: -------------------------------------------------------------------------------- 1 | /* 2 | standalone-websocket-proxy.js: Example of proxying websockets over HTTP with a standalone HTTP server. 3 | 4 | Copyright (c) 2013 - 2016 Charlie Robbins, Jarrett Cruger & the Contributors. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | */ 26 | 27 | var util = require('util'), 28 | http = require('http'), 29 | colors = require('colors'), 30 | httpProxy = require('../../lib/http-proxy'); 31 | 32 | try { 33 | var io = require('socket.io'), 34 | client = require('socket.io-client'); 35 | } 36 | catch (ex) { 37 | console.error('Socket.io is required for this example:'); 38 | console.error('npm ' + 'install'.green); 39 | process.exit(1); 40 | } 41 | 42 | // 43 | // Create the target HTTP server and setup 44 | // socket.io on it. 45 | // 46 | var server = io.listen(9016); 47 | server.sockets.on('connection', function (client) { 48 | util.debug('Got websocket connection'); 49 | 50 | client.on('message', function (msg) { 51 | util.debug('Got message from client: ' + msg); 52 | }); 53 | 54 | client.send('from server'); 55 | }); 56 | 57 | // 58 | // Setup our server to proxy standard HTTP requests 59 | // 60 | var proxy = new httpProxy.createProxyServer({ 61 | target: { 62 | host: 'localhost', 63 | port: 9016 64 | } 65 | }); 66 | 67 | var proxyServer = http.createServer(function (req, res) { 68 | proxy.web(req, res); 69 | }); 70 | 71 | // 72 | // Listen to the `upgrade` event and proxy the 73 | // WebSocket requests as well. 74 | // 75 | proxyServer.on('upgrade', function (req, socket, head) { 76 | setTimeout(function () { 77 | proxy.ws(req, socket, head); 78 | }, 1000); 79 | }); 80 | 81 | proxyServer.listen(8016); 82 | 83 | // 84 | // Setup the socket.io client against our proxy 85 | // 86 | var ws = client.connect('ws://localhost:8016'); 87 | 88 | ws.on('message', function (msg) { 89 | util.debug('Got message: ' + msg); 90 | ws.send('I am the client'); 91 | }); 92 | -------------------------------------------------------------------------------- /UPGRADING.md: -------------------------------------------------------------------------------- 1 | Looking to upgrade from `http-proxy@0.x.x` to `http-proxy@1.0`? You've come to the right place! 2 | `http-proxy@1.0` is a from-scratch implementation of `http-proxy` and, as such 3 | brings some breaking changes to APIs. 4 | 5 | ## Server creation 6 | 7 | Available through `.createServer()` or `.createProxyServer()`. 8 | 9 | ```javascript 10 | httpProxy.createServer({ 11 | target:'http://localhost:9003' 12 | }).listen(8003); 13 | ``` 14 | 15 | Check the [README.md](https://github.com/http-party/node-http-proxy/blob/caronte/README.md) for a more detailed explanation of the parameters. 16 | 17 | ## Proxying 18 | 19 | Web proxying is done by calling the `.web()` method on a Proxy instance. You can check among some use cases in the [examples folder](https://github.com/http-party/node-http-proxy/tree/caronte/examples/http) 20 | 21 | ```javascript 22 | // 23 | // Create a HTTP Proxy server with a HTTPS target 24 | // 25 | httpProxy.createProxyServer({ 26 | target: 'https://google.com', 27 | agent : https.globalAgent, 28 | headers: { 29 | host: 'google.com' 30 | } 31 | }).listen(8011); 32 | 33 | ``` 34 | 35 | Websockets are proxied by the `.ws()` method. The [examples folder](https://github.com/http-party/node-http-proxy/tree/caronte/examples/websocket) again provides a lot of useful snippets! 36 | 37 | ```javascript 38 | var proxy = new httpProxy.createProxyServer({ 39 | target: { 40 | host: 'localhost', 41 | port: 9015 42 | } 43 | }); 44 | var proxyServer = http.createServer(function (req, res) { 45 | proxy.web(req, res); 46 | }); 47 | 48 | // 49 | // Listen to the `upgrade` event and proxy the 50 | // WebSocket requests as well. 51 | // 52 | proxyServer.on('upgrade', function (req, socket, head) { 53 | proxy.ws(req, socket, head); 54 | }); 55 | ``` 56 | 57 | ## Error Handling 58 | 59 | It is possible to listen globally on the `error` event on the server. In alternative, a 60 | callback passed to `.web()` or `.ws()` as last parameter is also accepted. 61 | 62 | ```javascript 63 | var proxy = httpProxy.createServer({ 64 | target:'http://localhost:9005' 65 | }); 66 | 67 | // 68 | // Tell the proxy to listen on port 8000 69 | // 70 | proxy.listen(8005); 71 | 72 | // 73 | // Listen for the `error` event on `proxy`. 74 | proxy.on('error', function (err, req, res) { 75 | res.writeHead(500, { 76 | 'Content-Type': 'text/plain' 77 | }); 78 | 79 | res.end('Something went wrong. And we are reporting a custom error message.'); 80 | }); 81 | ``` 82 | 83 | ## Dropped 84 | 85 | Since the API was rewritten to be extremely flexible we decided to drop some features 86 | which were in the core and delegate them to eventual "userland" modules. 87 | 88 | - Middleware API 89 | - ProxyTable API 90 | 91 | ### Middleware API 92 | 93 | The new API makes it really easy to implement code that behaves like the old Middleware API. You can check some examples [here](https://github.com/http-party/node-http-proxy/tree/caronte/examples/middleware) 94 | 95 | ### ProxyTable API 96 | 97 | See this [link](https://github.com/donasaur/http-proxy-rules/) for an add-on proxy rules module that you can use to simulate the old ProxyTable API. 98 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at . All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /examples/middleware/bodyDecoder-middleware.js: -------------------------------------------------------------------------------- 1 | /* 2 | bodyDecoder-middleware.js: Basic example of `connect.bodyParser()` middleware in node-http-proxy 3 | 4 | Copyright (c) 2013 - 2016 Charlie Robbins, Jarrett Cruger & the Contributors. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | */ 26 | 27 | var http = require('http'), 28 | connect = require('connect'), 29 | request = require('request'), 30 | colors = require('colors'), 31 | util = require('util'), 32 | queryString = require('querystring'), 33 | bodyParser = require('body-parser'), 34 | httpProxy = require('../../lib/http-proxy'), 35 | proxy = httpProxy.createProxyServer({}); 36 | 37 | 38 | //restream parsed body before proxying 39 | proxy.on('proxyReq', function(proxyReq, req, res, options) { 40 | if (!req.body || !Object.keys(req.body).length) { 41 | return; 42 | } 43 | 44 | var contentType = proxyReq.getHeader('Content-Type'); 45 | var bodyData; 46 | 47 | if (contentType === 'application/json') { 48 | bodyData = JSON.stringify(req.body); 49 | } 50 | 51 | if (contentType === 'application/x-www-form-urlencoded') { 52 | bodyData = queryString.stringify(req.body); 53 | } 54 | 55 | if (bodyData) { 56 | proxyReq.setHeader('Content-Length', Buffer.byteLength(bodyData)); 57 | proxyReq.write(bodyData); 58 | } 59 | }); 60 | 61 | 62 | // 63 | // Basic Http Proxy Server 64 | // 65 | var app = connect() 66 | .use(bodyParser.json())//json parser 67 | .use(bodyParser.urlencoded())//urlencoded parser 68 | .use(function(req, res){ 69 | // modify body here, 70 | // eg: req.body = {a: 1}. 71 | console.log('proxy body:',req.body) 72 | proxy.web(req, res, { 73 | target: 'http://127.0.0.1:9013' 74 | }) 75 | }); 76 | 77 | http.createServer(app).listen(8013, function(){ 78 | console.log('proxy linsten 8013'); 79 | }); 80 | 81 | 82 | 83 | // 84 | // Target Http Server 85 | // 86 | var app1 = connect() 87 | .use(bodyParser.json()) 88 | .use(function(req, res){ 89 | console.log('app1:',req.body) 90 | res.end('request successfully proxied to: ' + req.url + '\n' + JSON.stringify(req.headers, true, 2)); 91 | }); 92 | http.createServer(app1).listen(9013, function(){ 93 | //request to 8013 to proxy 94 | request.post({// 95 | url: 'http://127.0.0.1:8013', 96 | json: {content: 123, type: "greeting from json request"} 97 | },function(err, res,data){ 98 | console.log('return for json request:' ,err, data) 99 | }) 100 | 101 | // application/x-www-form-urlencoded request 102 | request.post({// 103 | url: 'http://127.0.0.1:8013', 104 | form: {content: 123, type: "greeting from urlencoded request"} 105 | },function(err, res,data){ 106 | console.log('return for urlencoded request:' ,err, data) 107 | }) 108 | }); 109 | -------------------------------------------------------------------------------- /test/lib-http-proxy-passes-ws-incoming-test.js: -------------------------------------------------------------------------------- 1 | var httpProxy = require('../lib/http-proxy/passes/ws-incoming'), 2 | expect = require('expect.js'); 3 | 4 | describe('lib/http-proxy/passes/ws-incoming.js', function () { 5 | describe('#checkMethodAndHeader', function () { 6 | it('should drop non-GET connections', function () { 7 | var destroyCalled = false, 8 | stubRequest = { 9 | method: 'DELETE', 10 | headers: {} 11 | }, 12 | stubSocket = { 13 | destroy: function () { 14 | // Simulate Socket.destroy() method when call 15 | destroyCalled = true; 16 | } 17 | } 18 | returnValue = httpProxy.checkMethodAndHeader(stubRequest, stubSocket); 19 | expect(returnValue).to.be(true); 20 | expect(destroyCalled).to.be(true); 21 | }) 22 | 23 | it('should drop connections when no upgrade header', function () { 24 | var destroyCalled = false, 25 | stubRequest = { 26 | method: 'GET', 27 | headers: {} 28 | }, 29 | stubSocket = { 30 | destroy: function () { 31 | // Simulate Socket.destroy() method when call 32 | destroyCalled = true; 33 | } 34 | } 35 | returnValue = httpProxy.checkMethodAndHeader(stubRequest, stubSocket); 36 | expect(returnValue).to.be(true); 37 | expect(destroyCalled).to.be(true); 38 | }) 39 | 40 | it('should drop connections when upgrade header is different of `websocket`', function () { 41 | var destroyCalled = false, 42 | stubRequest = { 43 | method: 'GET', 44 | headers: { 45 | upgrade: 'anotherprotocol' 46 | } 47 | }, 48 | stubSocket = { 49 | destroy: function () { 50 | // Simulate Socket.destroy() method when call 51 | destroyCalled = true; 52 | } 53 | } 54 | returnValue = httpProxy.checkMethodAndHeader(stubRequest, stubSocket); 55 | expect(returnValue).to.be(true); 56 | expect(destroyCalled).to.be(true); 57 | }) 58 | 59 | it('should return nothing when all is ok', function () { 60 | var destroyCalled = false, 61 | stubRequest = { 62 | method: 'GET', 63 | headers: { 64 | upgrade: 'websocket' 65 | } 66 | }, 67 | stubSocket = { 68 | destroy: function () { 69 | // Simulate Socket.destroy() method when call 70 | destroyCalled = true; 71 | } 72 | } 73 | returnValue = httpProxy.checkMethodAndHeader(stubRequest, stubSocket); 74 | expect(returnValue).to.be(undefined); 75 | expect(destroyCalled).to.be(false); 76 | }) 77 | }); 78 | 79 | describe('#XHeaders', function () { 80 | it('return if no forward request', function () { 81 | var returnValue = httpProxy.XHeaders({}, {}, {}); 82 | expect(returnValue).to.be(undefined); 83 | }); 84 | 85 | it('set the correct x-forwarded-* headers from req.connection', function () { 86 | var stubRequest = { 87 | connection: { 88 | remoteAddress: '192.168.1.2', 89 | remotePort: '8080' 90 | }, 91 | headers: { 92 | host: '192.168.1.2:8080' 93 | } 94 | } 95 | httpProxy.XHeaders(stubRequest, {}, { xfwd: true }); 96 | expect(stubRequest.headers['x-forwarded-for']).to.be('192.168.1.2'); 97 | expect(stubRequest.headers['x-forwarded-port']).to.be('8080'); 98 | expect(stubRequest.headers['x-forwarded-proto']).to.be('ws'); 99 | }); 100 | 101 | it('set the correct x-forwarded-* headers from req.socket', function () { 102 | var stubRequest = { 103 | socket: { 104 | remoteAddress: '192.168.1.3', 105 | remotePort: '8181' 106 | }, 107 | connection: { 108 | pair: true 109 | }, 110 | headers: { 111 | host: '192.168.1.3:8181' 112 | } 113 | }; 114 | httpProxy.XHeaders(stubRequest, {}, { xfwd: true }); 115 | expect(stubRequest.headers['x-forwarded-for']).to.be('192.168.1.3'); 116 | expect(stubRequest.headers['x-forwarded-port']).to.be('8181'); 117 | expect(stubRequest.headers['x-forwarded-proto']).to.be('wss'); 118 | }); 119 | }); 120 | }); 121 | -------------------------------------------------------------------------------- /lib/http-proxy/passes/ws-incoming.js: -------------------------------------------------------------------------------- 1 | var http = require('http'), 2 | https = require('https'), 3 | common = require('../common'); 4 | 5 | /*! 6 | * Array of passes. 7 | * 8 | * A `pass` is just a function that is executed on `req, socket, options` 9 | * so that you can easily add new checks while still keeping the base 10 | * flexible. 11 | */ 12 | 13 | /* 14 | * Websockets Passes 15 | * 16 | */ 17 | 18 | 19 | module.exports = { 20 | /** 21 | * WebSocket requests must have the `GET` method and 22 | * the `upgrade:websocket` header 23 | * 24 | * @param {ClientRequest} Req Request object 25 | * @param {Socket} Websocket 26 | * 27 | * @api private 28 | */ 29 | 30 | checkMethodAndHeader : function checkMethodAndHeader(req, socket) { 31 | if (req.method !== 'GET' || !req.headers.upgrade) { 32 | socket.destroy(); 33 | return true; 34 | } 35 | 36 | if (req.headers.upgrade.toLowerCase() !== 'websocket') { 37 | socket.destroy(); 38 | return true; 39 | } 40 | }, 41 | 42 | /** 43 | * Sets `x-forwarded-*` headers if specified in config. 44 | * 45 | * @param {ClientRequest} Req Request object 46 | * @param {Socket} Websocket 47 | * @param {Object} Options Config object passed to the proxy 48 | * 49 | * @api private 50 | */ 51 | 52 | XHeaders : function XHeaders(req, socket, options) { 53 | if(!options.xfwd) return; 54 | 55 | var values = { 56 | for : req.connection.remoteAddress || req.socket.remoteAddress, 57 | port : common.getPort(req), 58 | proto: common.hasEncryptedConnection(req) ? 'wss' : 'ws' 59 | }; 60 | 61 | ['for', 'port', 'proto'].forEach(function(header) { 62 | req.headers['x-forwarded-' + header] = 63 | (req.headers['x-forwarded-' + header] || '') + 64 | (req.headers['x-forwarded-' + header] ? ',' : '') + 65 | values[header]; 66 | }); 67 | }, 68 | 69 | /** 70 | * Does the actual proxying. Make the request and upgrade it 71 | * send the Switching Protocols request and pipe the sockets. 72 | * 73 | * @param {ClientRequest} Req Request object 74 | * @param {Socket} Websocket 75 | * @param {Object} Options Config object passed to the proxy 76 | * 77 | * @api private 78 | */ 79 | stream : function stream(req, socket, options, head, server, clb) { 80 | 81 | var createHttpHeader = function(line, headers) { 82 | return Object.keys(headers).reduce(function (head, key) { 83 | var value = headers[key]; 84 | 85 | if (!Array.isArray(value)) { 86 | head.push(key + ': ' + value); 87 | return head; 88 | } 89 | 90 | for (var i = 0; i < value.length; i++) { 91 | head.push(key + ': ' + value[i]); 92 | } 93 | return head; 94 | }, [line]) 95 | .join('\r\n') + '\r\n\r\n'; 96 | } 97 | 98 | common.setupSocket(socket); 99 | 100 | if (head && head.length) socket.unshift(head); 101 | 102 | 103 | var proxyReq = (common.isSSL.test(options.target.protocol) ? https : http).request( 104 | common.setupOutgoing(options.ssl || {}, options, req) 105 | ); 106 | 107 | // Enable developers to modify the proxyReq before headers are sent 108 | if (server) { server.emit('proxyReqWs', proxyReq, req, socket, options, head); } 109 | 110 | // Error Handler 111 | proxyReq.on('error', onOutgoingError); 112 | proxyReq.on('response', function (res) { 113 | // if upgrade event isn't going to happen, close the socket 114 | if (!res.upgrade) { 115 | socket.write(createHttpHeader('HTTP/' + res.httpVersion + ' ' + res.statusCode + ' ' + res.statusMessage, res.headers)); 116 | res.pipe(socket); 117 | } 118 | }); 119 | 120 | proxyReq.on('upgrade', function(proxyRes, proxySocket, proxyHead) { 121 | proxySocket.on('error', onOutgoingError); 122 | 123 | // Allow us to listen when the websocket has completed 124 | proxySocket.on('end', function () { 125 | server.emit('close', proxyRes, proxySocket, proxyHead); 126 | }); 127 | 128 | // The pipe below will end proxySocket if socket closes cleanly, but not 129 | // if it errors (eg, vanishes from the net and starts returning 130 | // EHOSTUNREACH). We need to do that explicitly. 131 | socket.on('error', function () { 132 | proxySocket.end(); 133 | }); 134 | 135 | common.setupSocket(proxySocket); 136 | 137 | if (proxyHead && proxyHead.length) proxySocket.unshift(proxyHead); 138 | 139 | // 140 | // Remark: Handle writing the headers to the socket when switching protocols 141 | // Also handles when a header is an array 142 | // 143 | socket.write(createHttpHeader('HTTP/1.1 101 Switching Protocols', proxyRes.headers)); 144 | 145 | proxySocket.pipe(socket).pipe(proxySocket); 146 | 147 | server.emit('open', proxySocket); 148 | server.emit('proxySocket', proxySocket); //DEPRECATED. 149 | }); 150 | 151 | return proxyReq.end(); // XXX: CHECK IF THIS IS THIS CORRECT 152 | 153 | function onOutgoingError(err) { 154 | if (clb) { 155 | clb(err, req, socket); 156 | } else { 157 | server.emit('error', err, req, socket); 158 | } 159 | socket.end(); 160 | } 161 | } 162 | }; 163 | -------------------------------------------------------------------------------- /lib/http-proxy/passes/web-outgoing.js: -------------------------------------------------------------------------------- 1 | var url = require('url'), 2 | common = require('../common'); 3 | 4 | 5 | var redirectRegex = /^201|30(1|2|7|8)$/; 6 | 7 | /*! 8 | * Array of passes. 9 | * 10 | * A `pass` is just a function that is executed on `req, res, options` 11 | * so that you can easily add new checks while still keeping the base 12 | * flexible. 13 | */ 14 | 15 | module.exports = { // <-- 16 | 17 | /** 18 | * If is a HTTP 1.0 request, remove chunk headers 19 | * 20 | * @param {ClientRequest} Req Request object 21 | * @param {IncomingMessage} Res Response object 22 | * @param {proxyResponse} Res Response object from the proxy request 23 | * 24 | * @api private 25 | */ 26 | removeChunked: function removeChunked(req, res, proxyRes) { 27 | if (req.httpVersion === '1.0') { 28 | delete proxyRes.headers['transfer-encoding']; 29 | } 30 | }, 31 | 32 | /** 33 | * If is a HTTP 1.0 request, set the correct connection header 34 | * or if connection header not present, then use `keep-alive` 35 | * 36 | * @param {ClientRequest} Req Request object 37 | * @param {IncomingMessage} Res Response object 38 | * @param {proxyResponse} Res Response object from the proxy request 39 | * 40 | * @api private 41 | */ 42 | setConnection: function setConnection(req, res, proxyRes) { 43 | if (req.httpVersion === '1.0') { 44 | proxyRes.headers.connection = req.headers.connection || 'close'; 45 | } else if (req.httpVersion !== '2.0' && !proxyRes.headers.connection) { 46 | proxyRes.headers.connection = req.headers.connection || 'keep-alive'; 47 | } 48 | }, 49 | 50 | setRedirectHostRewrite: function setRedirectHostRewrite(req, res, proxyRes, options) { 51 | if ((options.hostRewrite || options.autoRewrite || options.protocolRewrite) 52 | && proxyRes.headers['location'] 53 | && redirectRegex.test(proxyRes.statusCode)) { 54 | var target = url.parse(options.target); 55 | var u = url.parse(proxyRes.headers['location']); 56 | 57 | // make sure the redirected host matches the target host before rewriting 58 | if (target.host != u.host) { 59 | return; 60 | } 61 | 62 | if (options.hostRewrite) { 63 | u.host = options.hostRewrite; 64 | } else if (options.autoRewrite) { 65 | u.host = req.headers['host']; 66 | } 67 | if (options.protocolRewrite) { 68 | u.protocol = options.protocolRewrite; 69 | } 70 | 71 | proxyRes.headers['location'] = u.format(); 72 | } 73 | }, 74 | /** 75 | * Copy headers from proxyResponse to response 76 | * set each header in response object. 77 | * 78 | * @param {ClientRequest} Req Request object 79 | * @param {IncomingMessage} Res Response object 80 | * @param {proxyResponse} Res Response object from the proxy request 81 | * @param {Object} Options options.cookieDomainRewrite: Config to rewrite cookie domain 82 | * 83 | * @api private 84 | */ 85 | writeHeaders: function writeHeaders(req, res, proxyRes, options) { 86 | var rewriteCookieDomainConfig = options.cookieDomainRewrite, 87 | rewriteCookiePathConfig = options.cookiePathRewrite, 88 | preserveHeaderKeyCase = options.preserveHeaderKeyCase, 89 | rawHeaderKeyMap, 90 | setHeader = function(key, header) { 91 | if (header == undefined) return; 92 | if (rewriteCookieDomainConfig && key.toLowerCase() === 'set-cookie') { 93 | header = common.rewriteCookieProperty(header, rewriteCookieDomainConfig, 'domain'); 94 | } 95 | if (rewriteCookiePathConfig && key.toLowerCase() === 'set-cookie') { 96 | header = common.rewriteCookieProperty(header, rewriteCookiePathConfig, 'path'); 97 | } 98 | res.setHeader(String(key).trim(), header); 99 | }; 100 | 101 | if (typeof rewriteCookieDomainConfig === 'string') { //also test for '' 102 | rewriteCookieDomainConfig = { '*': rewriteCookieDomainConfig }; 103 | } 104 | 105 | if (typeof rewriteCookiePathConfig === 'string') { //also test for '' 106 | rewriteCookiePathConfig = { '*': rewriteCookiePathConfig }; 107 | } 108 | 109 | // message.rawHeaders is added in: v0.11.6 110 | // https://nodejs.org/api/http.html#http_message_rawheaders 111 | if (preserveHeaderKeyCase && proxyRes.rawHeaders != undefined) { 112 | rawHeaderKeyMap = {}; 113 | for (var i = 0; i < proxyRes.rawHeaders.length; i += 2) { 114 | var key = proxyRes.rawHeaders[i]; 115 | rawHeaderKeyMap[key.toLowerCase()] = key; 116 | } 117 | } 118 | 119 | Object.keys(proxyRes.headers).forEach(function(key) { 120 | var header = proxyRes.headers[key]; 121 | if (preserveHeaderKeyCase && rawHeaderKeyMap) { 122 | key = rawHeaderKeyMap[key] || key; 123 | } 124 | setHeader(key, header); 125 | }); 126 | }, 127 | 128 | /** 129 | * Set the statusCode from the proxyResponse 130 | * 131 | * @param {ClientRequest} Req Request object 132 | * @param {IncomingMessage} Res Response object 133 | * @param {proxyResponse} Res Response object from the proxy request 134 | * 135 | * @api private 136 | */ 137 | writeStatusCode: function writeStatusCode(req, res, proxyRes) { 138 | // From Node.js docs: response.writeHead(statusCode[, statusMessage][, headers]) 139 | if(proxyRes.statusMessage) { 140 | res.statusCode = proxyRes.statusCode; 141 | res.statusMessage = proxyRes.statusMessage; 142 | } else { 143 | res.statusCode = proxyRes.statusCode; 144 | } 145 | } 146 | 147 | }; 148 | -------------------------------------------------------------------------------- /lib/http-proxy/index.js: -------------------------------------------------------------------------------- 1 | var httpProxy = module.exports, 2 | extend = require('util')._extend, 3 | parse_url = require('url').parse, 4 | EE3 = require('eventemitter3'), 5 | http = require('http'), 6 | https = require('https'), 7 | web = require('./passes/web-incoming'), 8 | ws = require('./passes/ws-incoming'); 9 | 10 | httpProxy.Server = ProxyServer; 11 | 12 | /** 13 | * Returns a function that creates the loader for 14 | * either `ws` or `web`'s passes. 15 | * 16 | * Examples: 17 | * 18 | * httpProxy.createRightProxy('ws') 19 | * // => [Function] 20 | * 21 | * @param {String} Type Either 'ws' or 'web' 22 | *  23 | * @return {Function} Loader Function that when called returns an iterator for the right passes 24 | * 25 | * @api private 26 | */ 27 | 28 | function createRightProxy(type) { 29 | 30 | return function(options) { 31 | return function(req, res /*, [head], [opts] */) { 32 | var passes = (type === 'ws') ? this.wsPasses : this.webPasses, 33 | args = [].slice.call(arguments), 34 | cntr = args.length - 1, 35 | head, cbl; 36 | 37 | /* optional args parse begin */ 38 | if(typeof args[cntr] === 'function') { 39 | cbl = args[cntr]; 40 | 41 | cntr--; 42 | } 43 | 44 | var requestOptions = options; 45 | if( 46 | !(args[cntr] instanceof Buffer) && 47 | args[cntr] !== res 48 | ) { 49 | //Copy global options 50 | requestOptions = extend({}, options); 51 | //Overwrite with request options 52 | extend(requestOptions, args[cntr]); 53 | 54 | cntr--; 55 | } 56 | 57 | if(args[cntr] instanceof Buffer) { 58 | head = args[cntr]; 59 | } 60 | 61 | /* optional args parse end */ 62 | 63 | ['target', 'forward'].forEach(function(e) { 64 | if (typeof requestOptions[e] === 'string') 65 | requestOptions[e] = parse_url(requestOptions[e]); 66 | }); 67 | 68 | if (!requestOptions.target && !requestOptions.forward) { 69 | return this.emit('error', new Error('Must provide a proper URL as target')); 70 | } 71 | 72 | for(var i=0; i < passes.length; i++) { 73 | /** 74 | * Call of passes functions 75 | * pass(req, res, options, head) 76 | * 77 | * In WebSockets case the `res` variable 78 | * refer to the connection socket 79 | * pass(req, socket, options, head) 80 | */ 81 | if(passes[i](req, res, requestOptions, head, this, cbl)) { // passes can return a truthy value to halt the loop 82 | break; 83 | } 84 | } 85 | }; 86 | }; 87 | } 88 | httpProxy.createRightProxy = createRightProxy; 89 | 90 | function ProxyServer(options) { 91 | EE3.call(this); 92 | 93 | options = options || {}; 94 | options.prependPath = options.prependPath === false ? false : true; 95 | 96 | this.web = this.proxyRequest = createRightProxy('web')(options); 97 | this.ws = this.proxyWebsocketRequest = createRightProxy('ws')(options); 98 | this.options = options; 99 | 100 | this.webPasses = Object.keys(web).map(function(pass) { 101 | return web[pass]; 102 | }); 103 | 104 | this.wsPasses = Object.keys(ws).map(function(pass) { 105 | return ws[pass]; 106 | }); 107 | 108 | this.on('error', this.onError, this); 109 | 110 | } 111 | 112 | require('util').inherits(ProxyServer, EE3); 113 | 114 | ProxyServer.prototype.onError = function (err) { 115 | // 116 | // Remark: Replicate node core behavior using EE3 117 | // so we force people to handle their own errors 118 | // 119 | if(this.listeners('error').length === 1) { 120 | throw err; 121 | } 122 | }; 123 | 124 | ProxyServer.prototype.listen = function(port, hostname) { 125 | var self = this, 126 | closure = function(req, res) { self.web(req, res); }; 127 | 128 | this._server = this.options.ssl ? 129 | https.createServer(this.options.ssl, closure) : 130 | http.createServer(closure); 131 | 132 | if(this.options.ws) { 133 | this._server.on('upgrade', function(req, socket, head) { self.ws(req, socket, head); }); 134 | } 135 | 136 | this._server.listen(port, hostname); 137 | 138 | return this; 139 | }; 140 | 141 | ProxyServer.prototype.close = function(callback) { 142 | var self = this; 143 | if (this._server) { 144 | this._server.close(done); 145 | } 146 | 147 | // Wrap callback to nullify server after all open connections are closed. 148 | function done() { 149 | self._server = null; 150 | if (callback) { 151 | callback.apply(null, arguments); 152 | } 153 | }; 154 | }; 155 | 156 | ProxyServer.prototype.before = function(type, passName, callback) { 157 | if (type !== 'ws' && type !== 'web') { 158 | throw new Error('type must be `web` or `ws`'); 159 | } 160 | var passes = (type === 'ws') ? this.wsPasses : this.webPasses, 161 | i = false; 162 | 163 | passes.forEach(function(v, idx) { 164 | if(v.name === passName) i = idx; 165 | }) 166 | 167 | if(i === false) throw new Error('No such pass'); 168 | 169 | passes.splice(i, 0, callback); 170 | }; 171 | ProxyServer.prototype.after = function(type, passName, callback) { 172 | if (type !== 'ws' && type !== 'web') { 173 | throw new Error('type must be `web` or `ws`'); 174 | } 175 | var passes = (type === 'ws') ? this.wsPasses : this.webPasses, 176 | i = false; 177 | 178 | passes.forEach(function(v, idx) { 179 | if(v.name === passName) i = idx; 180 | }) 181 | 182 | if(i === false) throw new Error('No such pass'); 183 | 184 | passes.splice(i++, 0, callback); 185 | }; 186 | -------------------------------------------------------------------------------- /lib/http-proxy/passes/web-incoming.js: -------------------------------------------------------------------------------- 1 | var httpNative = require('http'), 2 | httpsNative = require('https'), 3 | web_o = require('./web-outgoing'), 4 | common = require('../common'), 5 | followRedirects = require('follow-redirects'); 6 | 7 | web_o = Object.keys(web_o).map(function(pass) { 8 | return web_o[pass]; 9 | }); 10 | 11 | var nativeAgents = { http: httpNative, https: httpsNative }; 12 | 13 | /*! 14 | * Array of passes. 15 | * 16 | * A `pass` is just a function that is executed on `req, res, options` 17 | * so that you can easily add new checks while still keeping the base 18 | * flexible. 19 | */ 20 | 21 | 22 | module.exports = { 23 | 24 | /** 25 | * Sets `content-length` to '0' if request is of DELETE type. 26 | * 27 | * @param {ClientRequest} Req Request object 28 | * @param {IncomingMessage} Res Response object 29 | * @param {Object} Options Config object passed to the proxy 30 | * 31 | * @api private 32 | */ 33 | 34 | deleteLength: function deleteLength(req, res, options) { 35 | if((req.method === 'DELETE' || req.method === 'OPTIONS') 36 | && !req.headers['content-length']) { 37 | req.headers['content-length'] = '0'; 38 | delete req.headers['transfer-encoding']; 39 | } 40 | }, 41 | 42 | /** 43 | * Sets timeout in request socket if it was specified in options. 44 | * 45 | * @param {ClientRequest} Req Request object 46 | * @param {IncomingMessage} Res Response object 47 | * @param {Object} Options Config object passed to the proxy 48 | * 49 | * @api private 50 | */ 51 | 52 | timeout: function timeout(req, res, options) { 53 | if(options.timeout) { 54 | req.socket.setTimeout(options.timeout); 55 | } 56 | }, 57 | 58 | /** 59 | * Sets `x-forwarded-*` headers if specified in config. 60 | * 61 | * @param {ClientRequest} Req Request object 62 | * @param {IncomingMessage} Res Response object 63 | * @param {Object} Options Config object passed to the proxy 64 | * 65 | * @api private 66 | */ 67 | 68 | XHeaders: function XHeaders(req, res, options) { 69 | if(!options.xfwd) return; 70 | 71 | var encrypted = req.isSpdy || common.hasEncryptedConnection(req); 72 | var values = { 73 | for : req.connection.remoteAddress || req.socket.remoteAddress, 74 | port : common.getPort(req), 75 | proto: encrypted ? 'https' : 'http' 76 | }; 77 | 78 | ['for', 'port', 'proto'].forEach(function(header) { 79 | req.headers['x-forwarded-' + header] = 80 | (req.headers['x-forwarded-' + header] || '') + 81 | (req.headers['x-forwarded-' + header] ? ',' : '') + 82 | values[header]; 83 | }); 84 | 85 | req.headers['x-forwarded-host'] = req.headers['x-forwarded-host'] || req.headers['host'] || ''; 86 | }, 87 | 88 | /** 89 | * Does the actual proxying. If `forward` is enabled fires up 90 | * a ForwardStream, same happens for ProxyStream. The request 91 | * just dies otherwise. 92 | * 93 | * @param {ClientRequest} Req Request object 94 | * @param {IncomingMessage} Res Response object 95 | * @param {Object} Options Config object passed to the proxy 96 | * 97 | * @api private 98 | */ 99 | 100 | stream: function stream(req, res, options, _, server, clb) { 101 | 102 | // And we begin! 103 | server.emit('start', req, res, options.target || options.forward); 104 | 105 | var agents = options.followRedirects ? followRedirects : nativeAgents; 106 | var http = agents.http; 107 | var https = agents.https; 108 | 109 | if(options.forward) { 110 | // If forward enable, so just pipe the request 111 | var forwardReq = (options.forward.protocol === 'https:' ? https : http).request( 112 | common.setupOutgoing(options.ssl || {}, options, req, 'forward') 113 | ); 114 | 115 | // error handler (e.g. ECONNRESET, ECONNREFUSED) 116 | // Handle errors on incoming request as well as it makes sense to 117 | var forwardError = createErrorHandler(forwardReq, options.forward); 118 | req.on('error', forwardError); 119 | forwardReq.on('error', forwardError); 120 | 121 | (options.buffer || req).pipe(forwardReq); 122 | if(!options.target) { return res.end(); } 123 | } 124 | 125 | // Request initalization 126 | var proxyReq = (options.target.protocol === 'https:' ? https : http).request( 127 | common.setupOutgoing(options.ssl || {}, options, req) 128 | ); 129 | 130 | // Enable developers to modify the proxyReq before headers are sent 131 | proxyReq.on('socket', function(socket) { 132 | if(server && !proxyReq.getHeader('expect')) { 133 | server.emit('proxyReq', proxyReq, req, res, options); 134 | } 135 | }); 136 | 137 | // allow outgoing socket to timeout so that we could 138 | // show an error page at the initial request 139 | if(options.proxyTimeout) { 140 | proxyReq.setTimeout(options.proxyTimeout, function() { 141 | proxyReq.abort(); 142 | }); 143 | } 144 | 145 | // Ensure we abort proxy if request is aborted 146 | req.on('aborted', function () { 147 | proxyReq.abort(); 148 | }); 149 | 150 | // handle errors in proxy and incoming request, just like for forward proxy 151 | var proxyError = createErrorHandler(proxyReq, options.target); 152 | req.on('error', proxyError); 153 | proxyReq.on('error', proxyError); 154 | 155 | function createErrorHandler(proxyReq, url) { 156 | return function proxyError(err) { 157 | if (req.socket.destroyed && err.code === 'ECONNRESET') { 158 | server.emit('econnreset', err, req, res, url); 159 | return proxyReq.abort(); 160 | } 161 | 162 | if (clb) { 163 | clb(err, req, res, url); 164 | } else { 165 | server.emit('error', err, req, res, url); 166 | } 167 | } 168 | } 169 | 170 | (options.buffer || req).pipe(proxyReq); 171 | 172 | proxyReq.on('response', function(proxyRes) { 173 | if(server) { server.emit('proxyRes', proxyRes, req, res); } 174 | 175 | if(!res.headersSent && !options.selfHandleResponse) { 176 | for(var i=0; i < web_o.length; i++) { 177 | if(web_o[i](req, res, proxyRes, options)) { break; } 178 | } 179 | } 180 | 181 | if (!res.finished) { 182 | // Allow us to listen when the proxy has completed 183 | proxyRes.on('end', function () { 184 | if (server) server.emit('end', req, res, proxyRes); 185 | }); 186 | // We pipe to the response unless its expected to be handled by the user 187 | if (!options.selfHandleResponse) proxyRes.pipe(res); 188 | } else { 189 | if (server) server.emit('end', req, res, proxyRes); 190 | } 191 | }); 192 | } 193 | 194 | }; 195 | -------------------------------------------------------------------------------- /lib/http-proxy/common.js: -------------------------------------------------------------------------------- 1 | var common = exports, 2 | url = require('url'), 3 | extend = require('util')._extend, 4 | required = require('requires-port'); 5 | 6 | var upgradeHeader = /(^|,)\s*upgrade\s*($|,)/i, 7 | isSSL = /^https|wss/; 8 | 9 | /** 10 | * Simple Regex for testing if protocol is https 11 | */ 12 | common.isSSL = isSSL; 13 | /** 14 | * Copies the right headers from `options` and `req` to 15 | * `outgoing` which is then used to fire the proxied 16 | * request. 17 | * 18 | * Examples: 19 | * 20 | * common.setupOutgoing(outgoing, options, req) 21 | * // => { host: ..., hostname: ...} 22 | * 23 | * @param {Object} Outgoing Base object to be filled with required properties 24 | * @param {Object} Options Config object passed to the proxy 25 | * @param {ClientRequest} Req Request Object 26 | * @param {String} Forward String to select forward or target 27 | *  28 | * @return {Object} Outgoing Object with all required properties set 29 | * 30 | * @api private 31 | */ 32 | 33 | common.setupOutgoing = function(outgoing, options, req, forward) { 34 | outgoing.port = options[forward || 'target'].port || 35 | (isSSL.test(options[forward || 'target'].protocol) ? 443 : 80); 36 | 37 | ['host', 'hostname', 'socketPath', 'pfx', 'key', 38 | 'passphrase', 'cert', 'ca', 'ciphers', 'secureProtocol'].forEach( 39 | function(e) { outgoing[e] = options[forward || 'target'][e]; } 40 | ); 41 | 42 | outgoing.method = options.method || req.method; 43 | outgoing.headers = extend({}, req.headers); 44 | 45 | if (options.headers){ 46 | extend(outgoing.headers, options.headers); 47 | } 48 | 49 | if (options.auth) { 50 | outgoing.auth = options.auth; 51 | } 52 | 53 | if (options.ca) { 54 | outgoing.ca = options.ca; 55 | } 56 | 57 | if (isSSL.test(options[forward || 'target'].protocol)) { 58 | outgoing.rejectUnauthorized = (typeof options.secure === "undefined") ? true : options.secure; 59 | } 60 | 61 | 62 | outgoing.agent = options.agent || false; 63 | outgoing.localAddress = options.localAddress; 64 | 65 | // 66 | // Remark: If we are false and not upgrading, set the connection: close. This is the right thing to do 67 | // as node core doesn't handle this COMPLETELY properly yet. 68 | // 69 | if (!outgoing.agent) { 70 | outgoing.headers = outgoing.headers || {}; 71 | if (typeof outgoing.headers.connection !== 'string' 72 | || !upgradeHeader.test(outgoing.headers.connection) 73 | ) { outgoing.headers.connection = 'close'; } 74 | } 75 | 76 | 77 | // the final path is target path + relative path requested by user: 78 | var target = options[forward || 'target']; 79 | var targetPath = target && options.prependPath !== false 80 | ? (target.path || '') 81 | : ''; 82 | 83 | // 84 | // Remark: Can we somehow not use url.parse as a perf optimization? 85 | // 86 | var outgoingPath = !options.toProxy 87 | ? (url.parse(req.url).path || '') 88 | : req.url; 89 | 90 | // 91 | // Remark: ignorePath will just straight up ignore whatever the request's 92 | // path is. This can be labeled as FOOT-GUN material if you do not know what 93 | // you are doing and are using conflicting options. 94 | // 95 | outgoingPath = !options.ignorePath ? outgoingPath : ''; 96 | 97 | outgoing.path = common.urlJoin(targetPath, outgoingPath); 98 | 99 | if (options.changeOrigin) { 100 | outgoing.headers.host = 101 | required(outgoing.port, options[forward || 'target'].protocol) && !hasPort(outgoing.host) 102 | ? outgoing.host + ':' + outgoing.port 103 | : outgoing.host; 104 | } 105 | return outgoing; 106 | }; 107 | 108 | /** 109 | * Set the proper configuration for sockets, 110 | * set no delay and set keep alive, also set 111 | * the timeout to 0. 112 | * 113 | * Examples: 114 | * 115 | * common.setupSocket(socket) 116 | * // => Socket 117 | * 118 | * @param {Socket} Socket instance to setup 119 | *  120 | * @return {Socket} Return the configured socket. 121 | * 122 | * @api private 123 | */ 124 | 125 | common.setupSocket = function(socket) { 126 | socket.setTimeout(0); 127 | socket.setNoDelay(true); 128 | 129 | socket.setKeepAlive(true, 0); 130 | 131 | return socket; 132 | }; 133 | 134 | /** 135 | * Get the port number from the host. Or guess it based on the connection type. 136 | * 137 | * @param {Request} req Incoming HTTP request. 138 | * 139 | * @return {String} The port number. 140 | * 141 | * @api private 142 | */ 143 | common.getPort = function(req) { 144 | var res = req.headers.host ? req.headers.host.match(/:(\d+)/) : ''; 145 | 146 | return res ? 147 | res[1] : 148 | common.hasEncryptedConnection(req) ? '443' : '80'; 149 | }; 150 | 151 | /** 152 | * Check if the request has an encrypted connection. 153 | * 154 | * @param {Request} req Incoming HTTP request. 155 | * 156 | * @return {Boolean} Whether the connection is encrypted or not. 157 | * 158 | * @api private 159 | */ 160 | common.hasEncryptedConnection = function(req) { 161 | return Boolean(req.connection.encrypted || req.connection.pair); 162 | }; 163 | 164 | /** 165 | * OS-agnostic join (doesn't break on URLs like path.join does on Windows)> 166 | * 167 | * @return {String} The generated path. 168 | * 169 | * @api private 170 | */ 171 | 172 | common.urlJoin = function() { 173 | // 174 | // We do not want to mess with the query string. All we want to touch is the path. 175 | // 176 | var args = Array.prototype.slice.call(arguments), 177 | lastIndex = args.length - 1, 178 | last = args[lastIndex], 179 | lastSegs = last.split('?'), 180 | retSegs; 181 | 182 | args[lastIndex] = lastSegs.shift(); 183 | 184 | // 185 | // Join all strings, but remove empty strings so we don't get extra slashes from 186 | // joining e.g. ['', 'am'] 187 | // 188 | retSegs = [ 189 | args.filter(Boolean).join('/') 190 | .replace(/\/+/g, '/') 191 | .replace('http:/', 'http://') 192 | .replace('https:/', 'https://') 193 | ]; 194 | 195 | // Only join the query string if it exists so we don't have trailing a '?' 196 | // on every request 197 | 198 | // Handle case where there could be multiple ? in the URL. 199 | retSegs.push.apply(retSegs, lastSegs); 200 | 201 | return retSegs.join('?') 202 | }; 203 | 204 | /** 205 | * Rewrites or removes the domain of a cookie header 206 | * 207 | * @param {String|Array} Header 208 | * @param {Object} Config, mapping of domain to rewritten domain. 209 | * '*' key to match any domain, null value to remove the domain. 210 | * 211 | * @api private 212 | */ 213 | common.rewriteCookieProperty = function rewriteCookieProperty(header, config, property) { 214 | if (Array.isArray(header)) { 215 | return header.map(function (headerElement) { 216 | return rewriteCookieProperty(headerElement, config, property); 217 | }); 218 | } 219 | return header.replace(new RegExp("(;\\s*" + property + "=)([^;]+)", 'i'), function(match, prefix, previousValue) { 220 | var newValue; 221 | if (previousValue in config) { 222 | newValue = config[previousValue]; 223 | } else if ('*' in config) { 224 | newValue = config['*']; 225 | } else { 226 | //no match, return previous value 227 | return match; 228 | } 229 | if (newValue) { 230 | //replace value 231 | return prefix + newValue; 232 | } else { 233 | //remove value 234 | return ''; 235 | } 236 | }); 237 | }; 238 | 239 | /** 240 | * Check the host and see if it potentially has a port in it (keep it simple) 241 | * 242 | * @returns {Boolean} Whether we have one or not 243 | * 244 | * @api private 245 | */ 246 | function hasPort(host) { 247 | return !!~host.indexOf(':'); 248 | }; 249 | -------------------------------------------------------------------------------- /test/lib-https-proxy-test.js: -------------------------------------------------------------------------------- 1 | var httpProxy = require('../lib/http-proxy'), 2 | semver = require('semver'), 3 | expect = require('expect.js'), 4 | http = require('http') 5 | https = require('https'), 6 | path = require('path'), 7 | fs = require('fs'); 8 | 9 | // 10 | // Expose a port number generator. 11 | // thanks to @3rd-Eden 12 | // 13 | var initialPort = 1024, gen = {}; 14 | Object.defineProperty(gen, 'port', { 15 | get: function get() { 16 | return initialPort++; 17 | } 18 | }); 19 | 20 | describe('lib/http-proxy.js', function() { 21 | describe('HTTPS #createProxyServer', function() { 22 | describe('HTTPS to HTTP', function () { 23 | it('should proxy the request en send back the response', function (done) { 24 | var ports = { source: gen.port, proxy: gen.port }; 25 | var source = http.createServer(function(req, res) { 26 | expect(req.method).to.eql('GET'); 27 | expect(req.headers.host.split(':')[1]).to.eql(ports.proxy); 28 | res.writeHead(200, { 'Content-Type': 'text/plain' }); 29 | res.end('Hello from ' + ports.source); 30 | }); 31 | 32 | source.listen(ports.source); 33 | 34 | var proxy = httpProxy.createProxyServer({ 35 | target: 'http://127.0.0.1:' + ports.source, 36 | ssl: { 37 | key: fs.readFileSync(path.join(__dirname, 'fixtures', 'agent2-key.pem')), 38 | cert: fs.readFileSync(path.join(__dirname, 'fixtures', 'agent2-cert.pem')), 39 | ciphers: 'AES128-GCM-SHA256', 40 | } 41 | }).listen(ports.proxy); 42 | 43 | https.request({ 44 | host: 'localhost', 45 | port: ports.proxy, 46 | path: '/', 47 | method: 'GET', 48 | rejectUnauthorized: false 49 | }, function(res) { 50 | expect(res.statusCode).to.eql(200); 51 | 52 | res.on('data', function (data) { 53 | expect(data.toString()).to.eql('Hello from ' + ports.source); 54 | }); 55 | 56 | res.on('end', function () { 57 | source.close(); 58 | proxy.close(); 59 | done(); 60 | }) 61 | }).end(); 62 | }) 63 | }); 64 | describe('HTTP to HTTPS', function () { 65 | it('should proxy the request en send back the response', function (done) { 66 | var ports = { source: gen.port, proxy: gen.port }; 67 | var source = https.createServer({ 68 | key: fs.readFileSync(path.join(__dirname, 'fixtures', 'agent2-key.pem')), 69 | cert: fs.readFileSync(path.join(__dirname, 'fixtures', 'agent2-cert.pem')), 70 | ciphers: 'AES128-GCM-SHA256', 71 | }, function (req, res) { 72 | expect(req.method).to.eql('GET'); 73 | expect(req.headers.host.split(':')[1]).to.eql(ports.proxy); 74 | res.writeHead(200, { 'Content-Type': 'text/plain' }); 75 | res.end('Hello from ' + ports.source); 76 | }); 77 | 78 | source.listen(ports.source); 79 | 80 | var proxy = httpProxy.createProxyServer({ 81 | target: 'https://127.0.0.1:' + ports.source, 82 | // Allow to use SSL self signed 83 | secure: false 84 | }).listen(ports.proxy); 85 | 86 | http.request({ 87 | hostname: '127.0.0.1', 88 | port: ports.proxy, 89 | method: 'GET' 90 | }, function(res) { 91 | expect(res.statusCode).to.eql(200); 92 | 93 | res.on('data', function (data) { 94 | expect(data.toString()).to.eql('Hello from ' + ports.source); 95 | }); 96 | 97 | res.on('end', function () { 98 | source.close(); 99 | proxy.close(); 100 | done(); 101 | }); 102 | }).end(); 103 | }) 104 | }) 105 | describe('HTTPS to HTTPS', function () { 106 | it('should proxy the request en send back the response', function (done) { 107 | var ports = { source: gen.port, proxy: gen.port }; 108 | var source = https.createServer({ 109 | key: fs.readFileSync(path.join(__dirname, 'fixtures', 'agent2-key.pem')), 110 | cert: fs.readFileSync(path.join(__dirname, 'fixtures', 'agent2-cert.pem')), 111 | ciphers: 'AES128-GCM-SHA256', 112 | }, function(req, res) { 113 | expect(req.method).to.eql('GET'); 114 | expect(req.headers.host.split(':')[1]).to.eql(ports.proxy); 115 | res.writeHead(200, { 'Content-Type': 'text/plain' }); 116 | res.end('Hello from ' + ports.source); 117 | }); 118 | 119 | source.listen(ports.source); 120 | 121 | var proxy = httpProxy.createProxyServer({ 122 | target: 'https://127.0.0.1:' + ports.source, 123 | ssl: { 124 | key: fs.readFileSync(path.join(__dirname, 'fixtures', 'agent2-key.pem')), 125 | cert: fs.readFileSync(path.join(__dirname, 'fixtures', 'agent2-cert.pem')), 126 | ciphers: 'AES128-GCM-SHA256', 127 | }, 128 | secure: false 129 | }).listen(ports.proxy); 130 | 131 | https.request({ 132 | host: 'localhost', 133 | port: ports.proxy, 134 | path: '/', 135 | method: 'GET', 136 | rejectUnauthorized: false 137 | }, function(res) { 138 | expect(res.statusCode).to.eql(200); 139 | 140 | res.on('data', function (data) { 141 | expect(data.toString()).to.eql('Hello from ' + ports.source); 142 | }); 143 | 144 | res.on('end', function () { 145 | source.close(); 146 | proxy.close(); 147 | done(); 148 | }) 149 | }).end(); 150 | }) 151 | }); 152 | describe('HTTPS not allow SSL self signed', function () { 153 | it('should fail with error', function (done) { 154 | var ports = { source: gen.port, proxy: gen.port }; 155 | var source = https.createServer({ 156 | key: fs.readFileSync(path.join(__dirname, 'fixtures', 'agent2-key.pem')), 157 | cert: fs.readFileSync(path.join(__dirname, 'fixtures', 'agent2-cert.pem')), 158 | ciphers: 'AES128-GCM-SHA256', 159 | }).listen(ports.source); 160 | 161 | var proxy = httpProxy.createProxyServer({ 162 | target: 'https://127.0.0.1:' + ports.source, 163 | secure: true 164 | }); 165 | 166 | proxy.listen(ports.proxy); 167 | 168 | proxy.on('error', function (err, req, res) { 169 | expect(err).to.be.an(Error); 170 | if (semver.gt(process.versions.node, '0.12.0')) { 171 | expect(err.toString()).to.be('Error: unable to verify the first certificate') 172 | } else { 173 | expect(err.toString()).to.be('Error: DEPTH_ZERO_SELF_SIGNED_CERT') 174 | } 175 | done(); 176 | }) 177 | 178 | http.request({ 179 | hostname: '127.0.0.1', 180 | port: ports.proxy, 181 | method: 'GET' 182 | }).end(); 183 | }) 184 | }) 185 | describe('HTTPS to HTTP using own server', function () { 186 | it('should proxy the request en send back the response', function (done) { 187 | var ports = { source: gen.port, proxy: gen.port }; 188 | var source = http.createServer(function(req, res) { 189 | expect(req.method).to.eql('GET'); 190 | expect(req.headers.host.split(':')[1]).to.eql(ports.proxy); 191 | res.writeHead(200, { 'Content-Type': 'text/plain' }); 192 | res.end('Hello from ' + ports.source); 193 | }); 194 | 195 | source.listen(ports.source); 196 | 197 | var proxy = httpProxy.createServer({ 198 | agent: new http.Agent({ maxSockets: 2 }) 199 | }); 200 | 201 | var ownServer = https.createServer({ 202 | key: fs.readFileSync(path.join(__dirname, 'fixtures', 'agent2-key.pem')), 203 | cert: fs.readFileSync(path.join(__dirname, 'fixtures', 'agent2-cert.pem')), 204 | ciphers: 'AES128-GCM-SHA256', 205 | }, function (req, res) { 206 | proxy.web(req, res, { 207 | target: 'http://127.0.0.1:' + ports.source 208 | }) 209 | }).listen(ports.proxy); 210 | 211 | https.request({ 212 | host: 'localhost', 213 | port: ports.proxy, 214 | path: '/', 215 | method: 'GET', 216 | rejectUnauthorized: false 217 | }, function(res) { 218 | expect(res.statusCode).to.eql(200); 219 | 220 | res.on('data', function (data) { 221 | expect(data.toString()).to.eql('Hello from ' + ports.source); 222 | }); 223 | 224 | res.on('end', function () { 225 | source.close(); 226 | ownServer.close(); 227 | done(); 228 | }) 229 | }).end(); 230 | }) 231 | }) 232 | }); 233 | }); 234 | -------------------------------------------------------------------------------- /test/lib-http-proxy-common-test.js: -------------------------------------------------------------------------------- 1 | var common = require('../lib/http-proxy/common'), 2 | url = require('url'), 3 | expect = require('expect.js'); 4 | 5 | describe('lib/http-proxy/common.js', function () { 6 | describe('#setupOutgoing', function () { 7 | it('should setup the correct headers', function () { 8 | var outgoing = {}; 9 | common.setupOutgoing(outgoing, 10 | { 11 | agent : '?', 12 | target: { 13 | host : 'hey', 14 | hostname : 'how', 15 | socketPath: 'are', 16 | port : 'you', 17 | }, 18 | headers: {'fizz': 'bang', 'overwritten':true}, 19 | localAddress: 'local.address', 20 | auth:'username:pass' 21 | }, 22 | { 23 | method : 'i', 24 | url : 'am', 25 | headers : {'pro':'xy','overwritten':false} 26 | }); 27 | 28 | expect(outgoing.host).to.eql('hey'); 29 | expect(outgoing.hostname).to.eql('how'); 30 | expect(outgoing.socketPath).to.eql('are'); 31 | expect(outgoing.port).to.eql('you'); 32 | expect(outgoing.agent).to.eql('?'); 33 | 34 | expect(outgoing.method).to.eql('i'); 35 | expect(outgoing.path).to.eql('am'); 36 | 37 | expect(outgoing.headers.pro).to.eql('xy'); 38 | expect(outgoing.headers.fizz).to.eql('bang'); 39 | expect(outgoing.headers.overwritten).to.eql(true); 40 | expect(outgoing.localAddress).to.eql('local.address'); 41 | expect(outgoing.auth).to.eql('username:pass'); 42 | }); 43 | 44 | it('should not override agentless upgrade header', function () { 45 | var outgoing = {}; 46 | common.setupOutgoing(outgoing, 47 | { 48 | agent: undefined, 49 | target: { 50 | host : 'hey', 51 | hostname : 'how', 52 | socketPath: 'are', 53 | port : 'you', 54 | }, 55 | headers: {'connection': 'upgrade'}, 56 | }, 57 | { 58 | method : 'i', 59 | url : 'am', 60 | headers : {'pro':'xy','overwritten':false} 61 | }); 62 | expect(outgoing.headers.connection).to.eql('upgrade'); 63 | }); 64 | 65 | it('should not override agentless connection: contains upgrade', function () { 66 | var outgoing = {}; 67 | common.setupOutgoing(outgoing, 68 | { 69 | agent: undefined, 70 | target: { 71 | host : 'hey', 72 | hostname : 'how', 73 | socketPath: 'are', 74 | port : 'you', 75 | }, 76 | headers: {'connection': 'keep-alive, upgrade'}, // this is what Firefox sets 77 | }, 78 | { 79 | method : 'i', 80 | url : 'am', 81 | headers : {'pro':'xy','overwritten':false} 82 | }); 83 | expect(outgoing.headers.connection).to.eql('keep-alive, upgrade'); 84 | }); 85 | 86 | it('should override agentless connection: contains improper upgrade', function () { 87 | // sanity check on upgrade regex 88 | var outgoing = {}; 89 | common.setupOutgoing(outgoing, 90 | { 91 | agent: undefined, 92 | target: { 93 | host : 'hey', 94 | hostname : 'how', 95 | socketPath: 'are', 96 | port : 'you', 97 | }, 98 | headers: {'connection': 'keep-alive, not upgrade'}, 99 | }, 100 | { 101 | method : 'i', 102 | url : 'am', 103 | headers : {'pro':'xy','overwritten':false} 104 | }); 105 | expect(outgoing.headers.connection).to.eql('close'); 106 | }); 107 | 108 | it('should override agentless non-upgrade header to close', function () { 109 | var outgoing = {}; 110 | common.setupOutgoing(outgoing, 111 | { 112 | agent: undefined, 113 | target: { 114 | host : 'hey', 115 | hostname : 'how', 116 | socketPath: 'are', 117 | port : 'you', 118 | }, 119 | headers: {'connection': 'xyz'}, 120 | }, 121 | { 122 | method : 'i', 123 | url : 'am', 124 | headers : {'pro':'xy','overwritten':false} 125 | }); 126 | expect(outgoing.headers.connection).to.eql('close'); 127 | }); 128 | 129 | it('should set the agent to false if none is given', function () { 130 | var outgoing = {}; 131 | common.setupOutgoing(outgoing, {target: 132 | 'http://localhost' 133 | }, { url: '/' }); 134 | expect(outgoing.agent).to.eql(false); 135 | }); 136 | 137 | it('set the port according to the protocol', function () { 138 | var outgoing = {}; 139 | common.setupOutgoing(outgoing, 140 | { 141 | agent : '?', 142 | target: { 143 | host : 'how', 144 | hostname : 'are', 145 | socketPath: 'you', 146 | protocol: 'https:' 147 | } 148 | }, 149 | { 150 | method : 'i', 151 | url : 'am', 152 | headers : {pro:'xy'} 153 | }); 154 | 155 | expect(outgoing.host).to.eql('how'); 156 | expect(outgoing.hostname).to.eql('are'); 157 | expect(outgoing.socketPath).to.eql('you'); 158 | expect(outgoing.agent).to.eql('?'); 159 | 160 | expect(outgoing.method).to.eql('i'); 161 | expect(outgoing.path).to.eql('am'); 162 | expect(outgoing.headers.pro).to.eql('xy'); 163 | 164 | expect(outgoing.port).to.eql(443); 165 | }); 166 | 167 | it('should keep the original target path in the outgoing path', function(){ 168 | var outgoing = {}; 169 | common.setupOutgoing(outgoing, {target: 170 | { path: 'some-path' } 171 | }, { url : 'am' }); 172 | 173 | expect(outgoing.path).to.eql('some-path/am'); 174 | }); 175 | 176 | it('should keep the original forward path in the outgoing path', function(){ 177 | var outgoing = {}; 178 | common.setupOutgoing(outgoing, { 179 | target: {}, 180 | forward: { 181 | path: 'some-path' 182 | } 183 | }, { 184 | url : 'am' 185 | }, 'forward'); 186 | 187 | expect(outgoing.path).to.eql('some-path/am'); 188 | }); 189 | 190 | it('should properly detect https/wss protocol without the colon', function () { 191 | var outgoing = {}; 192 | common.setupOutgoing(outgoing, { 193 | target: { 194 | protocol: 'https', 195 | host: 'whatever.com' 196 | } 197 | }, { url: '/' }); 198 | 199 | expect(outgoing.port).to.eql(443); 200 | }); 201 | 202 | it('should not prepend the target path to the outgoing path with prependPath = false', function () { 203 | var outgoing = {}; 204 | common.setupOutgoing(outgoing, { 205 | target: { path: 'hellothere' }, 206 | prependPath: false 207 | }, { url: 'hi' }); 208 | 209 | expect(outgoing.path).to.eql('hi'); 210 | }) 211 | 212 | it('should properly join paths', function () { 213 | var outgoing = {}; 214 | common.setupOutgoing(outgoing, { 215 | target: { path: '/forward' }, 216 | }, { url: '/static/path' }); 217 | 218 | expect(outgoing.path).to.eql('/forward/static/path'); 219 | }) 220 | 221 | it('should not modify the query string', function () { 222 | var outgoing = {}; 223 | common.setupOutgoing(outgoing, { 224 | target: { path: '/forward' }, 225 | }, { url: '/?foo=bar//&target=http://foobar.com/?a=1%26b=2&other=2' }); 226 | 227 | expect(outgoing.path).to.eql('/forward/?foo=bar//&target=http://foobar.com/?a=1%26b=2&other=2'); 228 | }) 229 | 230 | // 231 | // This is the proper failing test case for the common.join problem 232 | // 233 | it('should correctly format the toProxy URL', function () { 234 | var outgoing = {}; 235 | var google = 'https://google.com' 236 | common.setupOutgoing(outgoing, { 237 | target: url.parse('http://sometarget.com:80'), 238 | toProxy: true, 239 | }, { url: google }); 240 | 241 | expect(outgoing.path).to.eql('/' + google); 242 | }); 243 | 244 | it('should not replace :\ to :\\ when no https word before', function () { 245 | var outgoing = {}; 246 | var google = 'https://google.com:/join/join.js' 247 | common.setupOutgoing(outgoing, { 248 | target: url.parse('http://sometarget.com:80'), 249 | toProxy: true, 250 | }, { url: google }); 251 | 252 | expect(outgoing.path).to.eql('/' + google); 253 | }); 254 | 255 | it('should not replace :\ to :\\ when no http word before', function () { 256 | var outgoing = {}; 257 | var google = 'http://google.com:/join/join.js' 258 | common.setupOutgoing(outgoing, { 259 | target: url.parse('http://sometarget.com:80'), 260 | toProxy: true, 261 | }, { url: google }); 262 | 263 | expect(outgoing.path).to.eql('/' + google); 264 | }); 265 | 266 | describe('when using ignorePath', function () { 267 | it('should ignore the path of the `req.url` passed in but use the target path', function () { 268 | var outgoing = {}; 269 | var myEndpoint = 'https://whatever.com/some/crazy/path/whoooo'; 270 | common.setupOutgoing(outgoing, { 271 | target: url.parse(myEndpoint), 272 | ignorePath: true 273 | }, { url: '/more/crazy/pathness' }); 274 | 275 | expect(outgoing.path).to.eql('/some/crazy/path/whoooo'); 276 | }); 277 | 278 | it('and prependPath: false, it should ignore path of target and incoming request', function () { 279 | var outgoing = {}; 280 | var myEndpoint = 'https://whatever.com/some/crazy/path/whoooo'; 281 | common.setupOutgoing(outgoing, { 282 | target: url.parse(myEndpoint), 283 | ignorePath: true, 284 | prependPath: false 285 | }, { url: '/more/crazy/pathness' }); 286 | 287 | expect(outgoing.path).to.eql(''); 288 | }); 289 | }); 290 | 291 | describe('when using changeOrigin', function () { 292 | it('should correctly set the port to the host when it is a non-standard port using url.parse', function () { 293 | var outgoing = {}; 294 | var myEndpoint = 'https://myCouch.com:6984'; 295 | common.setupOutgoing(outgoing, { 296 | target: url.parse(myEndpoint), 297 | changeOrigin: true 298 | }, { url: '/' }); 299 | 300 | expect(outgoing.headers.host).to.eql('mycouch.com:6984'); 301 | }); 302 | 303 | it('should correctly set the port to the host when it is a non-standard port when setting host and port manually (which ignores port)', function () { 304 | var outgoing = {}; 305 | common.setupOutgoing(outgoing, { 306 | target: { 307 | protocol: 'https:', 308 | host: 'mycouch.com', 309 | port: 6984 310 | }, 311 | changeOrigin: true 312 | }, { url: '/' }); 313 | expect(outgoing.headers.host).to.eql('mycouch.com:6984'); 314 | }) 315 | }); 316 | 317 | it('should pass through https client parameters', function () { 318 | var outgoing = {}; 319 | common.setupOutgoing(outgoing, 320 | { 321 | agent : '?', 322 | target: { 323 | host : 'how', 324 | hostname : 'are', 325 | socketPath: 'you', 326 | protocol: 'https:', 327 | pfx: 'my-pfx', 328 | key: 'my-key', 329 | passphrase: 'my-passphrase', 330 | cert: 'my-cert', 331 | ca: 'my-ca', 332 | ciphers: 'my-ciphers', 333 | secureProtocol: 'my-secure-protocol' 334 | } 335 | }, 336 | { 337 | method : 'i', 338 | url : 'am' 339 | }); 340 | 341 | expect(outgoing.pfx).eql('my-pfx'); 342 | expect(outgoing.key).eql('my-key'); 343 | expect(outgoing.passphrase).eql('my-passphrase'); 344 | expect(outgoing.cert).eql('my-cert'); 345 | expect(outgoing.ca).eql('my-ca'); 346 | expect(outgoing.ciphers).eql('my-ciphers'); 347 | expect(outgoing.secureProtocol).eql('my-secure-protocol'); 348 | }); 349 | 350 | it('should handle overriding the `method` of the http request', function () { 351 | var outgoing = {}; 352 | common.setupOutgoing(outgoing, { 353 | target: url.parse('https://whooooo.com'), 354 | method: 'POST' , 355 | }, { method: 'GET', url: '' }); 356 | 357 | expect(outgoing.method).eql('POST'); 358 | }); 359 | 360 | // url.parse('').path => null 361 | it('should not pass null as last arg to #urlJoin', function(){ 362 | var outgoing = {}; 363 | common.setupOutgoing(outgoing, {target: 364 | { path: '' } 365 | }, { url : '' }); 366 | 367 | expect(outgoing.path).to.be(''); 368 | }); 369 | 370 | }); 371 | 372 | describe('#setupSocket', function () { 373 | it('should setup a socket', function () { 374 | var socketConfig = { 375 | timeout: null, 376 | nodelay: false, 377 | keepalive: false 378 | }, 379 | stubSocket = { 380 | setTimeout: function (num) { 381 | socketConfig.timeout = num; 382 | }, 383 | setNoDelay: function (bol) { 384 | socketConfig.nodelay = bol; 385 | }, 386 | setKeepAlive: function (bol) { 387 | socketConfig.keepalive = bol; 388 | } 389 | } 390 | returnValue = common.setupSocket(stubSocket); 391 | 392 | expect(socketConfig.timeout).to.eql(0); 393 | expect(socketConfig.nodelay).to.eql(true); 394 | expect(socketConfig.keepalive).to.eql(true); 395 | }); 396 | }); 397 | }); 398 | -------------------------------------------------------------------------------- /test/lib-http-proxy-passes-web-outgoing-test.js: -------------------------------------------------------------------------------- 1 | var httpProxy = require('../lib/http-proxy/passes/web-outgoing'), 2 | expect = require('expect.js'); 3 | 4 | describe('lib/http-proxy/passes/web-outgoing.js', function () { 5 | describe('#setRedirectHostRewrite', function () { 6 | beforeEach(function() { 7 | this.req = { 8 | headers: { 9 | host: 'ext-auto.com' 10 | } 11 | }; 12 | this.proxyRes = { 13 | statusCode: 301, 14 | headers: { 15 | location: 'http://backend.com/' 16 | } 17 | }; 18 | this.options = { 19 | target: 'http://backend.com' 20 | }; 21 | }); 22 | 23 | context('rewrites location host with hostRewrite', function() { 24 | beforeEach(function() { 25 | this.options.hostRewrite = 'ext-manual.com'; 26 | }); 27 | [201, 301, 302, 307, 308].forEach(function(code) { 28 | it('on ' + code, function() { 29 | this.proxyRes.statusCode = code; 30 | httpProxy.setRedirectHostRewrite(this.req, {}, this.proxyRes, this.options); 31 | expect(this.proxyRes.headers.location).to.eql('http://ext-manual.com/'); 32 | }); 33 | }); 34 | 35 | it('not on 200', function() { 36 | this.proxyRes.statusCode = 200; 37 | httpProxy.setRedirectHostRewrite(this.req, {}, this.proxyRes, this.options); 38 | expect(this.proxyRes.headers.location).to.eql('http://backend.com/'); 39 | }); 40 | 41 | it('not when hostRewrite is unset', function() { 42 | delete this.options.hostRewrite; 43 | httpProxy.setRedirectHostRewrite(this.req, {}, this.proxyRes, this.options); 44 | expect(this.proxyRes.headers.location).to.eql('http://backend.com/'); 45 | }); 46 | 47 | it('takes precedence over autoRewrite', function() { 48 | this.options.autoRewrite = true; 49 | httpProxy.setRedirectHostRewrite(this.req, {}, this.proxyRes, this.options); 50 | expect(this.proxyRes.headers.location).to.eql('http://ext-manual.com/'); 51 | }); 52 | 53 | it('not when the redirected location does not match target host', function() { 54 | this.proxyRes.statusCode = 302; 55 | this.proxyRes.headers.location = 'http://some-other/'; 56 | httpProxy.setRedirectHostRewrite(this.req, {}, this.proxyRes, this.options); 57 | expect(this.proxyRes.headers.location).to.eql('http://some-other/'); 58 | }); 59 | 60 | it('not when the redirected location does not match target port', function() { 61 | this.proxyRes.statusCode = 302; 62 | this.proxyRes.headers.location = 'http://backend.com:8080/'; 63 | httpProxy.setRedirectHostRewrite(this.req, {}, this.proxyRes, this.options); 64 | expect(this.proxyRes.headers.location).to.eql('http://backend.com:8080/'); 65 | }); 66 | }); 67 | 68 | context('rewrites location host with autoRewrite', function() { 69 | beforeEach(function() { 70 | this.options.autoRewrite = true; 71 | }); 72 | [201, 301, 302, 307, 308].forEach(function(code) { 73 | it('on ' + code, function() { 74 | this.proxyRes.statusCode = code; 75 | httpProxy.setRedirectHostRewrite(this.req, {}, this.proxyRes, this.options); 76 | expect(this.proxyRes.headers.location).to.eql('http://ext-auto.com/'); 77 | }); 78 | }); 79 | 80 | it('not on 200', function() { 81 | this.proxyRes.statusCode = 200; 82 | httpProxy.setRedirectHostRewrite(this.req, {}, this.proxyRes, this.options); 83 | expect(this.proxyRes.headers.location).to.eql('http://backend.com/'); 84 | }); 85 | 86 | it('not when autoRewrite is unset', function() { 87 | delete this.options.autoRewrite; 88 | httpProxy.setRedirectHostRewrite(this.req, {}, this.proxyRes, this.options); 89 | expect(this.proxyRes.headers.location).to.eql('http://backend.com/'); 90 | }); 91 | 92 | it('not when the redirected location does not match target host', function() { 93 | this.proxyRes.statusCode = 302; 94 | this.proxyRes.headers.location = 'http://some-other/'; 95 | httpProxy.setRedirectHostRewrite(this.req, {}, this.proxyRes, this.options); 96 | expect(this.proxyRes.headers.location).to.eql('http://some-other/'); 97 | }); 98 | 99 | it('not when the redirected location does not match target port', function() { 100 | this.proxyRes.statusCode = 302; 101 | this.proxyRes.headers.location = 'http://backend.com:8080/'; 102 | httpProxy.setRedirectHostRewrite(this.req, {}, this.proxyRes, this.options); 103 | expect(this.proxyRes.headers.location).to.eql('http://backend.com:8080/'); 104 | }); 105 | }); 106 | 107 | context('rewrites location protocol with protocolRewrite', function() { 108 | beforeEach(function() { 109 | this.options.protocolRewrite = 'https'; 110 | }); 111 | [201, 301, 302, 307, 308].forEach(function(code) { 112 | it('on ' + code, function() { 113 | this.proxyRes.statusCode = code; 114 | httpProxy.setRedirectHostRewrite(this.req, {}, this.proxyRes, this.options); 115 | expect(this.proxyRes.headers.location).to.eql('https://backend.com/'); 116 | }); 117 | }); 118 | 119 | it('not on 200', function() { 120 | this.proxyRes.statusCode = 200; 121 | httpProxy.setRedirectHostRewrite(this.req, {}, this.proxyRes, this.options); 122 | expect(this.proxyRes.headers.location).to.eql('http://backend.com/'); 123 | }); 124 | 125 | it('not when protocolRewrite is unset', function() { 126 | delete this.options.protocolRewrite; 127 | httpProxy.setRedirectHostRewrite(this.req, {}, this.proxyRes, this.options); 128 | expect(this.proxyRes.headers.location).to.eql('http://backend.com/'); 129 | }); 130 | 131 | it('works together with hostRewrite', function() { 132 | this.options.hostRewrite = 'ext-manual.com'; 133 | httpProxy.setRedirectHostRewrite(this.req, {}, this.proxyRes, this.options); 134 | expect(this.proxyRes.headers.location).to.eql('https://ext-manual.com/'); 135 | }); 136 | 137 | it('works together with autoRewrite', function() { 138 | this.options.autoRewrite = true; 139 | httpProxy.setRedirectHostRewrite(this.req, {}, this.proxyRes, this.options); 140 | expect(this.proxyRes.headers.location).to.eql('https://ext-auto.com/'); 141 | }); 142 | }); 143 | }); 144 | 145 | describe('#setConnection', function () { 146 | it('set the right connection with 1.0 - `close`', function() { 147 | var proxyRes = { headers: {} }; 148 | httpProxy.setConnection({ 149 | httpVersion: '1.0', 150 | headers: { 151 | connection: null 152 | } 153 | }, {}, proxyRes); 154 | 155 | expect(proxyRes.headers.connection).to.eql('close'); 156 | }); 157 | 158 | it('set the right connection with 1.0 - req.connection', function() { 159 | var proxyRes = { headers: {} }; 160 | httpProxy.setConnection({ 161 | httpVersion: '1.0', 162 | headers: { 163 | connection: 'hey' 164 | } 165 | }, {}, proxyRes); 166 | 167 | expect(proxyRes.headers.connection).to.eql('hey'); 168 | }); 169 | 170 | it('set the right connection - req.connection', function() { 171 | var proxyRes = { headers: {} }; 172 | httpProxy.setConnection({ 173 | httpVersion: null, 174 | headers: { 175 | connection: 'hola' 176 | } 177 | }, {}, proxyRes); 178 | 179 | expect(proxyRes.headers.connection).to.eql('hola'); 180 | }); 181 | 182 | it('set the right connection - `keep-alive`', function() { 183 | var proxyRes = { headers: {} }; 184 | httpProxy.setConnection({ 185 | httpVersion: null, 186 | headers: { 187 | connection: null 188 | } 189 | }, {}, proxyRes); 190 | 191 | expect(proxyRes.headers.connection).to.eql('keep-alive'); 192 | }); 193 | 194 | it('don`t set connection with 2.0 if exist', function() { 195 | var proxyRes = { headers: {} }; 196 | httpProxy.setConnection({ 197 | httpVersion: '2.0', 198 | headers: { 199 | connection: 'namstey' 200 | } 201 | }, {}, proxyRes); 202 | 203 | expect(proxyRes.headers.connection).to.eql(undefined); 204 | }); 205 | 206 | it('don`t set connection with 2.0 if doesn`t exist', function() { 207 | var proxyRes = { headers: {} }; 208 | httpProxy.setConnection({ 209 | httpVersion: '2.0', 210 | headers: {} 211 | }, {}, proxyRes); 212 | 213 | expect(proxyRes.headers.connection).to.eql(undefined); 214 | }) 215 | 216 | }); 217 | 218 | describe('#writeStatusCode', function () { 219 | it('should write status code', function() { 220 | var res = { 221 | writeHead: function(n) { 222 | expect(n).to.eql(200); 223 | } 224 | }; 225 | 226 | httpProxy.writeStatusCode({}, res, { statusCode: 200 }); 227 | }); 228 | }); 229 | 230 | describe('#writeHeaders', function() { 231 | beforeEach(function() { 232 | this.proxyRes = { 233 | headers: { 234 | hey: 'hello', 235 | how: 'are you?', 236 | 'set-cookie': [ 237 | 'hello; domain=my.domain; path=/', 238 | 'there; domain=my.domain; path=/' 239 | ] 240 | } 241 | }; 242 | this.rawProxyRes = { 243 | headers: { 244 | hey: 'hello', 245 | how: 'are you?', 246 | 'set-cookie': [ 247 | 'hello; domain=my.domain; path=/', 248 | 'there; domain=my.domain; path=/' 249 | ] 250 | }, 251 | rawHeaders: [ 252 | 'Hey', 'hello', 253 | 'How', 'are you?', 254 | 'Set-Cookie', 'hello; domain=my.domain; path=/', 255 | 'Set-Cookie', 'there; domain=my.domain; path=/' 256 | ] 257 | }; 258 | this.res = { 259 | setHeader: function(k, v) { 260 | // https://nodejs.org/api/http.html#http_message_headers 261 | // Header names are lower-cased 262 | this.headers[k.toLowerCase()] = v; 263 | }, 264 | headers: {} 265 | }; 266 | }); 267 | 268 | it('writes headers', function() { 269 | var options = {}; 270 | httpProxy.writeHeaders({}, this.res, this.proxyRes, options); 271 | 272 | expect(this.res.headers.hey).to.eql('hello'); 273 | expect(this.res.headers.how).to.eql('are you?'); 274 | 275 | expect(this.res.headers).to.have.key('set-cookie'); 276 | expect(this.res.headers['set-cookie']).to.be.an(Array); 277 | expect(this.res.headers['set-cookie']).to.have.length(2); 278 | }); 279 | 280 | it('writes raw headers', function() { 281 | var options = {}; 282 | httpProxy.writeHeaders({}, this.res, this.rawProxyRes, options); 283 | 284 | expect(this.res.headers.hey).to.eql('hello'); 285 | expect(this.res.headers.how).to.eql('are you?'); 286 | 287 | expect(this.res.headers).to.have.key('set-cookie'); 288 | expect(this.res.headers['set-cookie']).to.be.an(Array); 289 | expect(this.res.headers['set-cookie']).to.have.length(2); 290 | }); 291 | 292 | it('rewrites path', function() { 293 | var options = { 294 | cookiePathRewrite: '/dummyPath' 295 | }; 296 | 297 | httpProxy.writeHeaders({}, this.res, this.proxyRes, options); 298 | 299 | expect(this.res.headers['set-cookie']) 300 | .to.contain('hello; domain=my.domain; path=/dummyPath'); 301 | }); 302 | 303 | it('does not rewrite path', function() { 304 | var options = {}; 305 | 306 | httpProxy.writeHeaders({}, this.res, this.proxyRes, options); 307 | 308 | expect(this.res.headers['set-cookie']) 309 | .to.contain('hello; domain=my.domain; path=/'); 310 | }); 311 | 312 | it('removes path', function() { 313 | var options = { 314 | cookiePathRewrite: '' 315 | }; 316 | 317 | httpProxy.writeHeaders({}, this.res, this.proxyRes, options); 318 | 319 | expect(this.res.headers['set-cookie']) 320 | .to.contain('hello; domain=my.domain'); 321 | }); 322 | 323 | it('does not rewrite domain', function() { 324 | var options = {}; 325 | 326 | httpProxy.writeHeaders({}, this.res, this.proxyRes, options); 327 | 328 | expect(this.res.headers['set-cookie']) 329 | .to.contain('hello; domain=my.domain; path=/'); 330 | }); 331 | 332 | it('rewrites domain', function() { 333 | var options = { 334 | cookieDomainRewrite: 'my.new.domain' 335 | }; 336 | 337 | httpProxy.writeHeaders({}, this.res, this.proxyRes, options); 338 | 339 | expect(this.res.headers['set-cookie']) 340 | .to.contain('hello; domain=my.new.domain; path=/'); 341 | }); 342 | 343 | it('removes domain', function() { 344 | var options = { 345 | cookieDomainRewrite: '' 346 | }; 347 | 348 | httpProxy.writeHeaders({}, this.res, this.proxyRes, options); 349 | 350 | expect(this.res.headers['set-cookie']) 351 | .to.contain('hello; path=/'); 352 | }); 353 | 354 | it('rewrites headers with advanced configuration', function() { 355 | var options = { 356 | cookieDomainRewrite: { 357 | '*': '', 358 | 'my.old.domain': 'my.new.domain', 359 | 'my.special.domain': 'my.special.domain' 360 | } 361 | }; 362 | this.proxyRes.headers['set-cookie'] = [ 363 | 'hello-on-my.domain; domain=my.domain; path=/', 364 | 'hello-on-my.old.domain; domain=my.old.domain; path=/', 365 | 'hello-on-my.special.domain; domain=my.special.domain; path=/' 366 | ]; 367 | httpProxy.writeHeaders({}, this.res, this.proxyRes, options); 368 | 369 | expect(this.res.headers['set-cookie']) 370 | .to.contain('hello-on-my.domain; path=/'); 371 | expect(this.res.headers['set-cookie']) 372 | .to.contain('hello-on-my.old.domain; domain=my.new.domain; path=/'); 373 | expect(this.res.headers['set-cookie']) 374 | .to.contain('hello-on-my.special.domain; domain=my.special.domain; path=/'); 375 | }); 376 | 377 | it('rewrites raw headers with advanced configuration', function() { 378 | var options = { 379 | cookieDomainRewrite: { 380 | '*': '', 381 | 'my.old.domain': 'my.new.domain', 382 | 'my.special.domain': 'my.special.domain' 383 | } 384 | }; 385 | this.rawProxyRes.headers['set-cookie'] = [ 386 | 'hello-on-my.domain; domain=my.domain; path=/', 387 | 'hello-on-my.old.domain; domain=my.old.domain; path=/', 388 | 'hello-on-my.special.domain; domain=my.special.domain; path=/' 389 | ]; 390 | this.rawProxyRes.rawHeaders = this.rawProxyRes.rawHeaders.concat([ 391 | 'Set-Cookie', 392 | 'hello-on-my.domain; domain=my.domain; path=/', 393 | 'Set-Cookie', 394 | 'hello-on-my.old.domain; domain=my.old.domain; path=/', 395 | 'Set-Cookie', 396 | 'hello-on-my.special.domain; domain=my.special.domain; path=/' 397 | ]); 398 | httpProxy.writeHeaders({}, this.res, this.rawProxyRes, options); 399 | 400 | expect(this.res.headers['set-cookie']) 401 | .to.contain('hello-on-my.domain; path=/'); 402 | expect(this.res.headers['set-cookie']) 403 | .to.contain('hello-on-my.old.domain; domain=my.new.domain; path=/'); 404 | expect(this.res.headers['set-cookie']) 405 | .to.contain('hello-on-my.special.domain; domain=my.special.domain; path=/'); 406 | }); 407 | }); 408 | 409 | 410 | describe('#removeChunked', function() { 411 | var proxyRes = { 412 | headers: { 413 | 'transfer-encoding': 'hello' 414 | } 415 | }; 416 | 417 | 418 | httpProxy.removeChunked({ httpVersion: '1.0' }, {}, proxyRes); 419 | 420 | expect(proxyRes.headers['transfer-encoding']).to.eql(undefined); 421 | }); 422 | 423 | }); 424 | -------------------------------------------------------------------------------- /test/lib-http-proxy-passes-web-incoming-test.js: -------------------------------------------------------------------------------- 1 | var webPasses = require('../lib/http-proxy/passes/web-incoming'), 2 | httpProxy = require('../lib/http-proxy'), 3 | expect = require('expect.js'), 4 | concat = require('concat-stream'), 5 | async = require('async'), 6 | url = require('url'), 7 | http = require('http'); 8 | 9 | describe('lib/http-proxy/passes/web.js', function() { 10 | describe('#deleteLength', function() { 11 | it('should change `content-length` for DELETE requests', function() { 12 | var stubRequest = { 13 | method: 'DELETE', 14 | headers: {} 15 | }; 16 | webPasses.deleteLength(stubRequest, {}, {}); 17 | expect(stubRequest.headers['content-length']).to.eql('0'); 18 | }); 19 | 20 | it('should change `content-length` for OPTIONS requests', function() { 21 | var stubRequest = { 22 | method: 'OPTIONS', 23 | headers: {} 24 | }; 25 | webPasses.deleteLength(stubRequest, {}, {}); 26 | expect(stubRequest.headers['content-length']).to.eql('0'); 27 | }); 28 | 29 | it('should remove `transfer-encoding` from empty DELETE requests', function() { 30 | var stubRequest = { 31 | method: 'DELETE', 32 | headers: { 33 | 'transfer-encoding': 'chunked' 34 | } 35 | }; 36 | webPasses.deleteLength(stubRequest, {}, {}); 37 | expect(stubRequest.headers['content-length']).to.eql('0'); 38 | expect(stubRequest.headers).to.not.have.key('transfer-encoding'); 39 | }); 40 | }); 41 | 42 | describe('#timeout', function() { 43 | it('should set timeout on the socket', function() { 44 | var done = false, stubRequest = { 45 | socket: { 46 | setTimeout: function(value) { done = value; } 47 | } 48 | } 49 | 50 | webPasses.timeout(stubRequest, {}, { timeout: 5000}); 51 | expect(done).to.eql(5000); 52 | }); 53 | }); 54 | 55 | describe('#XHeaders', function () { 56 | var stubRequest = { 57 | connection: { 58 | remoteAddress: '192.168.1.2', 59 | remotePort: '8080' 60 | }, 61 | headers: { 62 | host: '192.168.1.2:8080' 63 | } 64 | } 65 | 66 | it('set the correct x-forwarded-* headers', function () { 67 | webPasses.XHeaders(stubRequest, {}, { xfwd: true }); 68 | expect(stubRequest.headers['x-forwarded-for']).to.be('192.168.1.2'); 69 | expect(stubRequest.headers['x-forwarded-port']).to.be('8080'); 70 | expect(stubRequest.headers['x-forwarded-proto']).to.be('http'); 71 | }); 72 | }); 73 | }); 74 | 75 | describe('#createProxyServer.web() using own http server', function () { 76 | it('should proxy the request using the web proxy handler', function (done) { 77 | var proxy = httpProxy.createProxyServer({ 78 | target: 'http://127.0.0.1:8080' 79 | }); 80 | 81 | function requestHandler(req, res) { 82 | proxy.web(req, res); 83 | } 84 | 85 | var proxyServer = http.createServer(requestHandler); 86 | 87 | var source = http.createServer(function(req, res) { 88 | source.close(); 89 | proxyServer.close(); 90 | expect(req.method).to.eql('GET'); 91 | expect(req.headers.host.split(':')[1]).to.eql('8081'); 92 | done(); 93 | }); 94 | 95 | proxyServer.listen('8081'); 96 | source.listen('8080'); 97 | 98 | http.request('http://127.0.0.1:8081', function() {}).end(); 99 | }); 100 | 101 | it('should detect a proxyReq event and modify headers', function (done) { 102 | var proxy = httpProxy.createProxyServer({ 103 | target: 'http://127.0.0.1:8080', 104 | }); 105 | 106 | proxy.on('proxyReq', function(proxyReq, req, res, options) { 107 | proxyReq.setHeader('X-Special-Proxy-Header', 'foobar'); 108 | }); 109 | 110 | function requestHandler(req, res) { 111 | proxy.web(req, res); 112 | } 113 | 114 | var proxyServer = http.createServer(requestHandler); 115 | 116 | var source = http.createServer(function(req, res) { 117 | source.close(); 118 | proxyServer.close(); 119 | expect(req.headers['x-special-proxy-header']).to.eql('foobar'); 120 | done(); 121 | }); 122 | 123 | proxyServer.listen('8081'); 124 | source.listen('8080'); 125 | 126 | http.request('http://127.0.0.1:8081', function() {}).end(); 127 | }); 128 | 129 | it('should skip proxyReq event when handling a request with header "expect: 100-continue" [https://www.npmjs.com/advisories/1486]', function (done) { 130 | var proxy = httpProxy.createProxyServer({ 131 | target: 'http://127.0.0.1:8080', 132 | }); 133 | 134 | proxy.on('proxyReq', function(proxyReq, req, res, options) { 135 | proxyReq.setHeader('X-Special-Proxy-Header', 'foobar'); 136 | }); 137 | 138 | function requestHandler(req, res) { 139 | proxy.web(req, res); 140 | } 141 | 142 | var proxyServer = http.createServer(requestHandler); 143 | 144 | var source = http.createServer(function(req, res) { 145 | source.close(); 146 | proxyServer.close(); 147 | expect(req.headers['x-special-proxy-header']).to.not.eql('foobar'); 148 | done(); 149 | }); 150 | 151 | proxyServer.listen('8081'); 152 | source.listen('8080'); 153 | 154 | const postData = ''.padStart(1025, 'x'); 155 | 156 | const postOptions = { 157 | hostname: '127.0.0.1', 158 | port: 8081, 159 | path: '/', 160 | method: 'POST', 161 | headers: { 162 | 'Content-Type': 'application/x-www-form-urlencoded', 163 | 'Content-Length': Buffer.byteLength(postData), 164 | 'expect': '100-continue' 165 | } 166 | }; 167 | 168 | const req = http.request(postOptions, function() {}); 169 | req.write(postData); 170 | req.end(); 171 | }); 172 | 173 | it('should proxy the request and handle error via callback', function(done) { 174 | var proxy = httpProxy.createProxyServer({ 175 | target: 'http://127.0.0.1:8080' 176 | }); 177 | 178 | var proxyServer = http.createServer(requestHandler); 179 | 180 | function requestHandler(req, res) { 181 | proxy.web(req, res, function (err) { 182 | proxyServer.close(); 183 | expect(err).to.be.an(Error); 184 | expect(err.code).to.be('ECONNREFUSED'); 185 | done(); 186 | }); 187 | } 188 | 189 | proxyServer.listen('8082'); 190 | 191 | http.request({ 192 | hostname: '127.0.0.1', 193 | port: '8082', 194 | method: 'GET', 195 | }, function() {}).end(); 196 | }); 197 | 198 | it('should proxy the request and handle error via event listener', function(done) { 199 | var proxy = httpProxy.createProxyServer({ 200 | target: 'http://127.0.0.1:8080' 201 | }); 202 | 203 | var proxyServer = http.createServer(requestHandler); 204 | 205 | function requestHandler(req, res) { 206 | proxy.once('error', function (err, errReq, errRes) { 207 | proxyServer.close(); 208 | expect(err).to.be.an(Error); 209 | expect(errReq).to.be.equal(req); 210 | expect(errRes).to.be.equal(res); 211 | expect(err.code).to.be('ECONNREFUSED'); 212 | done(); 213 | }); 214 | 215 | proxy.web(req, res); 216 | } 217 | 218 | proxyServer.listen('8083'); 219 | 220 | http.request({ 221 | hostname: '127.0.0.1', 222 | port: '8083', 223 | method: 'GET', 224 | }, function() {}).end(); 225 | }); 226 | 227 | it('should forward the request and handle error via event listener', function(done) { 228 | var proxy = httpProxy.createProxyServer({ 229 | forward: 'http://127.0.0.1:8080' 230 | }); 231 | 232 | var proxyServer = http.createServer(requestHandler); 233 | 234 | function requestHandler(req, res) { 235 | proxy.once('error', function (err, errReq, errRes) { 236 | proxyServer.close(); 237 | expect(err).to.be.an(Error); 238 | expect(errReq).to.be.equal(req); 239 | expect(errRes).to.be.equal(res); 240 | expect(err.code).to.be('ECONNREFUSED'); 241 | done(); 242 | }); 243 | 244 | proxy.web(req, res); 245 | } 246 | 247 | proxyServer.listen('8083'); 248 | 249 | http.request({ 250 | hostname: '127.0.0.1', 251 | port: '8083', 252 | method: 'GET', 253 | }, function() {}).end(); 254 | }); 255 | 256 | it('should proxy the request and handle timeout error (proxyTimeout)', function(done) { 257 | var proxy = httpProxy.createProxyServer({ 258 | target: 'http://127.0.0.1:45000', 259 | proxyTimeout: 100 260 | }); 261 | 262 | require('net').createServer().listen(45000); 263 | 264 | var proxyServer = http.createServer(requestHandler); 265 | 266 | var started = new Date().getTime(); 267 | function requestHandler(req, res) { 268 | proxy.once('error', function (err, errReq, errRes) { 269 | proxyServer.close(); 270 | expect(err).to.be.an(Error); 271 | expect(errReq).to.be.equal(req); 272 | expect(errRes).to.be.equal(res); 273 | expect(new Date().getTime() - started).to.be.greaterThan(99); 274 | expect(err.code).to.be('ECONNRESET'); 275 | done(); 276 | }); 277 | 278 | proxy.web(req, res); 279 | } 280 | 281 | proxyServer.listen('8084'); 282 | 283 | http.request({ 284 | hostname: '127.0.0.1', 285 | port: '8084', 286 | method: 'GET', 287 | }, function() {}).end(); 288 | }); 289 | 290 | it('should proxy the request and handle timeout error', function(done) { 291 | var proxy = httpProxy.createProxyServer({ 292 | target: 'http://127.0.0.1:45001', 293 | timeout: 100 294 | }); 295 | 296 | require('net').createServer().listen(45001); 297 | 298 | var proxyServer = http.createServer(requestHandler); 299 | 300 | var cnt = 0; 301 | var doneOne = function() { 302 | cnt += 1; 303 | if(cnt === 2) done(); 304 | } 305 | 306 | var started = new Date().getTime(); 307 | function requestHandler(req, res) { 308 | proxy.once('econnreset', function (err, errReq, errRes) { 309 | proxyServer.close(); 310 | expect(err).to.be.an(Error); 311 | expect(errReq).to.be.equal(req); 312 | expect(errRes).to.be.equal(res); 313 | expect(err.code).to.be('ECONNRESET'); 314 | doneOne(); 315 | }); 316 | 317 | proxy.web(req, res); 318 | } 319 | 320 | proxyServer.listen('8085'); 321 | 322 | var req = http.request({ 323 | hostname: '127.0.0.1', 324 | port: '8085', 325 | method: 'GET', 326 | }, function() {}); 327 | 328 | req.on('error', function(err) { 329 | expect(err).to.be.an(Error); 330 | expect(err.code).to.be('ECONNRESET'); 331 | expect(new Date().getTime() - started).to.be.greaterThan(99); 332 | doneOne(); 333 | }); 334 | req.end(); 335 | }); 336 | 337 | it('should proxy the request and provide a proxyRes event with the request and response parameters', function(done) { 338 | var proxy = httpProxy.createProxyServer({ 339 | target: 'http://127.0.0.1:8080' 340 | }); 341 | 342 | function requestHandler(req, res) { 343 | proxy.once('proxyRes', function (proxyRes, pReq, pRes) { 344 | source.close(); 345 | proxyServer.close(); 346 | expect(pReq).to.be.equal(req); 347 | expect(pRes).to.be.equal(res); 348 | done(); 349 | }); 350 | 351 | proxy.web(req, res); 352 | } 353 | 354 | var proxyServer = http.createServer(requestHandler); 355 | 356 | var source = http.createServer(function(req, res) { 357 | res.end('Response'); 358 | }); 359 | 360 | proxyServer.listen('8086'); 361 | source.listen('8080'); 362 | http.request('http://127.0.0.1:8086', function() {}).end(); 363 | }); 364 | 365 | it('should proxy the request and provide and respond to manual user response when using modifyResponse', function(done) { 366 | var proxy = httpProxy.createProxyServer({ 367 | target: 'http://127.0.0.1:8080', 368 | selfHandleResponse: true 369 | }); 370 | 371 | function requestHandler(req, res) { 372 | proxy.once('proxyRes', function (proxyRes, pReq, pRes) { 373 | proxyRes.pipe(concat(function (body) { 374 | expect(body.toString('utf8')).eql('Response'); 375 | pRes.end(Buffer.from('my-custom-response')); 376 | })) 377 | }); 378 | 379 | proxy.web(req, res); 380 | } 381 | 382 | var proxyServer = http.createServer(requestHandler); 383 | 384 | var source = http.createServer(function(req, res) { 385 | res.end('Response'); 386 | }); 387 | 388 | async.parallel([ 389 | next => proxyServer.listen(8086, next), 390 | next => source.listen(8080, next) 391 | ], function (err) { 392 | http.get('http://127.0.0.1:8086', function(res) { 393 | res.pipe(concat(function(body) { 394 | expect(body.toString('utf8')).eql('my-custom-response'); 395 | source.close(); 396 | proxyServer.close(); 397 | done(); 398 | })); 399 | }).once('error', done); 400 | }) 401 | }); 402 | 403 | it('should proxy the request and handle changeOrigin option', function (done) { 404 | var proxy = httpProxy.createProxyServer({ 405 | target: 'http://127.0.0.1:8080', 406 | changeOrigin: true 407 | }); 408 | 409 | function requestHandler(req, res) { 410 | proxy.web(req, res); 411 | } 412 | 413 | var proxyServer = http.createServer(requestHandler); 414 | 415 | var source = http.createServer(function(req, res) { 416 | source.close(); 417 | proxyServer.close(); 418 | expect(req.method).to.eql('GET'); 419 | expect(req.headers.host.split(':')[1]).to.eql('8080'); 420 | done(); 421 | }); 422 | 423 | proxyServer.listen('8081'); 424 | source.listen('8080'); 425 | 426 | http.request('http://127.0.0.1:8081', function() {}).end(); 427 | }); 428 | 429 | it('should proxy the request with the Authorization header set', function (done) { 430 | var proxy = httpProxy.createProxyServer({ 431 | target: 'http://127.0.0.1:8080', 432 | auth: 'user:pass' 433 | }); 434 | 435 | function requestHandler(req, res) { 436 | proxy.web(req, res); 437 | } 438 | 439 | var proxyServer = http.createServer(requestHandler); 440 | 441 | var source = http.createServer(function(req, res) { 442 | source.close(); 443 | proxyServer.close(); 444 | var auth = new Buffer(req.headers.authorization.split(' ')[1], 'base64'); 445 | expect(req.method).to.eql('GET'); 446 | expect(auth.toString()).to.eql('user:pass'); 447 | done(); 448 | }); 449 | 450 | proxyServer.listen('8081'); 451 | source.listen('8080'); 452 | 453 | http.request('http://127.0.0.1:8081', function() {}).end(); 454 | }); 455 | 456 | it('should proxy requests to multiple servers with different options', function (done) { 457 | var proxy = httpProxy.createProxyServer(); 458 | 459 | // proxies to two servers depending on url, rewriting the url as well 460 | // http://127.0.0.1:8080/s1/ -> http://127.0.0.1:8081/ 461 | // http://127.0.0.1:8080/ -> http://127.0.0.1:8082/ 462 | function requestHandler(req, res) { 463 | if (req.url.indexOf('/s1/') === 0) { 464 | proxy.web(req, res, { 465 | ignorePath: true, 466 | target: 'http://127.0.0.1:8081' + req.url.substring(3) 467 | }); 468 | } else { 469 | proxy.web(req, res, { 470 | target: 'http://127.0.0.1:8082' 471 | }); 472 | } 473 | } 474 | 475 | var proxyServer = http.createServer(requestHandler); 476 | 477 | var source1 = http.createServer(function(req, res) { 478 | expect(req.method).to.eql('GET'); 479 | expect(req.headers.host.split(':')[1]).to.eql('8080'); 480 | expect(req.url).to.eql('/test1'); 481 | }); 482 | 483 | var source2 = http.createServer(function(req, res) { 484 | source1.close(); 485 | source2.close(); 486 | proxyServer.close(); 487 | expect(req.method).to.eql('GET'); 488 | expect(req.headers.host.split(':')[1]).to.eql('8080'); 489 | expect(req.url).to.eql('/test2'); 490 | done(); 491 | }); 492 | 493 | proxyServer.listen('8080'); 494 | source1.listen('8081'); 495 | source2.listen('8082'); 496 | 497 | http.request('http://127.0.0.1:8080/s1/test1', function() {}).end(); 498 | http.request('http://127.0.0.1:8080/test2', function() {}).end(); 499 | }); 500 | }); 501 | 502 | describe('#followRedirects', function () { 503 | it('should proxy the request follow redirects', function (done) { 504 | var proxy = httpProxy.createProxyServer({ 505 | target: 'http://127.0.0.1:8080', 506 | followRedirects: true 507 | }); 508 | 509 | function requestHandler(req, res) { 510 | proxy.web(req, res); 511 | } 512 | 513 | var proxyServer = http.createServer(requestHandler); 514 | 515 | var source = http.createServer(function(req, res) { 516 | 517 | if (url.parse(req.url).pathname === '/redirect') { 518 | res.writeHead(200, { 'Content-Type': 'text/plain' }); 519 | res.end('ok'); 520 | } 521 | 522 | res.writeHead(301, { 'Location': '/redirect' }); 523 | res.end(); 524 | }); 525 | 526 | proxyServer.listen('8081'); 527 | source.listen('8080'); 528 | 529 | http.request('http://127.0.0.1:8081', function(res) { 530 | source.close(); 531 | proxyServer.close(); 532 | expect(res.statusCode).to.eql(200); 533 | done(); 534 | }).end(); 535 | }); 536 | }); 537 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | # node-http-proxy [![Build Status](https://travis-ci.org/http-party/node-http-proxy.svg?branch=master)](https://travis-ci.org/http-party/node-http-proxy) [![codecov](https://codecov.io/gh/http-party/node-http-proxy/branch/master/graph/badge.svg)](https://codecov.io/gh/http-party/node-http-proxy) 6 | 7 | `node-http-proxy` is an HTTP programmable proxying library that supports 8 | websockets. It is suitable for implementing components such as reverse 9 | proxies and load balancers. 10 | 11 | ### Table of Contents 12 | * [Installation](#installation) 13 | * [Upgrading from 0.8.x ?](#upgrading-from-08x-) 14 | * [Core Concept](#core-concept) 15 | * [Use Cases](#use-cases) 16 | * [Setup a basic stand-alone proxy server](#setup-a-basic-stand-alone-proxy-server) 17 | * [Setup a stand-alone proxy server with custom server logic](#setup-a-stand-alone-proxy-server-with-custom-server-logic) 18 | * [Setup a stand-alone proxy server with proxy request header re-writing](#setup-a-stand-alone-proxy-server-with-proxy-request-header-re-writing) 19 | * [Modify a response from a proxied server](#modify-a-response-from-a-proxied-server) 20 | * [Setup a stand-alone proxy server with latency](#setup-a-stand-alone-proxy-server-with-latency) 21 | * [Using HTTPS](#using-https) 22 | * [Proxying WebSockets](#proxying-websockets) 23 | * [Options](#options) 24 | * [Listening for proxy events](#listening-for-proxy-events) 25 | * [Shutdown](#shutdown) 26 | * [Miscellaneous](#miscellaneous) 27 | * [Test](#test) 28 | * [ProxyTable API](#proxytable-api) 29 | * [Logo](#logo) 30 | * [Contributing and Issues](#contributing-and-issues) 31 | * [License](#license) 32 | 33 | ### Installation 34 | 35 | `npm install http-proxy --save` 36 | 37 | **[Back to top](#table-of-contents)** 38 | 39 | ### Upgrading from 0.8.x ? 40 | 41 | Click [here](UPGRADING.md) 42 | 43 | **[Back to top](#table-of-contents)** 44 | 45 | ### Core Concept 46 | 47 | A new proxy is created by calling `createProxyServer` and passing 48 | an `options` object as argument ([valid properties are available here](lib/http-proxy.js#L26-L42)) 49 | 50 | ```javascript 51 | var httpProxy = require('http-proxy'); 52 | 53 | var proxy = httpProxy.createProxyServer(options); // See (†) 54 | ``` 55 | †Unless listen(..) is invoked on the object, this does not create a webserver. See below. 56 | 57 | An object will be returned with four methods: 58 | 59 | * web `req, res, [options]` (used for proxying regular HTTP(S) requests) 60 | * ws `req, socket, head, [options]` (used for proxying WS(S) requests) 61 | * listen `port` (a function that wraps the object in a webserver, for your convenience) 62 | * close `[callback]` (a function that closes the inner webserver and stops listening on given port) 63 | 64 | It is then possible to proxy requests by calling these functions 65 | 66 | ```javascript 67 | http.createServer(function(req, res) { 68 | proxy.web(req, res, { target: 'http://mytarget.com:8080' }); 69 | }); 70 | ``` 71 | 72 | Errors can be listened on either using the Event Emitter API 73 | 74 | ```javascript 75 | proxy.on('error', function(e) { 76 | ... 77 | }); 78 | ``` 79 | 80 | or using the callback API 81 | 82 | ```javascript 83 | proxy.web(req, res, { target: 'http://mytarget.com:8080' }, function(e) { ... }); 84 | ``` 85 | 86 | When a request is proxied it follows two different pipelines ([available here](lib/http-proxy/passes)) 87 | which apply transformations to both the `req` and `res` object. 88 | The first pipeline (incoming) is responsible for the creation and manipulation of the stream that connects your client to the target. 89 | The second pipeline (outgoing) is responsible for the creation and manipulation of the stream that, from your target, returns data 90 | to the client. 91 | 92 | **[Back to top](#table-of-contents)** 93 | 94 | ### Use Cases 95 | 96 | #### Setup a basic stand-alone proxy server 97 | 98 | ```js 99 | var http = require('http'), 100 | httpProxy = require('http-proxy'); 101 | // 102 | // Create your proxy server and set the target in the options. 103 | // 104 | httpProxy.createProxyServer({target:'http://localhost:9000'}).listen(8000); // See (†) 105 | 106 | // 107 | // Create your target server 108 | // 109 | http.createServer(function (req, res) { 110 | res.writeHead(200, { 'Content-Type': 'text/plain' }); 111 | res.write('request successfully proxied!' + '\n' + JSON.stringify(req.headers, true, 2)); 112 | res.end(); 113 | }).listen(9000); 114 | ``` 115 | †Invoking listen(..) triggers the creation of a web server. Otherwise, just the proxy instance is created. 116 | 117 | **[Back to top](#table-of-contents)** 118 | 119 | #### Setup a stand-alone proxy server with custom server logic 120 | This example shows how you can proxy a request using your own HTTP server 121 | and also you can put your own logic to handle the request. 122 | 123 | ```js 124 | var http = require('http'), 125 | httpProxy = require('http-proxy'); 126 | 127 | // 128 | // Create a proxy server with custom application logic 129 | // 130 | var proxy = httpProxy.createProxyServer({}); 131 | 132 | // 133 | // Create your custom server and just call `proxy.web()` to proxy 134 | // a web request to the target passed in the options 135 | // also you can use `proxy.ws()` to proxy a websockets request 136 | // 137 | var server = http.createServer(function(req, res) { 138 | // You can define here your custom logic to handle the request 139 | // and then proxy the request. 140 | proxy.web(req, res, { target: 'http://127.0.0.1:5050' }); 141 | }); 142 | 143 | console.log("listening on port 5050") 144 | server.listen(5050); 145 | ``` 146 | 147 | **[Back to top](#table-of-contents)** 148 | 149 | #### Setup a stand-alone proxy server with proxy request header re-writing 150 | This example shows how you can proxy a request using your own HTTP server that 151 | modifies the outgoing proxy request by adding a special header. 152 | 153 | ```js 154 | var http = require('http'), 155 | httpProxy = require('http-proxy'); 156 | 157 | // 158 | // Create a proxy server with custom application logic 159 | // 160 | var proxy = httpProxy.createProxyServer({}); 161 | 162 | // To modify the proxy connection before data is sent, you can listen 163 | // for the 'proxyReq' event. When the event is fired, you will receive 164 | // the following arguments: 165 | // (http.ClientRequest proxyReq, http.IncomingMessage req, 166 | // http.ServerResponse res, Object options). This mechanism is useful when 167 | // you need to modify the proxy request before the proxy connection 168 | // is made to the target. 169 | // 170 | proxy.on('proxyReq', function(proxyReq, req, res, options) { 171 | proxyReq.setHeader('X-Special-Proxy-Header', 'foobar'); 172 | }); 173 | 174 | var server = http.createServer(function(req, res) { 175 | // You can define here your custom logic to handle the request 176 | // and then proxy the request. 177 | proxy.web(req, res, { 178 | target: 'http://127.0.0.1:5050' 179 | }); 180 | }); 181 | 182 | console.log("listening on port 5050") 183 | server.listen(5050); 184 | ``` 185 | 186 | **[Back to top](#table-of-contents)** 187 | 188 | #### Modify a response from a proxied server 189 | Sometimes when you have received a HTML/XML document from the server of origin you would like to modify it before forwarding it on. 190 | 191 | [Harmon](https://github.com/No9/harmon) allows you to do this in a streaming style so as to keep the pressure on the proxy to a minimum. 192 | 193 | **[Back to top](#table-of-contents)** 194 | 195 | #### Setup a stand-alone proxy server with latency 196 | 197 | ```js 198 | var http = require('http'), 199 | httpProxy = require('http-proxy'); 200 | 201 | // 202 | // Create a proxy server with latency 203 | // 204 | var proxy = httpProxy.createProxyServer(); 205 | 206 | // 207 | // Create your server that makes an operation that waits a while 208 | // and then proxies the request 209 | // 210 | http.createServer(function (req, res) { 211 | // This simulates an operation that takes 500ms to execute 212 | setTimeout(function () { 213 | proxy.web(req, res, { 214 | target: 'http://localhost:9008' 215 | }); 216 | }, 500); 217 | }).listen(8008); 218 | 219 | // 220 | // Create your target server 221 | // 222 | http.createServer(function (req, res) { 223 | res.writeHead(200, { 'Content-Type': 'text/plain' }); 224 | res.write('request successfully proxied to: ' + req.url + '\n' + JSON.stringify(req.headers, true, 2)); 225 | res.end(); 226 | }).listen(9008); 227 | ``` 228 | 229 | **[Back to top](#table-of-contents)** 230 | 231 | #### Using HTTPS 232 | You can activate the validation of a secure SSL certificate to the target connection (avoid self-signed certs), just set `secure: true` in the options. 233 | 234 | ##### HTTPS -> HTTP 235 | 236 | ```js 237 | // 238 | // Create the HTTPS proxy server in front of a HTTP server 239 | // 240 | httpProxy.createServer({ 241 | target: { 242 | host: 'localhost', 243 | port: 9009 244 | }, 245 | ssl: { 246 | key: fs.readFileSync('valid-ssl-key.pem', 'utf8'), 247 | cert: fs.readFileSync('valid-ssl-cert.pem', 'utf8') 248 | } 249 | }).listen(8009); 250 | ``` 251 | 252 | ##### HTTPS -> HTTPS 253 | 254 | ```js 255 | // 256 | // Create the proxy server listening on port 443 257 | // 258 | httpProxy.createServer({ 259 | ssl: { 260 | key: fs.readFileSync('valid-ssl-key.pem', 'utf8'), 261 | cert: fs.readFileSync('valid-ssl-cert.pem', 'utf8') 262 | }, 263 | target: 'https://localhost:9010', 264 | secure: true // Depends on your needs, could be false. 265 | }).listen(443); 266 | ``` 267 | 268 | ##### HTTP -> HTTPS (using a PKCS12 client certificate) 269 | 270 | ```js 271 | // 272 | // Create an HTTP proxy server with an HTTPS target 273 | // 274 | httpProxy.createProxyServer({ 275 | target: { 276 | protocol: 'https:', 277 | host: 'my-domain-name', 278 | port: 443, 279 | pfx: fs.readFileSync('path/to/certificate.p12'), 280 | passphrase: 'password', 281 | }, 282 | changeOrigin: true, 283 | }).listen(8000); 284 | ``` 285 | 286 | **[Back to top](#table-of-contents)** 287 | 288 | #### Proxying WebSockets 289 | You can activate the websocket support for the proxy using `ws:true` in the options. 290 | 291 | ```js 292 | // 293 | // Create a proxy server for websockets 294 | // 295 | httpProxy.createServer({ 296 | target: 'ws://localhost:9014', 297 | ws: true 298 | }).listen(8014); 299 | ``` 300 | 301 | Also you can proxy the websocket requests just calling the `ws(req, socket, head)` method. 302 | 303 | ```js 304 | // 305 | // Setup our server to proxy standard HTTP requests 306 | // 307 | var proxy = new httpProxy.createProxyServer({ 308 | target: { 309 | host: 'localhost', 310 | port: 9015 311 | } 312 | }); 313 | var proxyServer = http.createServer(function (req, res) { 314 | proxy.web(req, res); 315 | }); 316 | 317 | // 318 | // Listen to the `upgrade` event and proxy the 319 | // WebSocket requests as well. 320 | // 321 | proxyServer.on('upgrade', function (req, socket, head) { 322 | proxy.ws(req, socket, head); 323 | }); 324 | 325 | proxyServer.listen(8015); 326 | ``` 327 | 328 | **[Back to top](#table-of-contents)** 329 | 330 | ### Options 331 | 332 | `httpProxy.createProxyServer` supports the following options: 333 | 334 | * **target**: url string to be parsed with the url module 335 | * **forward**: url string to be parsed with the url module 336 | * **agent**: object to be passed to http(s).request (see Node's [https agent](http://nodejs.org/api/https.html#https_class_https_agent) and [http agent](http://nodejs.org/api/http.html#http_class_http_agent) objects) 337 | * **ssl**: object to be passed to https.createServer() 338 | * **ws**: true/false, if you want to proxy websockets 339 | * **xfwd**: true/false, adds x-forward headers 340 | * **secure**: true/false, if you want to verify the SSL Certs 341 | * **toProxy**: true/false, passes the absolute URL as the `path` (useful for proxying to proxies) 342 | * **prependPath**: true/false, Default: true - specify whether you want to prepend the target's path to the proxy path 343 | * **ignorePath**: true/false, Default: false - specify whether you want to ignore the proxy path of the incoming request (note: you will have to append / manually if required). 344 | * **localAddress**: Local interface string to bind for outgoing connections 345 | * **changeOrigin**: true/false, Default: false - changes the origin of the host header to the target URL 346 | * **preserveHeaderKeyCase**: true/false, Default: false - specify whether you want to keep letter case of response header key 347 | * **auth**: Basic authentication i.e. 'user:password' to compute an Authorization header. 348 | * **hostRewrite**: rewrites the location hostname on (201/301/302/307/308) redirects. 349 | * **autoRewrite**: rewrites the location host/port on (201/301/302/307/308) redirects based on requested host/port. Default: false. 350 | * **protocolRewrite**: rewrites the location protocol on (201/301/302/307/308) redirects to 'http' or 'https'. Default: null. 351 | * **cookieDomainRewrite**: rewrites domain of `set-cookie` headers. Possible values: 352 | * `false` (default): disable cookie rewriting 353 | * String: new domain, for example `cookieDomainRewrite: "new.domain"`. To remove the domain, use `cookieDomainRewrite: ""`. 354 | * Object: mapping of domains to new domains, use `"*"` to match all domains. 355 | For example keep one domain unchanged, rewrite one domain and remove other domains: 356 | ``` 357 | cookieDomainRewrite: { 358 | "unchanged.domain": "unchanged.domain", 359 | "old.domain": "new.domain", 360 | "*": "" 361 | } 362 | ``` 363 | * **cookiePathRewrite**: rewrites path of `set-cookie` headers. Possible values: 364 | * `false` (default): disable cookie rewriting 365 | * String: new path, for example `cookiePathRewrite: "/newPath/"`. To remove the path, use `cookiePathRewrite: ""`. To set path to root use `cookiePathRewrite: "/"`. 366 | * Object: mapping of paths to new paths, use `"*"` to match all paths. 367 | For example, to keep one path unchanged, rewrite one path and remove other paths: 368 | ``` 369 | cookiePathRewrite: { 370 | "/unchanged.path/": "/unchanged.path/", 371 | "/old.path/": "/new.path/", 372 | "*": "" 373 | } 374 | ``` 375 | * **headers**: object with extra headers to be added to target requests. 376 | * **proxyTimeout**: timeout (in millis) for outgoing proxy requests 377 | * **timeout**: timeout (in millis) for incoming requests 378 | * **followRedirects**: true/false, Default: false - specify whether you want to follow redirects 379 | * **selfHandleResponse** true/false, if set to true, none of the webOutgoing passes are called and it's your responsibility to appropriately return the response by listening and acting on the `proxyRes` event 380 | * **buffer**: stream of data to send as the request body. Maybe you have some middleware that consumes the request stream before proxying it on e.g. If you read the body of a request into a field called 'req.rawbody' you could restream this field in the buffer option: 381 | 382 | ``` 383 | 'use strict'; 384 | 385 | const streamify = require('stream-array'); 386 | const HttpProxy = require('http-proxy'); 387 | const proxy = new HttpProxy(); 388 | 389 | module.exports = (req, res, next) => { 390 | 391 | proxy.web(req, res, { 392 | target: 'http://localhost:4003/', 393 | buffer: streamify(req.rawBody) 394 | }, next); 395 | 396 | }; 397 | ``` 398 | 399 | **NOTE:** 400 | `options.ws` and `options.ssl` are optional. 401 | `options.target` and `options.forward` cannot both be missing 402 | 403 | If you are using the `proxyServer.listen` method, the following options are also applicable: 404 | 405 | * **ssl**: object to be passed to https.createServer() 406 | * **ws**: true/false, if you want to proxy websockets 407 | 408 | 409 | **[Back to top](#table-of-contents)** 410 | 411 | ### Listening for proxy events 412 | 413 | * `error`: The error event is emitted if the request to the target fail. **We do not do any error handling of messages passed between client and proxy, and messages passed between proxy and target, so it is recommended that you listen on errors and handle them.** 414 | * `proxyReq`: This event is emitted before the data is sent. It gives you a chance to alter the proxyReq request object. Applies to "web" connections 415 | * `proxyReqWs`: This event is emitted before the data is sent. It gives you a chance to alter the proxyReq request object. Applies to "websocket" connections 416 | * `proxyRes`: This event is emitted if the request to the target got a response. 417 | * `open`: This event is emitted once the proxy websocket was created and piped into the target websocket. 418 | * `close`: This event is emitted once the proxy websocket was closed. 419 | * (DEPRECATED) `proxySocket`: Deprecated in favor of `open`. 420 | 421 | ```js 422 | var httpProxy = require('http-proxy'); 423 | // Error example 424 | // 425 | // Http Proxy Server with bad target 426 | // 427 | var proxy = httpProxy.createServer({ 428 | target:'http://localhost:9005' 429 | }); 430 | 431 | proxy.listen(8005); 432 | 433 | // 434 | // Listen for the `error` event on `proxy`. 435 | proxy.on('error', function (err, req, res) { 436 | res.writeHead(500, { 437 | 'Content-Type': 'text/plain' 438 | }); 439 | 440 | res.end('Something went wrong. And we are reporting a custom error message.'); 441 | }); 442 | 443 | // 444 | // Listen for the `proxyRes` event on `proxy`. 445 | // 446 | proxy.on('proxyRes', function (proxyRes, req, res) { 447 | console.log('RAW Response from the target', JSON.stringify(proxyRes.headers, true, 2)); 448 | }); 449 | 450 | // 451 | // Listen for the `open` event on `proxy`. 452 | // 453 | proxy.on('open', function (proxySocket) { 454 | // listen for messages coming FROM the target here 455 | proxySocket.on('data', hybiParseAndLogMessage); 456 | }); 457 | 458 | // 459 | // Listen for the `close` event on `proxy`. 460 | // 461 | proxy.on('close', function (res, socket, head) { 462 | // view disconnected websocket connections 463 | console.log('Client disconnected'); 464 | }); 465 | ``` 466 | 467 | **[Back to top](#table-of-contents)** 468 | 469 | ### Shutdown 470 | 471 | * When testing or running server within another program it may be necessary to close the proxy. 472 | * This will stop the proxy from accepting new connections. 473 | 474 | ```js 475 | var proxy = new httpProxy.createProxyServer({ 476 | target: { 477 | host: 'localhost', 478 | port: 1337 479 | } 480 | }); 481 | 482 | proxy.close(); 483 | ``` 484 | 485 | **[Back to top](#table-of-contents)** 486 | 487 | ### Miscellaneous 488 | 489 | If you want to handle your own response after receiving the `proxyRes`, you can do 490 | so with `selfHandleResponse`. As you can see below, if you use this option, you 491 | are able to intercept and read the `proxyRes` but you must also make sure to 492 | reply to the `res` itself otherwise the original client will never receive any 493 | data. 494 | 495 | ### Modify response 496 | 497 | ```js 498 | 499 | var option = { 500 | target: target, 501 | selfHandleResponse : true 502 | }; 503 | proxy.on('proxyRes', function (proxyRes, req, res) { 504 | var body = []; 505 | proxyRes.on('data', function (chunk) { 506 | body.push(chunk); 507 | }); 508 | proxyRes.on('end', function () { 509 | body = Buffer.concat(body).toString(); 510 | console.log("res from proxied server:", body); 511 | res.end("my response to cli"); 512 | }); 513 | }); 514 | proxy.web(req, res, option); 515 | 516 | 517 | ``` 518 | 519 | #### ProxyTable API 520 | 521 | A proxy table API is available through this add-on [module](https://github.com/donasaur/http-proxy-rules), which lets you define a set of rules to translate matching routes to target routes that the reverse proxy will talk to. 522 | 523 | #### Test 524 | 525 | ``` 526 | $ npm test 527 | ``` 528 | 529 | #### Logo 530 | 531 | Logo created by [Diego Pasquali](http://dribbble.com/diegopq) 532 | 533 | **[Back to top](#table-of-contents)** 534 | 535 | ### Contributing and Issues 536 | 537 | * Read carefully our [Code Of Conduct](https://github.com/http-party/node-http-proxy/blob/master/CODE_OF_CONDUCT.md) 538 | * Search on Google/Github 539 | * If you can't find anything, open an issue 540 | * If you feel comfortable about fixing the issue, fork the repo 541 | * Commit to your local branch (which must be different from `master`) 542 | * Submit your Pull Request (be sure to include tests and update documentation) 543 | 544 | **[Back to top](#table-of-contents)** 545 | 546 | ### License 547 | 548 | >The MIT License (MIT) 549 | > 550 | >Copyright (c) 2010 - 2016 Charlie Robbins, Jarrett Cruger & the Contributors. 551 | > 552 | >Permission is hereby granted, free of charge, to any person obtaining a copy 553 | >of this software and associated documentation files (the "Software"), to deal 554 | >in the Software without restriction, including without limitation the rights 555 | >to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 556 | >copies of the Software, and to permit persons to whom the Software is 557 | >furnished to do so, subject to the following conditions: 558 | > 559 | >The above copyright notice and this permission notice shall be included in 560 | >all copies or substantial portions of the Software. 561 | > 562 | >THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 563 | >IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 564 | >FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 565 | >AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 566 | >LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 567 | >OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 568 | >THE SOFTWARE. 569 | -------------------------------------------------------------------------------- /test/lib-http-proxy-test.js: -------------------------------------------------------------------------------- 1 | var httpProxy = require('../lib/http-proxy'), 2 | expect = require('expect.js'), 3 | http = require('http'), 4 | net = require('net'), 5 | ws = require('ws'), 6 | io = require('socket.io'), 7 | SSE = require('sse'), 8 | ioClient = require('socket.io-client'); 9 | 10 | // 11 | // Expose a port number generator. 12 | // thanks to @3rd-Eden 13 | // 14 | var initialPort = 1024, gen = {}; 15 | Object.defineProperty(gen, 'port', { 16 | get: function get() { 17 | return initialPort++; 18 | } 19 | }); 20 | 21 | describe('lib/http-proxy.js', function() { 22 | describe('#createProxyServer', function() { 23 | it.skip('should throw without options', function() { 24 | var error; 25 | try { 26 | httpProxy.createProxyServer(); 27 | } catch(e) { 28 | error = e; 29 | } 30 | 31 | expect(error).to.be.an(Error); 32 | }) 33 | 34 | it('should return an object otherwise', function() { 35 | var obj = httpProxy.createProxyServer({ 36 | target: 'http://www.google.com:80' 37 | }); 38 | 39 | expect(obj.web).to.be.a(Function); 40 | expect(obj.ws).to.be.a(Function); 41 | expect(obj.listen).to.be.a(Function); 42 | }); 43 | }); 44 | 45 | describe('#createProxyServer with forward options and using web-incoming passes', function () { 46 | it('should pipe the request using web-incoming#stream method', function (done) { 47 | var ports = { source: gen.port, proxy: gen.port }; 48 | var proxy = httpProxy.createProxyServer({ 49 | forward: 'http://127.0.0.1:' + ports.source 50 | }).listen(ports.proxy); 51 | 52 | var source = http.createServer(function(req, res) { 53 | expect(req.method).to.eql('GET'); 54 | expect(req.headers.host.split(':')[1]).to.eql(ports.proxy); 55 | source.close(); 56 | proxy.close(); 57 | done(); 58 | }); 59 | 60 | source.listen(ports.source); 61 | http.request('http://127.0.0.1:' + ports.proxy, function() {}).end(); 62 | }) 63 | }); 64 | 65 | describe('#createProxyServer using the web-incoming passes', function () { 66 | it('should proxy sse', function(done){ 67 | var ports = { source: gen.port, proxy: gen.port }, 68 | proxy = httpProxy.createProxyServer({ 69 | target: 'http://localhost:' + ports.source, 70 | }), 71 | proxyServer = proxy.listen(ports.proxy), 72 | source = http.createServer(), 73 | sse = new SSE(source, {path: '/'}); 74 | 75 | sse.on('connection', function(client) { 76 | client.send('Hello over SSE'); 77 | client.close(); 78 | }); 79 | 80 | source.listen(ports.source); 81 | 82 | var options = { 83 | hostname: 'localhost', 84 | port: ports.proxy, 85 | }; 86 | 87 | var req = http.request(options, function(res) { 88 | var streamData = ''; 89 | res.on('data', function (chunk) { 90 | streamData += chunk.toString('utf8'); 91 | }); 92 | res.on('end', function (chunk) { 93 | expect(streamData).to.equal(':ok\n\ndata: Hello over SSE\n\n'); 94 | source.close(); 95 | proxy.close(); 96 | done(); 97 | }); 98 | }).end(); 99 | }); 100 | 101 | it('should make the request on pipe and finish it', function(done) { 102 | var ports = { source: gen.port, proxy: gen.port }; 103 | var proxy = httpProxy.createProxyServer({ 104 | target: 'http://127.0.0.1:' + ports.source 105 | }).listen(ports.proxy); 106 | 107 | var source = http.createServer(function(req, res) { 108 | expect(req.method).to.eql('POST'); 109 | expect(req.headers['x-forwarded-for']).to.eql('127.0.0.1'); 110 | expect(req.headers.host.split(':')[1]).to.eql(ports.proxy); 111 | source.close(); 112 | proxy.close(); 113 | done(); 114 | }); 115 | 116 | source.listen(ports.source); 117 | 118 | http.request({ 119 | hostname: '127.0.0.1', 120 | port: ports.proxy, 121 | method: 'POST', 122 | headers: { 123 | 'x-forwarded-for': '127.0.0.1' 124 | } 125 | }, function() {}).end(); 126 | }); 127 | }); 128 | 129 | describe('#createProxyServer using the web-incoming passes', function () { 130 | it('should make the request, handle response and finish it', function(done) { 131 | var ports = { source: gen.port, proxy: gen.port }; 132 | var proxy = httpProxy.createProxyServer({ 133 | target: 'http://127.0.0.1:' + ports.source, 134 | preserveHeaderKeyCase: true 135 | }).listen(ports.proxy); 136 | 137 | var source = http.createServer(function(req, res) { 138 | expect(req.method).to.eql('GET'); 139 | expect(req.headers.host.split(':')[1]).to.eql(ports.proxy); 140 | res.writeHead(200, {'Content-Type': 'text/plain'}) 141 | res.end('Hello from ' + source.address().port); 142 | }); 143 | 144 | source.listen(ports.source); 145 | 146 | http.request({ 147 | hostname: '127.0.0.1', 148 | port: ports.proxy, 149 | method: 'GET' 150 | }, function(res) { 151 | expect(res.statusCode).to.eql(200); 152 | expect(res.headers['content-type']).to.eql('text/plain'); 153 | if (res.rawHeaders != undefined) { 154 | expect(res.rawHeaders.indexOf('Content-Type')).not.to.eql(-1); 155 | expect(res.rawHeaders.indexOf('text/plain')).not.to.eql(-1); 156 | } 157 | 158 | res.on('data', function (data) { 159 | expect(data.toString()).to.eql('Hello from ' + ports.source); 160 | }); 161 | 162 | res.on('end', function () { 163 | source.close(); 164 | proxy.close(); 165 | done(); 166 | }); 167 | }).end(); 168 | }); 169 | }); 170 | 171 | describe('#createProxyServer() method with error response', function () { 172 | it('should make the request and emit the error event', function(done) { 173 | var ports = { source: gen.port, proxy: gen.port }; 174 | var proxy = httpProxy.createProxyServer({ 175 | target: 'http://127.0.0.1:' + ports.source 176 | }); 177 | 178 | proxy.on('error', function (err) { 179 | expect(err).to.be.an(Error); 180 | expect(err.code).to.be('ECONNREFUSED'); 181 | proxy.close(); 182 | done(); 183 | }) 184 | 185 | proxy.listen(ports.proxy); 186 | 187 | http.request({ 188 | hostname: '127.0.0.1', 189 | port: ports.proxy, 190 | method: 'GET', 191 | }, function() {}).end(); 192 | }); 193 | }); 194 | 195 | describe('#createProxyServer setting the correct timeout value', function () { 196 | it('should hang up the socket at the timeout', function (done) { 197 | this.timeout(30); 198 | var ports = { source: gen.port, proxy: gen.port }; 199 | var proxy = httpProxy.createProxyServer({ 200 | target: 'http://127.0.0.1:' + ports.source, 201 | timeout: 3 202 | }).listen(ports.proxy); 203 | 204 | proxy.on('error', function (e) { 205 | expect(e).to.be.an(Error); 206 | expect(e.code).to.be.eql('ECONNRESET'); 207 | }); 208 | 209 | var source = http.createServer(function(req, res) { 210 | setTimeout(function () { 211 | res.end('At this point the socket should be closed'); 212 | }, 5) 213 | }); 214 | 215 | source.listen(ports.source); 216 | 217 | var testReq = http.request({ 218 | hostname: '127.0.0.1', 219 | port: ports.proxy, 220 | method: 'GET', 221 | }, function() {}); 222 | 223 | testReq.on('error', function (e) { 224 | expect(e).to.be.an(Error); 225 | expect(e.code).to.be.eql('ECONNRESET'); 226 | proxy.close(); 227 | source.close(); 228 | done(); 229 | }); 230 | 231 | testReq.end(); 232 | }); 233 | }); 234 | 235 | describe('#createProxyServer with xfwd option', function () { 236 | it('should not throw on empty http host header', function (done) { 237 | var ports = { source: gen.port, proxy: gen.port }; 238 | var proxy = httpProxy.createProxyServer({ 239 | forward: 'http://127.0.0.1:' + ports.source, 240 | xfwd: true 241 | }).listen(ports.proxy); 242 | 243 | var source = http.createServer(function(req, res) { 244 | expect(req.method).to.eql('GET'); 245 | expect(req.headers.host.split(':')[1]).to.eql(ports.source); 246 | source.close(); 247 | proxy.close(); 248 | done(); 249 | }); 250 | 251 | source.listen(ports.source); 252 | 253 | var socket = net.connect({port: ports.proxy}, function() 254 | { 255 | socket.write('GET / HTTP/1.0\r\n\r\n'); 256 | }); 257 | 258 | // handle errors 259 | socket.on('error', function() 260 | { 261 | expect.fail('Unexpected socket error'); 262 | }); 263 | 264 | socket.on('data', function(data) 265 | { 266 | socket.end(); 267 | }); 268 | 269 | socket.on('end', function() 270 | { 271 | expect('Socket to finish').to.be.ok(); 272 | }); 273 | 274 | // http.request('http://127.0.0.1:' + ports.proxy, function() {}).end(); 275 | }) 276 | }); 277 | 278 | // describe('#createProxyServer using the web-incoming passes', function () { 279 | // it('should emit events correctly', function(done) { 280 | // var proxy = httpProxy.createProxyServer({ 281 | // target: 'http://127.0.0.1:8080' 282 | // }), 283 | 284 | // proxyServer = proxy.listen('8081'), 285 | 286 | // source = http.createServer(function(req, res) { 287 | // expect(req.method).to.eql('GET'); 288 | // expect(req.headers.host.split(':')[1]).to.eql('8081'); 289 | // res.writeHead(200, {'Content-Type': 'text/plain'}) 290 | // res.end('Hello from ' + source.address().port); 291 | // }), 292 | 293 | // events = []; 294 | 295 | // source.listen('8080'); 296 | 297 | // proxy.ee.on('http-proxy:**', function (uno, dos, tres) { 298 | // events.push(this.event); 299 | // }) 300 | 301 | // http.request({ 302 | // hostname: '127.0.0.1', 303 | // port: '8081', 304 | // method: 'GET', 305 | // }, function(res) { 306 | // expect(res.statusCode).to.eql(200); 307 | 308 | // res.on('data', function (data) { 309 | // expect(data.toString()).to.eql('Hello from 8080'); 310 | // }); 311 | 312 | // res.on('end', function () { 313 | // expect(events).to.contain('http-proxy:outgoing:web:begin'); 314 | // expect(events).to.contain('http-proxy:outgoing:web:end'); 315 | // source.close(); 316 | // proxyServer.close(); 317 | // done(); 318 | // }); 319 | // }).end(); 320 | // }); 321 | // }); 322 | 323 | describe('#createProxyServer using the ws-incoming passes', function () { 324 | it('should proxy the websockets stream', function (done) { 325 | var ports = { source: gen.port, proxy: gen.port }; 326 | var proxy = httpProxy.createProxyServer({ 327 | target: 'ws://127.0.0.1:' + ports.source, 328 | ws: true 329 | }), 330 | proxyServer = proxy.listen(ports.proxy), 331 | destiny = new ws.Server({ port: ports.source }, function () { 332 | var client = new ws('ws://127.0.0.1:' + ports.proxy); 333 | 334 | client.on('open', function () { 335 | client.send('hello there'); 336 | }); 337 | 338 | client.on('message', function (msg) { 339 | expect(msg).to.be('Hello over websockets'); 340 | client.close(); 341 | proxyServer.close(); 342 | destiny.close(); 343 | done(); 344 | }); 345 | }); 346 | 347 | destiny.on('connection', function (socket) { 348 | socket.on('message', function (msg) { 349 | expect(msg).to.be('hello there'); 350 | socket.send('Hello over websockets'); 351 | }); 352 | }); 353 | }); 354 | 355 | it('should emit error on proxy error', function (done) { 356 | var ports = { source: gen.port, proxy: gen.port }; 357 | var proxy = httpProxy.createProxyServer({ 358 | // note: we don't ever listen on this port 359 | target: 'ws://127.0.0.1:' + ports.source, 360 | ws: true 361 | }), 362 | proxyServer = proxy.listen(ports.proxy), 363 | client = new ws('ws://127.0.0.1:' + ports.proxy); 364 | 365 | client.on('open', function () { 366 | client.send('hello there'); 367 | }); 368 | 369 | var count = 0; 370 | function maybe_done () { 371 | count += 1; 372 | if (count === 2) done(); 373 | } 374 | 375 | client.on('error', function (err) { 376 | expect(err).to.be.an(Error); 377 | expect(err.code).to.be('ECONNRESET'); 378 | maybe_done(); 379 | }); 380 | 381 | proxy.on('error', function (err) { 382 | expect(err).to.be.an(Error); 383 | expect(err.code).to.be('ECONNREFUSED'); 384 | proxyServer.close(); 385 | maybe_done(); 386 | }); 387 | }); 388 | 389 | it('should close client socket if upstream is closed before upgrade', function (done) { 390 | var ports = { source: gen.port, proxy: gen.port }; 391 | var server = http.createServer(); 392 | server.on('upgrade', function (req, socket, head) { 393 | var response = [ 394 | 'HTTP/1.1 404 Not Found', 395 | 'Content-type: text/html', 396 | '', 397 | '' 398 | ]; 399 | socket.write(response.join('\r\n')); 400 | socket.end(); 401 | }); 402 | server.listen(ports.source); 403 | 404 | var proxy = httpProxy.createProxyServer({ 405 | // note: we don't ever listen on this port 406 | target: 'ws://127.0.0.1:' + ports.source, 407 | ws: true 408 | }), 409 | proxyServer = proxy.listen(ports.proxy), 410 | client = new ws('ws://127.0.0.1:' + ports.proxy); 411 | 412 | client.on('open', function () { 413 | client.send('hello there'); 414 | }); 415 | 416 | client.on('error', function (err) { 417 | expect(err).to.be.an(Error); 418 | proxyServer.close(); 419 | done(); 420 | }); 421 | }); 422 | 423 | it('should proxy a socket.io stream', function (done) { 424 | var ports = { source: gen.port, proxy: gen.port }, 425 | proxy = httpProxy.createProxyServer({ 426 | target: 'ws://127.0.0.1:' + ports.source, 427 | ws: true 428 | }), 429 | proxyServer = proxy.listen(ports.proxy), 430 | server = http.createServer(), 431 | destiny = io.listen(server); 432 | 433 | function startSocketIo() { 434 | var client = ioClient.connect('ws://127.0.0.1:' + ports.proxy); 435 | 436 | client.on('connect', function () { 437 | client.emit('incoming', 'hello there'); 438 | }); 439 | 440 | client.on('outgoing', function (data) { 441 | expect(data).to.be('Hello over websockets'); 442 | proxyServer.close(); 443 | server.close(); 444 | done(); 445 | }); 446 | } 447 | server.listen(ports.source); 448 | server.on('listening', startSocketIo); 449 | 450 | destiny.sockets.on('connection', function (socket) { 451 | socket.on('incoming', function (msg) { 452 | expect(msg).to.be('hello there'); 453 | socket.emit('outgoing', 'Hello over websockets'); 454 | }); 455 | }) 456 | }); 457 | 458 | 459 | it('should emit open and close events when socket.io client connects and disconnects', function (done) { 460 | var ports = { source: gen.port, proxy: gen.port }; 461 | var proxy = httpProxy.createProxyServer({ 462 | target: 'ws://127.0.0.1:' + ports.source, 463 | ws: true 464 | }); 465 | var proxyServer = proxy.listen(ports.proxy); 466 | var server = http.createServer(); 467 | var destiny = io.listen(server); 468 | 469 | function startSocketIo() { 470 | var client = ioClient.connect('ws://127.0.0.1:' + ports.proxy, {rejectUnauthorized: null}); 471 | client.on('connect', function () { 472 | client.disconnect(); 473 | }); 474 | } 475 | var count = 0; 476 | 477 | proxyServer.on('open', function() { 478 | count += 1; 479 | 480 | }); 481 | 482 | proxyServer.on('close', function() { 483 | proxyServer.close(); 484 | server.close(); 485 | destiny.close(); 486 | if (count == 1) { done(); } 487 | }); 488 | 489 | server.listen(ports.source); 490 | server.on('listening', startSocketIo); 491 | 492 | }); 493 | 494 | it('should pass all set-cookie headers to client', function (done) { 495 | var ports = { source: gen.port, proxy: gen.port }; 496 | var proxy = httpProxy.createProxyServer({ 497 | target: 'ws://127.0.0.1:' + ports.source, 498 | ws: true 499 | }), 500 | proxyServer = proxy.listen(ports.proxy), 501 | destiny = new ws.Server({ port: ports.source }, function () { 502 | var key = new Buffer(Math.random().toString()).toString('base64'); 503 | 504 | var requestOptions = { 505 | port: ports.proxy, 506 | host: '127.0.0.1', 507 | headers: { 508 | 'Connection': 'Upgrade', 509 | 'Upgrade': 'websocket', 510 | 'Host': 'ws://127.0.0.1', 511 | 'Sec-WebSocket-Version': 13, 512 | 'Sec-WebSocket-Key': key 513 | } 514 | }; 515 | 516 | var req = http.request(requestOptions); 517 | 518 | req.on('upgrade', function (res, socket, upgradeHead) { 519 | expect(res.headers['set-cookie'].length).to.be(2); 520 | done(); 521 | }); 522 | 523 | req.end(); 524 | }); 525 | 526 | destiny.on('headers', function (headers) { 527 | headers.push('Set-Cookie: test1=test1'); 528 | headers.push('Set-Cookie: test2=test2'); 529 | }); 530 | }); 531 | 532 | it('should detect a proxyReq event and modify headers', function (done) { 533 | var ports = { source: gen.port, proxy: gen.port }, 534 | proxy, 535 | proxyServer, 536 | destiny; 537 | 538 | proxy = httpProxy.createProxyServer({ 539 | target: 'ws://127.0.0.1:' + ports.source, 540 | ws: true 541 | }); 542 | 543 | proxy.on('proxyReqWs', function(proxyReq, req, socket, options, head) { 544 | proxyReq.setHeader('X-Special-Proxy-Header', 'foobar'); 545 | }); 546 | 547 | proxyServer = proxy.listen(ports.proxy); 548 | 549 | destiny = new ws.Server({ port: ports.source }, function () { 550 | var client = new ws('ws://127.0.0.1:' + ports.proxy); 551 | 552 | client.on('open', function () { 553 | client.send('hello there'); 554 | }); 555 | 556 | client.on('message', function (msg) { 557 | expect(msg).to.be('Hello over websockets'); 558 | client.close(); 559 | proxyServer.close(); 560 | destiny.close(); 561 | done(); 562 | }); 563 | }); 564 | 565 | destiny.on('connection', function (socket, upgradeReq) { 566 | expect(upgradeReq.headers['x-special-proxy-header']).to.eql('foobar'); 567 | 568 | socket.on('message', function (msg) { 569 | expect(msg).to.be('hello there'); 570 | socket.send('Hello over websockets'); 571 | }); 572 | }); 573 | }); 574 | 575 | it('should forward frames with single frame payload (including on node 4.x)', function (done) { 576 | var payload = Array(65529).join('0'); 577 | var ports = { source: gen.port, proxy: gen.port }; 578 | var proxy = httpProxy.createProxyServer({ 579 | target: 'ws://127.0.0.1:' + ports.source, 580 | ws: true 581 | }), 582 | proxyServer = proxy.listen(ports.proxy), 583 | destiny = new ws.Server({ port: ports.source }, function () { 584 | var client = new ws('ws://127.0.0.1:' + ports.proxy); 585 | 586 | client.on('open', function () { 587 | client.send(payload); 588 | }); 589 | 590 | client.on('message', function (msg) { 591 | expect(msg).to.be('Hello over websockets'); 592 | client.close(); 593 | proxyServer.close(); 594 | destiny.close(); 595 | done(); 596 | }); 597 | }); 598 | 599 | destiny.on('connection', function (socket) { 600 | socket.on('message', function (msg) { 601 | expect(msg).to.be(payload); 602 | socket.send('Hello over websockets'); 603 | }); 604 | }); 605 | }); 606 | 607 | it('should forward continuation frames with big payload (including on node 4.x)', function (done) { 608 | var payload = Array(65530).join('0'); 609 | var ports = { source: gen.port, proxy: gen.port }; 610 | var proxy = httpProxy.createProxyServer({ 611 | target: 'ws://127.0.0.1:' + ports.source, 612 | ws: true 613 | }), 614 | proxyServer = proxy.listen(ports.proxy), 615 | destiny = new ws.Server({ port: ports.source }, function () { 616 | var client = new ws('ws://127.0.0.1:' + ports.proxy); 617 | 618 | client.on('open', function () { 619 | client.send(payload); 620 | }); 621 | 622 | client.on('message', function (msg) { 623 | expect(msg).to.be('Hello over websockets'); 624 | client.close(); 625 | proxyServer.close(); 626 | destiny.close(); 627 | done(); 628 | }); 629 | }); 630 | 631 | destiny.on('connection', function (socket) { 632 | socket.on('message', function (msg) { 633 | expect(msg).to.be(payload); 634 | socket.send('Hello over websockets'); 635 | }); 636 | }); 637 | }); 638 | }); 639 | }); 640 | --------------------------------------------------------------------------------