├── .gitignore ├── README.md ├── benchmark ├── README.md └── scripts │ ├── hello.js │ └── proxy.js ├── index.js ├── package.json └── test ├── cert ├── cert.pem ├── chain.pem ├── fullchain.pem └── privkey.pem └── main.test.js /.gitignore: -------------------------------------------------------------------------------- 1 | ## Directory-based project format 2 | *.sublime* 3 | 4 | # OSX 5 | *.DS_Store 6 | 7 | node_modules 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HOST-PROXY 2 | Fast, lightweight and transparent http(s) proxy that supports dynamic hostnames. 3 | 4 | # Installation 5 | 6 | #### Npm 7 | ```console 8 | npm install host-proxy 9 | ``` 10 | 11 | # Example 12 | 13 | ```javascript 14 | var createProxy = require('host-proxy'); 15 | 16 | // Start a proxy server. 17 | createProxy(function (hostname, secure) { 18 | // Respond with an address to proxy to. 19 | // Result is passed to net.createConnection. 20 | return ({ 21 | "test.com": "http://localhost:3002", 22 | "secure.test.com": "https://localhost:3003", 23 | "search.test.com": "http://google.ca", 24 | "api.test.com": { port: 12345, family: 'IPv4', address: "127.0.0.1" } 25 | })[hostname]; 26 | }).listen(80); 27 | ``` 28 | 29 | ### The above example creates the following proxy: 30 | 31 | * **test.com** -> http://localhost:3002 32 | * **secure.test.com** -> https://localhost:3003 33 | * **search.test.com** -> http://google.ca 34 | * **api.test.com** -> http://localhost:12345 35 | 36 | ### Contributions 37 | 38 | * Use npm to run tests. 39 | 40 | Please feel free to create a PR! 41 | -------------------------------------------------------------------------------- /benchmark/README.md: -------------------------------------------------------------------------------- 1 | # Benchmarking `host-proxy` 2 | 3 | ## Pre-requisites 4 | 5 | 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**. 6 | The easiest way to get wrk on OSX is with homebrew. 7 | 8 | ``` 9 | $ brew install wrk 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://localhost:8000` 38 | -------------------------------------------------------------------------------- /benchmark/scripts/hello.js: -------------------------------------------------------------------------------- 1 | require('http').createServer(function (req, res) { 2 | res.end('Hello world!') 3 | }).listen(9000) 4 | -------------------------------------------------------------------------------- /benchmark/scripts/proxy.js: -------------------------------------------------------------------------------- 1 | var createProxy = require('../../') 2 | createProxy(function (hostname) { 3 | if (hostname !== 'localhost') return 4 | return { 5 | port: 9000, 6 | address: 'localhost' 7 | } 8 | }).listen(8000) 9 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var net = require('net') 4 | var url = require('url') 5 | var hostReg = /^Host: ([^:\r\n]+).*$/im 6 | var wwwReg = /^www\./ 7 | var ignoreErrors = { ECONNREFUSED: true, ECONNRESET: true } 8 | module.exports = createProxy 9 | 10 | /** 11 | * Creates a proxy server that will forward a hostname to a specific address. 12 | * 13 | * @params {Function} findAddress - a function that when given a hostname returns a valid address object. 14 | * @returns {net.Server} 15 | */ 16 | function createProxy (findAddress) { 17 | if (typeof findAddress !== 'function') { 18 | throw new TypeError('host resolver must be a function') 19 | } 20 | 21 | return net.createServer(handleConnection) 22 | 23 | /** 24 | * Function to handle a new connection. 25 | * Adds an eventlistener to wait for data. 26 | * 27 | * @param {net.Socket|tls.TLSSocket} socket 28 | */ 29 | function handleConnection (socket) { 30 | socket 31 | .once('error', handleError) 32 | .once('data', handleData) 33 | } 34 | 35 | /** 36 | * Function to listen 37 | for initial request data. 38 | * Attempts to proxy request based on hostname. 39 | * 40 | * @param {Buffer} data 41 | */ 42 | function handleData (data) { 43 | var secure = data[0] === 22 44 | var hostname = secure ? parseSNI(data) : parseHeader(data) 45 | var address = parseAddress(findAddress(hostname, secure)) 46 | if (address == null) return this.end() 47 | var proxy = net.connect(address) 48 | proxy 49 | .setTimeout(0) 50 | .once('error', handleError) 51 | .write(data) 52 | this 53 | .setTimeout(0) 54 | .pipe(proxy) 55 | .pipe(this) 56 | } 57 | } 58 | 59 | /** 60 | * Gracefully handle net errors. 61 | * 62 | * @param {Error} err 63 | */ 64 | function handleError (err) { 65 | if (ignoreErrors[err.code]) return this.end() 66 | this.destroy(err) 67 | } 68 | 69 | /** 70 | * Parses initial http header for a hostname. 71 | * 72 | * @params {Buffer} data 73 | * @returns {String|null} 74 | */ 75 | function parseHeader (data) { 76 | var match = data.toString('utf8').match(hostReg) 77 | return match && match[1] 78 | } 79 | 80 | /** 81 | * Parses initial https SNI data for a hostname. 82 | * 83 | * @params {Buffer} data 84 | * @returns {String|null} 85 | */ 86 | function parseSNI (data) { 87 | // Session ID Length (static position) 88 | var currentPos = 43 89 | // Skip session IDs 90 | currentPos += 1 + data[currentPos] 91 | // skip Cipher Suites 92 | currentPos += 2 + data.readInt16BE(currentPos) 93 | // skip compression methods 94 | currentPos += 1 + data[currentPos] 95 | // We are now at extensions! 96 | currentPos += 2 // ignore extensions length 97 | while (currentPos < data.length) { 98 | if (data.readInt16BE(currentPos) === 0) { 99 | // we have found an SNI 100 | var sniLength = data.readInt16BE(currentPos + 2) 101 | currentPos += 4 102 | // the RFC says this is a reserved host type, not DNS 103 | if (data[currentPos] !== 0) return null 104 | currentPos += 5 105 | return data.toString('utf8', currentPos, currentPos + sniLength - 5) 106 | } else { 107 | currentPos += 4 + data.readInt16BE(currentPos + 2) 108 | } 109 | } 110 | return null 111 | } 112 | 113 | /** 114 | * Converts an href string into a valid socket address. 115 | * 116 | * @params {*} address 117 | * @returns {Object} 118 | */ 119 | function parseAddress (address) { 120 | if (typeof address !== 'string') return address 121 | var parsed = url.parse(address) 122 | return { host: parsed.hostname.replace(wwwReg, ''), port: parsed.port } 123 | } 124 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "host-proxy", 3 | "description": "Fast, lightweight and transparent http(s) proxy that supports dynamic hostnames.", 4 | "version": "1.0.2", 5 | "author": "Dylan Piercey ", 6 | "bugs": "https://github.com/DylanPiercey/host-proxy/issues", 7 | "devDependencies": { 8 | "snazzy": "^5.0.0", 9 | "standard": "^8.6.0", 10 | "supertest": "^2.0.1", 11 | "tap-spec": "^4.1.1", 12 | "tape": "^4.6.3" 13 | }, 14 | "homepage": "https://github.com/DylanPiercey/host-proxy", 15 | "keywords": [ 16 | "forward", 17 | "host", 18 | "hosts", 19 | "proxy", 20 | "redirect", 21 | "virtual" 22 | ], 23 | "license": "MIT", 24 | "main": "index.js", 25 | "repository": { 26 | "type": "git", 27 | "url": "https://github.com/DylanPiercey/host-proxy" 28 | }, 29 | "scripts": { 30 | "lint": "standard --verbose | snazzy", 31 | "test": "npm run lint && tape ./**/*.test.js | tap-spec" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /test/cert/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDHTCCAgWgAwIBAgIJANBgK5/jOawGMA0GCSqGSIb3DQEBBQUAMBMxETAPBgNV 3 | BAMTCHRlc3QuY29tMB4XDTE2MDMwODA0MjU1N1oXDTE2MDYwNjA0MjU1N1owEzER 4 | MA8GA1UEAxMIdGVzdC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB 5 | AQDse9yqLIrjd1Q0brJ8pCCw/PM9dAGtxBvEtCA/REOjCdnv4QdBZTTeclihIKmj 6 | AZIJH0HsdyaiQV+sQamyO1CiRhOIvI2o0SYvmsS4G0IhVqLUf1dEDPv8e5BH2mr8 7 | g8cQvsVkIZg/C768IXS9zaUqZEUPI7XOeCA68sNXm7P5VsP7NJ/4oWY6z9MLiesm 8 | +zGxKYsWOlE9tuJbu5oNLJXbLxgxZoqpP1oqR2LebMqYBRSQjOGFv/aFusWrNhtL 9 | dCNSRcTRUcrOvm2QRg2L7Ooic1nucypZShzSxmRvdBxL2ZcOS2nv6G9u6iPeBc5h 10 | yJ001eCrhwuOzbOScePAlxezAgMBAAGjdDByMB0GA1UdDgQWBBRK9LQxgJjnhJ91 11 | PI0rdtCcviwqbjBDBgNVHSMEPDA6gBRK9LQxgJjnhJ91PI0rdtCcviwqbqEXpBUw 12 | EzERMA8GA1UEAxMIdGVzdC5jb22CCQDQYCuf4zmsBjAMBgNVHRMEBTADAQH/MA0G 13 | CSqGSIb3DQEBBQUAA4IBAQADWw7Zs4i2q4rL2TYwAp7iFixDRHWrTnxfarOOlcWX 14 | Mdk9cHSS7by9hvCyrIQ8sfidacV0Et5aRPaCF3LEC0LF1qn6OmCOz7NXo9F3YOy4 15 | WdN53k0YS1a/zhwAFZGeKrucimzL7ZiOT7IeQqd8Edu1Bk+iqLqkrnywH/dTMlas 16 | ZnIlkwXJhApD9yB5DrfJP11AfMIZpOF+O0dFe2gsLE5tv5I7ismjANZh/YOabAIy 17 | UBiJjHetsIteLEihvgIepfnF0vSHynmtpcYiGkR93IKHeAFI6FRY9KBhbv9SAYc4 18 | 7MpA3TebzocuWa/bEpC3/mRMCudfvU2lbPgMJoK1gw0U 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /test/cert/chain.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEogIBAAKCAQEAvVI7t3jYc0OVIazcbS6Uhqes7GbKrM3A5U91p+sw2er9fuad 3 | qWFqSz6JU9dm+zzLeVKeEXmsKfUiZ6jF4rwIFfg3zKlaVLldXnHB+0nP6nJDTGza 4 | pcWkT3iUpEGY9Txb6toHaKo7D2QPH4jllNi9uU+0OPzQ26XIgDao7aurrSO/62Du 5 | lPVsxtfthNkto4txQzUCmrQay/N/o6UKfW/yoHcohn0SIvkXHXb+t0/thXyzmhwe 6 | URNkrFsJHJpZOoHfribp0eBz5GlRVTj/wG5u1q4o+RTtrRP4zZntEUc7YNg09y2Y 7 | yslJ10RNH/ZBZ/CqPwSihic5Fjw6lFXt0vDByQIDAQABAoIBAEBBP7lWj4gXYO8r 8 | /ygjTg+hqyQVAybBMn3hiRozzcG4as7+MHaDdJMTJ4LIMdCFYr3Ll5BwzkjzRPkP 9 | pBH/JXNnanDQ7bKhsxnR3DeQZru4ijTPISpwOQMr9q8FAIgDM2MzGp6gYCkqGaPn 10 | T5xEL6hMb+uDitNxoeo6Iv1/bWX2jGPhXhQ99q1HlaEP6MIcYxgoyYdPn6PMqzFc 11 | UFurfgkIc/ZcLIcLnaiQLd8izJaAEyBHzlRNKPjLvmrPmAXu2Wx5dN/LbPhlhKvP 12 | Uyy3FLC9FB7/JrPhCwLBkjZIJLQwjcdArgjBuBKou+rrFq4ScHT1ZkE6jIG1j4bC 13 | zJw3kg0CgYEA/C7AJfEt/xy9D1zelMfLUyErYEdrNwZTfbn6olsDOrqy7bnIUacP 14 | vPDxghdxFjO6Vwn+C+9u7pv+ZYs/vcA7AKi4vZHmlEKB5mk9Souoag42sMRU8oee 15 | 3QZu5YG9OqME4o1BsTWTd/1piGvRWv2yMeQ7DnQ0pG5JCfAravQzLjMCgYEAwC/i 16 | Zfyv+TYQ2CQSMW+nbZMvst/eSq7GkEI5MZHBWWOdS0KPISXJR4bh4MND4kXRB1U8 17 | qvg8NRVQaeZuGxvp/7/wX3OdVJpWN2ExgI/7NbAasfhVtUvg0yJNlKSELexGF6lM 18 | MAdK6wpVbV00ChGcKSetMbR11cirgfwgntlOXBMCgYAD3QN8kVFPEzWiQp32UYk/ 19 | 4MY1V2LBGQu2ZV79lb9a6vlKwwnalmCbWGWaxJkq6ef05WZK8IQWi7U+AQAc/BDr 20 | Vape3RlXNB2hVgNmXPVbipQz6Q4UGRfJ8wtH4NDxUQYGInvbJyL3N56iRGSUrEZK 21 | lWCCPC2CR14/FLrVjKAsGQKBgFugOLLg5tDMmvlBMT9KeSs/ifUg0C0Q6G/scrIU 22 | SHcii+JOqEhoaG7mepga4ClnmcGCPLtSP/oa4Eys5H+eLlCVJLeNOkwUjNg4SU5+ 23 | 1oiBHikNOWeG9ngSmaQtTytJwx0SxNT9FRW7xoCud9n5DVd9qA/Tdn5m/oWpO7Af 24 | 8EDrAoGADXAlHwJt+MRVl/vHAiCE9i8vLPq1OmxPy9G2JVKdwGUKJ7DoI65H+d72 25 | +6SLbeAWyEir2XzLBB9WiQfEIhySyGmc7bwdKD0ioCZQ5V7QggioXXnKzFqGHCh+ 26 | Y15ytKsd9GsUh8laQCAqeAEKcB0pCoPClPihwnIQz2vfXaVcyZA= 27 | -----END RSA PRIVATE KEY----- 28 | -----BEGIN CERTIFICATE----- 29 | MIIDIDCCAgigAwIBAgIJANInns7RVUYBMA0GCSqGSIb3DQEBBQUAMBQxEjAQBgNV 30 | BAMTCWxvY2FsaG9zdDAeFw0xNjAzMDgwNDE5MTRaFw0xNjA2MDYwNDE5MTRaMBQx 31 | EjAQBgNVBAMTCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC 32 | ggEBAL1SO7d42HNDlSGs3G0ulIanrOxmyqzNwOVPdafrMNnq/X7mnalhaks+iVPX 33 | Zvs8y3lSnhF5rCn1ImeoxeK8CBX4N8ypWlS5XV5xwftJz+pyQ0xs2qXFpE94lKRB 34 | mPU8W+raB2iqOw9kDx+I5ZTYvblPtDj80NulyIA2qO2rq60jv+tg7pT1bMbX7YTZ 35 | LaOLcUM1Apq0Gsvzf6OlCn1v8qB3KIZ9EiL5Fx12/rdP7YV8s5ocHlETZKxbCRya 36 | WTqB364m6dHgc+RpUVU4/8BubtauKPkU7a0T+M2Z7RFHO2DYNPctmMrJSddETR/2 37 | QWfwqj8EooYnORY8OpRV7dLwwckCAwEAAaN1MHMwHQYDVR0OBBYEFKL8OmX5WKWP 38 | rFXt0CxzfsE5HtAvMEQGA1UdIwQ9MDuAFKL8OmX5WKWPrFXt0CxzfsE5HtAvoRik 39 | FjAUMRIwEAYDVQQDEwlsb2NhbGhvc3SCCQDSJ57O0VVGATAMBgNVHRMEBTADAQH/ 40 | MA0GCSqGSIb3DQEBBQUAA4IBAQAJNXskDhaANlxjYyiPxNzQYALAwY+XpR1+pAUR 41 | fryMYii0ZHaxIzWxdR2hT02BkNpz0CXe7b89UL7ULL1P3tMVqW0SfkUiHhurllcL 42 | TiiOug5D9finvhl+6ijWHcFsdX4z0Q2OJHzCImakMdLTAOmWzeA6aSkNx7XWwIcE 43 | Jg+7ERb5iPMMTxitO541Yq0BGfMbdvni+Wb35dLh8kXVLt3YLn8QsI5xUIrFpMGl 44 | tPv/xfIW9NAvZa5gJTQnXSN1zz2oYut2q3o7SHNVLrFrW5L7Det6thrvrxg/JG5j 45 | QOxs5GCEvcQlOpwqxSrF22PCMRz4bt13cZYOAKlfhydfni9O 46 | -----END CERTIFICATE----- 47 | -----BEGIN RSA PRIVATE KEY----- 48 | MIIEpAIBAAKCAQEA7HvcqiyK43dUNG6yfKQgsPzzPXQBrcQbxLQgP0RDownZ7+EH 49 | QWU03nJYoSCpowGSCR9B7HcmokFfrEGpsjtQokYTiLyNqNEmL5rEuBtCIVai1H9X 50 | RAz7/HuQR9pq/IPHEL7FZCGYPwu+vCF0vc2lKmRFDyO1znggOvLDV5uz+VbD+zSf 51 | +KFmOs/TC4nrJvsxsSmLFjpRPbbiW7uaDSyV2y8YMWaKqT9aKkdi3mzKmAUUkIzh 52 | hb/2hbrFqzYbS3QjUkXE0VHKzr5tkEYNi+zqInNZ7nMqWUoc0sZkb3QcS9mXDktp 53 | 7+hvbuoj3gXOYcidNNXgq4cLjs2zknHjwJcXswIDAQABAoIBAQDRz37S2JwM/hUO 54 | Q9xFq1R50qJ4NlE6+w/r8CfgGc+sehQKuGq4gXNrnKW3pwP6ZG1NfJb/liMHt9qL 55 | OOK3B9sdbfDs0uUYDx/DApxf/4cnjvb+c1BYRvDWbEZxCX78NGpMGAWHNiseXGxH 56 | +6LYtvg/6SBAM12dJoTp5/KZ6sG9h6G6NTdthTO2C1qrl3glMGqVbE5ByCEKm8SF 57 | FOz4euRdQ/zpFcaJfYnuCgPiJj/6hSb6XZ+YJbpyH4LXIs0tyMA+BrVqFlqZ9CRN 58 | jIRfxHCvfqeK8w+HQGKs6rGWDGlH25Wnw8+Y9g9vIz0TZMUp5MW3yZTPKlbvg7nH 59 | 51TaWarpAoGBAPuXAAaxCiZsfQd9oOT9VPpo56rOZ4PlBgIaN63mmwhhDwvRq0Wr 60 | mHq9M5bTvGMqz6zzOuBkdwrYSfbloP7gJy9yVqAblerzc8sULtG0gIiM992svak8 61 | t38V28ylLvOAma5xUu4DjjezdBvBYXCRwryJ9LkRu8Bas5sxYYVSwD0/AoGBAPCh 62 | EwCwZXhnFiuGyTKOt5hBG5Gg+UiD+t5gaF++eD3g3fdVLM5JQHLquEo1D5d4hxC/ 63 | 609CKvdB6n8YnJ3K3QZfMCcq5J6DdQPJpGjQkIxPEA2GIUqPSaYsH+4zSRFMq430 64 | MBJyW2wdoaUOIrOzAATq5ivWXNKw7CM88We3R6SNAoGAQeYFXbtVm0bjYOjpEdHB 65 | Rm3f8H9r4cJFVpujZet3RDSYClJ6+B7AK7YPIzyGpbr69qQnEct+2tpYVVFEYD46 66 | RVU/l2RiWk42UEUTFl6fJCU4b1nlu8Rpk+IX1nyV1bYjiuun+yv3PmPEz9hRO3kS 67 | duvzq94XPYUEcmv2zUMlk2UCgYEAi7PolXDUKJgpDfI+I4UqYFIEkIMV150QbEUq 68 | s9OgecRw+iTQU5/BtDZD8oll2PoX6IiUNDrlORJi85E1dIMP2aAwE71aaBFIQX+z 69 | XUshcOSCHGfwMhqIltptW10ZIdsSiuLnef48x8NYUrynNw9IhpJeObtFvukK39aF 70 | DUMibA0CgYALFFkKCYTUpO3QDEXVoQ4fbFs8SEVwx0qjG+K2pUiOxOe4BZCzijdd 71 | zgZVEGoJjvbbsQhb/nqlc6O9x1uKNa1vNqPj/HV6VK+95bVAuvSn6v7RgNHvIdiK 72 | Gk/ZYhR3A5hCFwX4aG31i3hoLHjewzzD7HyMdvoTxuXJOAoaGimp1w== 73 | -----END RSA PRIVATE KEY----- 74 | -----BEGIN CERTIFICATE----- 75 | MIIDHTCCAgWgAwIBAgIJANBgK5/jOawGMA0GCSqGSIb3DQEBBQUAMBMxETAPBgNV 76 | BAMTCHRlc3QuY29tMB4XDTE2MDMwODA0MjU1N1oXDTE2MDYwNjA0MjU1N1owEzER 77 | MA8GA1UEAxMIdGVzdC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB 78 | AQDse9yqLIrjd1Q0brJ8pCCw/PM9dAGtxBvEtCA/REOjCdnv4QdBZTTeclihIKmj 79 | AZIJH0HsdyaiQV+sQamyO1CiRhOIvI2o0SYvmsS4G0IhVqLUf1dEDPv8e5BH2mr8 80 | g8cQvsVkIZg/C768IXS9zaUqZEUPI7XOeCA68sNXm7P5VsP7NJ/4oWY6z9MLiesm 81 | +zGxKYsWOlE9tuJbu5oNLJXbLxgxZoqpP1oqR2LebMqYBRSQjOGFv/aFusWrNhtL 82 | dCNSRcTRUcrOvm2QRg2L7Ooic1nucypZShzSxmRvdBxL2ZcOS2nv6G9u6iPeBc5h 83 | yJ001eCrhwuOzbOScePAlxezAgMBAAGjdDByMB0GA1UdDgQWBBRK9LQxgJjnhJ91 84 | PI0rdtCcviwqbjBDBgNVHSMEPDA6gBRK9LQxgJjnhJ91PI0rdtCcviwqbqEXpBUw 85 | EzERMA8GA1UEAxMIdGVzdC5jb22CCQDQYCuf4zmsBjAMBgNVHRMEBTADAQH/MA0G 86 | CSqGSIb3DQEBBQUAA4IBAQADWw7Zs4i2q4rL2TYwAp7iFixDRHWrTnxfarOOlcWX 87 | Mdk9cHSS7by9hvCyrIQ8sfidacV0Et5aRPaCF3LEC0LF1qn6OmCOz7NXo9F3YOy4 88 | WdN53k0YS1a/zhwAFZGeKrucimzL7ZiOT7IeQqd8Edu1Bk+iqLqkrnywH/dTMlas 89 | ZnIlkwXJhApD9yB5DrfJP11AfMIZpOF+O0dFe2gsLE5tv5I7ismjANZh/YOabAIy 90 | UBiJjHetsIteLEihvgIepfnF0vSHynmtpcYiGkR93IKHeAFI6FRY9KBhbv9SAYc4 91 | 7MpA3TebzocuWa/bEpC3/mRMCudfvU2lbPgMJoK1gw0U 92 | -----END CERTIFICATE----- 93 | -------------------------------------------------------------------------------- /test/cert/fullchain.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEogIBAAKCAQEAvVI7t3jYc0OVIazcbS6Uhqes7GbKrM3A5U91p+sw2er9fuad 3 | qWFqSz6JU9dm+zzLeVKeEXmsKfUiZ6jF4rwIFfg3zKlaVLldXnHB+0nP6nJDTGza 4 | pcWkT3iUpEGY9Txb6toHaKo7D2QPH4jllNi9uU+0OPzQ26XIgDao7aurrSO/62Du 5 | lPVsxtfthNkto4txQzUCmrQay/N/o6UKfW/yoHcohn0SIvkXHXb+t0/thXyzmhwe 6 | URNkrFsJHJpZOoHfribp0eBz5GlRVTj/wG5u1q4o+RTtrRP4zZntEUc7YNg09y2Y 7 | yslJ10RNH/ZBZ/CqPwSihic5Fjw6lFXt0vDByQIDAQABAoIBAEBBP7lWj4gXYO8r 8 | /ygjTg+hqyQVAybBMn3hiRozzcG4as7+MHaDdJMTJ4LIMdCFYr3Ll5BwzkjzRPkP 9 | pBH/JXNnanDQ7bKhsxnR3DeQZru4ijTPISpwOQMr9q8FAIgDM2MzGp6gYCkqGaPn 10 | T5xEL6hMb+uDitNxoeo6Iv1/bWX2jGPhXhQ99q1HlaEP6MIcYxgoyYdPn6PMqzFc 11 | UFurfgkIc/ZcLIcLnaiQLd8izJaAEyBHzlRNKPjLvmrPmAXu2Wx5dN/LbPhlhKvP 12 | Uyy3FLC9FB7/JrPhCwLBkjZIJLQwjcdArgjBuBKou+rrFq4ScHT1ZkE6jIG1j4bC 13 | zJw3kg0CgYEA/C7AJfEt/xy9D1zelMfLUyErYEdrNwZTfbn6olsDOrqy7bnIUacP 14 | vPDxghdxFjO6Vwn+C+9u7pv+ZYs/vcA7AKi4vZHmlEKB5mk9Souoag42sMRU8oee 15 | 3QZu5YG9OqME4o1BsTWTd/1piGvRWv2yMeQ7DnQ0pG5JCfAravQzLjMCgYEAwC/i 16 | Zfyv+TYQ2CQSMW+nbZMvst/eSq7GkEI5MZHBWWOdS0KPISXJR4bh4MND4kXRB1U8 17 | qvg8NRVQaeZuGxvp/7/wX3OdVJpWN2ExgI/7NbAasfhVtUvg0yJNlKSELexGF6lM 18 | MAdK6wpVbV00ChGcKSetMbR11cirgfwgntlOXBMCgYAD3QN8kVFPEzWiQp32UYk/ 19 | 4MY1V2LBGQu2ZV79lb9a6vlKwwnalmCbWGWaxJkq6ef05WZK8IQWi7U+AQAc/BDr 20 | Vape3RlXNB2hVgNmXPVbipQz6Q4UGRfJ8wtH4NDxUQYGInvbJyL3N56iRGSUrEZK 21 | lWCCPC2CR14/FLrVjKAsGQKBgFugOLLg5tDMmvlBMT9KeSs/ifUg0C0Q6G/scrIU 22 | SHcii+JOqEhoaG7mepga4ClnmcGCPLtSP/oa4Eys5H+eLlCVJLeNOkwUjNg4SU5+ 23 | 1oiBHikNOWeG9ngSmaQtTytJwx0SxNT9FRW7xoCud9n5DVd9qA/Tdn5m/oWpO7Af 24 | 8EDrAoGADXAlHwJt+MRVl/vHAiCE9i8vLPq1OmxPy9G2JVKdwGUKJ7DoI65H+d72 25 | +6SLbeAWyEir2XzLBB9WiQfEIhySyGmc7bwdKD0ioCZQ5V7QggioXXnKzFqGHCh+ 26 | Y15ytKsd9GsUh8laQCAqeAEKcB0pCoPClPihwnIQz2vfXaVcyZA= 27 | -----END RSA PRIVATE KEY----- 28 | -----BEGIN CERTIFICATE----- 29 | MIIDIDCCAgigAwIBAgIJANInns7RVUYBMA0GCSqGSIb3DQEBBQUAMBQxEjAQBgNV 30 | BAMTCWxvY2FsaG9zdDAeFw0xNjAzMDgwNDE5MTRaFw0xNjA2MDYwNDE5MTRaMBQx 31 | EjAQBgNVBAMTCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC 32 | ggEBAL1SO7d42HNDlSGs3G0ulIanrOxmyqzNwOVPdafrMNnq/X7mnalhaks+iVPX 33 | Zvs8y3lSnhF5rCn1ImeoxeK8CBX4N8ypWlS5XV5xwftJz+pyQ0xs2qXFpE94lKRB 34 | mPU8W+raB2iqOw9kDx+I5ZTYvblPtDj80NulyIA2qO2rq60jv+tg7pT1bMbX7YTZ 35 | LaOLcUM1Apq0Gsvzf6OlCn1v8qB3KIZ9EiL5Fx12/rdP7YV8s5ocHlETZKxbCRya 36 | WTqB364m6dHgc+RpUVU4/8BubtauKPkU7a0T+M2Z7RFHO2DYNPctmMrJSddETR/2 37 | QWfwqj8EooYnORY8OpRV7dLwwckCAwEAAaN1MHMwHQYDVR0OBBYEFKL8OmX5WKWP 38 | rFXt0CxzfsE5HtAvMEQGA1UdIwQ9MDuAFKL8OmX5WKWPrFXt0CxzfsE5HtAvoRik 39 | FjAUMRIwEAYDVQQDEwlsb2NhbGhvc3SCCQDSJ57O0VVGATAMBgNVHRMEBTADAQH/ 40 | MA0GCSqGSIb3DQEBBQUAA4IBAQAJNXskDhaANlxjYyiPxNzQYALAwY+XpR1+pAUR 41 | fryMYii0ZHaxIzWxdR2hT02BkNpz0CXe7b89UL7ULL1P3tMVqW0SfkUiHhurllcL 42 | TiiOug5D9finvhl+6ijWHcFsdX4z0Q2OJHzCImakMdLTAOmWzeA6aSkNx7XWwIcE 43 | Jg+7ERb5iPMMTxitO541Yq0BGfMbdvni+Wb35dLh8kXVLt3YLn8QsI5xUIrFpMGl 44 | tPv/xfIW9NAvZa5gJTQnXSN1zz2oYut2q3o7SHNVLrFrW5L7Det6thrvrxg/JG5j 45 | QOxs5GCEvcQlOpwqxSrF22PCMRz4bt13cZYOAKlfhydfni9O 46 | -----END CERTIFICATE----- 47 | -----BEGIN RSA PRIVATE KEY----- 48 | MIIEpAIBAAKCAQEA7HvcqiyK43dUNG6yfKQgsPzzPXQBrcQbxLQgP0RDownZ7+EH 49 | QWU03nJYoSCpowGSCR9B7HcmokFfrEGpsjtQokYTiLyNqNEmL5rEuBtCIVai1H9X 50 | RAz7/HuQR9pq/IPHEL7FZCGYPwu+vCF0vc2lKmRFDyO1znggOvLDV5uz+VbD+zSf 51 | +KFmOs/TC4nrJvsxsSmLFjpRPbbiW7uaDSyV2y8YMWaKqT9aKkdi3mzKmAUUkIzh 52 | hb/2hbrFqzYbS3QjUkXE0VHKzr5tkEYNi+zqInNZ7nMqWUoc0sZkb3QcS9mXDktp 53 | 7+hvbuoj3gXOYcidNNXgq4cLjs2zknHjwJcXswIDAQABAoIBAQDRz37S2JwM/hUO 54 | Q9xFq1R50qJ4NlE6+w/r8CfgGc+sehQKuGq4gXNrnKW3pwP6ZG1NfJb/liMHt9qL 55 | OOK3B9sdbfDs0uUYDx/DApxf/4cnjvb+c1BYRvDWbEZxCX78NGpMGAWHNiseXGxH 56 | +6LYtvg/6SBAM12dJoTp5/KZ6sG9h6G6NTdthTO2C1qrl3glMGqVbE5ByCEKm8SF 57 | FOz4euRdQ/zpFcaJfYnuCgPiJj/6hSb6XZ+YJbpyH4LXIs0tyMA+BrVqFlqZ9CRN 58 | jIRfxHCvfqeK8w+HQGKs6rGWDGlH25Wnw8+Y9g9vIz0TZMUp5MW3yZTPKlbvg7nH 59 | 51TaWarpAoGBAPuXAAaxCiZsfQd9oOT9VPpo56rOZ4PlBgIaN63mmwhhDwvRq0Wr 60 | mHq9M5bTvGMqz6zzOuBkdwrYSfbloP7gJy9yVqAblerzc8sULtG0gIiM992svak8 61 | t38V28ylLvOAma5xUu4DjjezdBvBYXCRwryJ9LkRu8Bas5sxYYVSwD0/AoGBAPCh 62 | EwCwZXhnFiuGyTKOt5hBG5Gg+UiD+t5gaF++eD3g3fdVLM5JQHLquEo1D5d4hxC/ 63 | 609CKvdB6n8YnJ3K3QZfMCcq5J6DdQPJpGjQkIxPEA2GIUqPSaYsH+4zSRFMq430 64 | MBJyW2wdoaUOIrOzAATq5ivWXNKw7CM88We3R6SNAoGAQeYFXbtVm0bjYOjpEdHB 65 | Rm3f8H9r4cJFVpujZet3RDSYClJ6+B7AK7YPIzyGpbr69qQnEct+2tpYVVFEYD46 66 | RVU/l2RiWk42UEUTFl6fJCU4b1nlu8Rpk+IX1nyV1bYjiuun+yv3PmPEz9hRO3kS 67 | duvzq94XPYUEcmv2zUMlk2UCgYEAi7PolXDUKJgpDfI+I4UqYFIEkIMV150QbEUq 68 | s9OgecRw+iTQU5/BtDZD8oll2PoX6IiUNDrlORJi85E1dIMP2aAwE71aaBFIQX+z 69 | XUshcOSCHGfwMhqIltptW10ZIdsSiuLnef48x8NYUrynNw9IhpJeObtFvukK39aF 70 | DUMibA0CgYALFFkKCYTUpO3QDEXVoQ4fbFs8SEVwx0qjG+K2pUiOxOe4BZCzijdd 71 | zgZVEGoJjvbbsQhb/nqlc6O9x1uKNa1vNqPj/HV6VK+95bVAuvSn6v7RgNHvIdiK 72 | Gk/ZYhR3A5hCFwX4aG31i3hoLHjewzzD7HyMdvoTxuXJOAoaGimp1w== 73 | -----END RSA PRIVATE KEY----- 74 | -----BEGIN CERTIFICATE----- 75 | MIIDHTCCAgWgAwIBAgIJANBgK5/jOawGMA0GCSqGSIb3DQEBBQUAMBMxETAPBgNV 76 | BAMTCHRlc3QuY29tMB4XDTE2MDMwODA0MjU1N1oXDTE2MDYwNjA0MjU1N1owEzER 77 | MA8GA1UEAxMIdGVzdC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB 78 | AQDse9yqLIrjd1Q0brJ8pCCw/PM9dAGtxBvEtCA/REOjCdnv4QdBZTTeclihIKmj 79 | AZIJH0HsdyaiQV+sQamyO1CiRhOIvI2o0SYvmsS4G0IhVqLUf1dEDPv8e5BH2mr8 80 | g8cQvsVkIZg/C768IXS9zaUqZEUPI7XOeCA68sNXm7P5VsP7NJ/4oWY6z9MLiesm 81 | +zGxKYsWOlE9tuJbu5oNLJXbLxgxZoqpP1oqR2LebMqYBRSQjOGFv/aFusWrNhtL 82 | dCNSRcTRUcrOvm2QRg2L7Ooic1nucypZShzSxmRvdBxL2ZcOS2nv6G9u6iPeBc5h 83 | yJ001eCrhwuOzbOScePAlxezAgMBAAGjdDByMB0GA1UdDgQWBBRK9LQxgJjnhJ91 84 | PI0rdtCcviwqbjBDBgNVHSMEPDA6gBRK9LQxgJjnhJ91PI0rdtCcviwqbqEXpBUw 85 | EzERMA8GA1UEAxMIdGVzdC5jb22CCQDQYCuf4zmsBjAMBgNVHRMEBTADAQH/MA0G 86 | CSqGSIb3DQEBBQUAA4IBAQADWw7Zs4i2q4rL2TYwAp7iFixDRHWrTnxfarOOlcWX 87 | Mdk9cHSS7by9hvCyrIQ8sfidacV0Et5aRPaCF3LEC0LF1qn6OmCOz7NXo9F3YOy4 88 | WdN53k0YS1a/zhwAFZGeKrucimzL7ZiOT7IeQqd8Edu1Bk+iqLqkrnywH/dTMlas 89 | ZnIlkwXJhApD9yB5DrfJP11AfMIZpOF+O0dFe2gsLE5tv5I7ismjANZh/YOabAIy 90 | UBiJjHetsIteLEihvgIepfnF0vSHynmtpcYiGkR93IKHeAFI6FRY9KBhbv9SAYc4 91 | 7MpA3TebzocuWa/bEpC3/mRMCudfvU2lbPgMJoK1gw0U 92 | -----END CERTIFICATE----- 93 | -------------------------------------------------------------------------------- /test/cert/privkey.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEA7HvcqiyK43dUNG6yfKQgsPzzPXQBrcQbxLQgP0RDownZ7+EH 3 | QWU03nJYoSCpowGSCR9B7HcmokFfrEGpsjtQokYTiLyNqNEmL5rEuBtCIVai1H9X 4 | RAz7/HuQR9pq/IPHEL7FZCGYPwu+vCF0vc2lKmRFDyO1znggOvLDV5uz+VbD+zSf 5 | +KFmOs/TC4nrJvsxsSmLFjpRPbbiW7uaDSyV2y8YMWaKqT9aKkdi3mzKmAUUkIzh 6 | hb/2hbrFqzYbS3QjUkXE0VHKzr5tkEYNi+zqInNZ7nMqWUoc0sZkb3QcS9mXDktp 7 | 7+hvbuoj3gXOYcidNNXgq4cLjs2zknHjwJcXswIDAQABAoIBAQDRz37S2JwM/hUO 8 | Q9xFq1R50qJ4NlE6+w/r8CfgGc+sehQKuGq4gXNrnKW3pwP6ZG1NfJb/liMHt9qL 9 | OOK3B9sdbfDs0uUYDx/DApxf/4cnjvb+c1BYRvDWbEZxCX78NGpMGAWHNiseXGxH 10 | +6LYtvg/6SBAM12dJoTp5/KZ6sG9h6G6NTdthTO2C1qrl3glMGqVbE5ByCEKm8SF 11 | FOz4euRdQ/zpFcaJfYnuCgPiJj/6hSb6XZ+YJbpyH4LXIs0tyMA+BrVqFlqZ9CRN 12 | jIRfxHCvfqeK8w+HQGKs6rGWDGlH25Wnw8+Y9g9vIz0TZMUp5MW3yZTPKlbvg7nH 13 | 51TaWarpAoGBAPuXAAaxCiZsfQd9oOT9VPpo56rOZ4PlBgIaN63mmwhhDwvRq0Wr 14 | mHq9M5bTvGMqz6zzOuBkdwrYSfbloP7gJy9yVqAblerzc8sULtG0gIiM992svak8 15 | t38V28ylLvOAma5xUu4DjjezdBvBYXCRwryJ9LkRu8Bas5sxYYVSwD0/AoGBAPCh 16 | EwCwZXhnFiuGyTKOt5hBG5Gg+UiD+t5gaF++eD3g3fdVLM5JQHLquEo1D5d4hxC/ 17 | 609CKvdB6n8YnJ3K3QZfMCcq5J6DdQPJpGjQkIxPEA2GIUqPSaYsH+4zSRFMq430 18 | MBJyW2wdoaUOIrOzAATq5ivWXNKw7CM88We3R6SNAoGAQeYFXbtVm0bjYOjpEdHB 19 | Rm3f8H9r4cJFVpujZet3RDSYClJ6+B7AK7YPIzyGpbr69qQnEct+2tpYVVFEYD46 20 | RVU/l2RiWk42UEUTFl6fJCU4b1nlu8Rpk+IX1nyV1bYjiuun+yv3PmPEz9hRO3kS 21 | duvzq94XPYUEcmv2zUMlk2UCgYEAi7PolXDUKJgpDfI+I4UqYFIEkIMV150QbEUq 22 | s9OgecRw+iTQU5/BtDZD8oll2PoX6IiUNDrlORJi85E1dIMP2aAwE71aaBFIQX+z 23 | XUshcOSCHGfwMhqIltptW10ZIdsSiuLnef48x8NYUrynNw9IhpJeObtFvukK39aF 24 | DUMibA0CgYALFFkKCYTUpO3QDEXVoQ4fbFs8SEVwx0qjG+K2pUiOxOe4BZCzijdd 25 | zgZVEGoJjvbbsQhb/nqlc6O9x1uKNa1vNqPj/HV6VK+95bVAuvSn6v7RgNHvIdiK 26 | Gk/ZYhR3A5hCFwX4aG31i3hoLHjewzzD7HyMdvoTxuXJOAoaGimp1w== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /test/main.test.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | var path = require('path') 3 | var test = require('tape') 4 | var http = require('http') 5 | var https = require('https') 6 | var request = require('supertest') 7 | var createProxy = require('../') 8 | 9 | test('required fields', function (t) { 10 | t.plan(1) 11 | t.throws(createProxy.bind(null), TypeError, 'host resolver must be a function.') 12 | }) 13 | 14 | test('proxy http', function (t) { 15 | t.plan(3) 16 | 17 | // Start up an example http server. 18 | var server = http.createServer(function (req, res) { 19 | res.end('HTTP-Server') 20 | }).unref().listen() 21 | 22 | // Start the proxy. 23 | var proxy = createProxy(function (hostname) { 24 | // Only handle localhost 25 | if (hostname === 'localhost') { 26 | return server.address() 27 | } 28 | }).unref().listen() 29 | 30 | // Valid host proxy 31 | request('http://localhost:' + proxy.address().port) 32 | .get('/') 33 | .end(function (err, res) { 34 | if (err) t.fail(err) 35 | t.equals(res.text, 'HTTP-Server', 'server should respond') 36 | }) 37 | 38 | // Invalid host proxy. 39 | request('http://127.0.0.1:' + proxy.address().port) 40 | .get('/') 41 | .end(function (err, res) { 42 | t.ok(err, 'error should exist') 43 | t.equals(err.code, 'ECONNRESET', 'connection should fail') 44 | }) 45 | }) 46 | 47 | test('proxy https', function (t) { 48 | t.plan(3) 49 | 50 | // Allow self signed certs. 51 | process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0' 52 | // Start up an example http server. 53 | var options = { 54 | key: fs.readFileSync(path.join(__dirname, './cert/privkey.pem')), 55 | cert: fs.readFileSync(path.join(__dirname, './cert/cert.pem')) 56 | } 57 | 58 | var server = https.createServer(options, function (req, res) { 59 | res.end('HTTPS-Server') 60 | }).unref().listen() 61 | 62 | // Start the proxy. 63 | var proxy = createProxy(function (hostname) { 64 | // Only handle localhost 65 | if (hostname === 'localhost') { 66 | return server.address() 67 | } 68 | }).unref().listen() 69 | 70 | // Valid host proxy 71 | request('https://localhost:' + proxy.address().port) 72 | .get('/') 73 | .end(function (err, res) { 74 | if (err) t.fail(err) 75 | t.equals(res.text, 'HTTPS-Server', 'server should respond') 76 | }) 77 | 78 | // Invalid host proxy. 79 | request('https://127.0.0.1:' + proxy.address().port) 80 | .get('/') 81 | .end(function (err, res) { 82 | t.ok(err, 'error should exist') 83 | t.equals(err.code, 'ECONNRESET', 'connection should fail') 84 | }) 85 | }) 86 | --------------------------------------------------------------------------------