├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── examples ├── package.json ├── proxy.coffee ├── proxy.js ├── server.coffee ├── server.js ├── wsclient.coffee ├── wsclient.js ├── wshttp.coffee ├── wshttp.js ├── wsproxy.coffee ├── wsproxy.js ├── wsserver.coffee └── wsserver.js ├── index.coffee ├── index.js ├── package.json ├── src ├── bindings.coffee ├── bindings.js ├── certificate-store.coffee ├── certificate-store.js ├── dispatch-node.coffee ├── dispatch-node.js ├── load-balancer.coffee ├── load-balancer.js ├── redwire.coffee ├── redwire.js ├── tcp-proxy.coffee ├── tcp-proxy.js ├── testhttp.coffee ├── testhttp.js ├── use-node.coffee ├── use-node.js ├── web-proxy.coffee └── web-proxy.js └── test ├── cors.coffee ├── cors.js ├── displatch-node.coffee ├── displatch-node.js ├── load-balancer.coffee ├── load-balancer.js ├── redwire.coffee ├── redwire.js ├── set-host.coffee ├── set-host.js ├── use-node.coffee ├── use-node.js ├── wildcard-match.coffee └── wildcard-match.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Commenting this out is preferred by some people, see 24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 25 | node_modules 26 | 27 | # Users Environment Variables 28 | .lock-wscript 29 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - '0.10' -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 MetOcean Solutions Ltd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RedWire Reverse Proxy 2 | 3 | A dynamic, high performance, load balancing reverse proxy. 4 | 5 | A reverse proxy accepts web requests and dispatches these out to other web servers based on the host header. This is identical to Apache's mod_proxy and nginx's proxy_pass. 6 | 7 | RedWire can replace nginx in many situations. 8 | 9 | RedWire supports: 10 | - HTTP 11 | - HTTPS / SSL through SNI (sorry IE6) 12 | - websockets over http and https 13 | - changing configuration at runtime 14 | - custom functions (middleware) 15 | - Node.js v0.12 / iojs 16 | 17 | [![BuildStatus](https://secure.travis-ci.org/metocean/redwire.png?branch=master)](http://travis-ci.org/metocean/redwire) 18 | [![NPM version](https://badge.fury.io/js/redwire.svg)](http://badge.fury.io/js/redwire) 19 | 20 | ## Install 21 | 22 | ```sh 23 | npm install redwire 24 | ``` 25 | 26 | RedWire only depends on the excellent [node-http-proxy](https://github.com/nodejitsu/node-http-proxy) and has been heavily inspired by the fantastic [redbird reverse proxy](https://github.com/OptimalBits/redbird). 27 | 28 | ## Examples 29 | 30 | ```js 31 | var RedWire = require('redwire'); 32 | var redwire = new RedWire({ 33 | // more configuration is available, see below 34 | http: { port: 80 } 35 | }); 36 | 37 | 38 | // proxy requests for example.com to port 3000 locally 39 | redwire.http('http://example.com').use(redwire.proxy('http://127.0.0.1:3000')); 40 | // a shorthand form is available 41 | redwire.http('example.com', '127.0.0.1:3000'); 42 | 43 | 44 | // proxy to another server with a different host header 45 | // paths can be used as part of the proxy, longer paths are matched first 46 | redwire.http('example.com/api') 47 | .use(redwire.setHost('testapi.com')) 48 | .use(redwire.proxy('testapi.com')); 49 | 50 | 51 | // paths can also be used on the destination 52 | redwire.http('example.com/awesomeimage.png', 'test.com/wp-upload/long/path/IMG0234.png'); 53 | 54 | 55 | // some helpful middleware has been provided 56 | redwire.http('example.com/expired', redwire.error404()); 57 | redwire.http('example.com/old', redwire.redirect301('example.com/new')); 58 | redwire.http('example.com', redwire.sslRedirect()); 59 | 60 | 61 | // you can write your own middleware 62 | redwire.http('example.com/api') 63 | .use(function (mount, url, req, res, next) { 64 | // mount is 'example.com/api' in this example 65 | // url is the requested url - something like 'example.com/api/v0/user' 66 | // req and res are generic NodeJs http request and response objects 67 | // next is a function to call if you want to continue the chain 68 | next() 69 | }) 70 | .use(redwire.proxy('testapi.com')); 71 | 72 | 73 | // a single middleware can be used for an entire domain 74 | example = redwire.http('example.com') 75 | .use(function (mount, url, req, res, next) { 76 | // example logging middleware 77 | // authentication is possible too 78 | console.log(url); 79 | next() 80 | }); 81 | // additional 'matches' can be registered 82 | example.match('example.com/blog').use(redwire.proxy('example.wordpress.com')); 83 | example.match('example.com/api').use(redwire.proxy('testapi.com')); 84 | 85 | 86 | // balance load across several servers 87 | load = redwire.loadBalancer() 88 | .add('localhost:6000') 89 | .add('localhost:6001') 90 | .add('localhost:6002'); 91 | redwire.http('example.com') 92 | .use(load.distribute()) 93 | .use(redwire.proxy()); 94 | // adjust servers at runtime 95 | load.remove('localhost:6000'); 96 | ``` 97 | 98 | ## Configuration 99 | 100 | ```js 101 | // defaults are shown 102 | var RedWire = require('redwire'); 103 | var options = { 104 | http: { 105 | port: 8080, 106 | websockets: no 107 | }, 108 | https: no, 109 | proxy: { 110 | xfwd: yes, 111 | prependPath: no, 112 | keepAlive: no 113 | }, 114 | log: { 115 | debug: function() {}, 116 | notice: function() {}, 117 | error: function(err) { 118 | if (err.stack) { 119 | console.error(err.stack); 120 | } else { 121 | console.error(err); 122 | } 123 | } 124 | } 125 | }; 126 | var redwire = new RedWire(options); 127 | ``` 128 | 129 | ### HTTP Configuration 130 | 131 | When websockets are enabled use `redwire.httpWs` to setup routes and middleware for websocket requests. 132 | RedWire is often most useful handing request on port 80. If this is required your NodeJs application will need to run as root. 133 | 134 | ### HTTPS Configuration 135 | 136 | SNI (multiple ssl certificates) is supported by Internet Explorer 7 and above. 137 | 138 | ```js 139 | // a default certificate is required 140 | var RedWire = require('redwire'); 141 | var options = { 142 | https: { 143 | port: 443, 144 | key: 'path/to/key.pem', 145 | cert: 'path/to/cert.pem', 146 | ca: 'path/to/ca.pem (optional)' 147 | } 148 | }; 149 | var redwire = new RedWire(options); 150 | 151 | // additional certificates can be added per host (optional) 152 | redwire.certificates.add('example.com', { 153 | key: 'path/to/key.pem', 154 | cert: 'path/to/cert.pem', 155 | ca: 'path/to/ca.pem (optional)' 156 | }); 157 | 158 | redwire.https('example.com', 'localhost:3000'); 159 | ``` 160 | 161 | ### Proxy Configuration 162 | 163 | ```js 164 | var RedWire = require('redwire'); 165 | var options = { 166 | proxy: { 167 | prependPath: no, 168 | xfwd: yes 169 | } 170 | }; 171 | var redwire = new RedWire(options); 172 | ``` 173 | 174 | The whole options.proxy structure is passed to node-http-proxy so any options can be passed through. This for experts as changing things may interfere with RedWire's operation. [A list of node-http-proxy options is available](https://github.com/nodejitsu/node-http-proxy#options). 175 | 176 | ## Use Cases 177 | 178 | RedWire is a perfect replacement for nginx to dispatch requests to NodeJs servers. Here are other use cases: 179 | 180 | - **HTTP/SSL server** - use RedWire to expose insecure websites and APIs secure on the internet. 181 | - **Authentication** - add authentication checks in front of your generated APIs. 182 | - **Logging and Diagnostics** - all requests can be logged with simple middleware, use RedWire to diagnose web issues. 183 | - **API Aggregation** - pull together many different APIs at different URLs into one uniform URL for your users. e.g. api.company.com or company.com/api. 184 | - **Dynamic Load Balancing** - add and remove servers from your pool at runtime. Hook up etcd or consul and load balance automatically. 185 | 186 | At MetOcean we're using RedWire in all these situations. 187 | 188 | Are you using RedWire for something that isn't on this list? Let us know! 189 | 190 | 191 | ## TODO 192 | 193 | - Tests for https 194 | - Tests for ws 195 | - Tests for removing dispatch nodes 196 | -------------------------------------------------------------------------------- /examples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "examples", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "proxy.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "node server.js" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "ws": "0.8.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/proxy.coffee: -------------------------------------------------------------------------------- 1 | Redwire = require '../' 2 | 3 | options = 4 | http: 5 | port: 8081 6 | http2: 7 | port: 8082 8 | key: '/Users/tcoats/MetOcean/tugboat/harmony/metoceanview.com.key' 9 | cert: '/Users/tcoats/MetOcean/tugboat/harmony/metoceanview.com.crt' 10 | redwire = new Redwire options 11 | redwire 12 | .http 'localhost:8081' 13 | .use redwire.proxy 'http://localhost:8080' 14 | redwire 15 | .http2 'localhost:8082' 16 | .use redwire.proxy 'http://localhost:8080' -------------------------------------------------------------------------------- /examples/proxy.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.9.2 2 | var Redwire, options, redwire; 3 | 4 | Redwire = require('../'); 5 | 6 | options = { 7 | http: { 8 | port: 8081 9 | }, 10 | http2: { 11 | port: 8082, 12 | key: '/Users/tcoats/MetOcean/tugboat/harmony/metoceanview.com.key', 13 | cert: '/Users/tcoats/MetOcean/tugboat/harmony/metoceanview.com.crt' 14 | } 15 | }; 16 | 17 | redwire = new Redwire(options); 18 | 19 | redwire.http('localhost:8081').use(redwire.proxy('http://localhost:8080')); 20 | 21 | redwire.http2('localhost:8082').use(redwire.proxy('http://localhost:8080')); 22 | -------------------------------------------------------------------------------- /examples/server.coffee: -------------------------------------------------------------------------------- 1 | fs = require 'fs' 2 | http = require 'http' 3 | 4 | #http.globalAgent.keepAlive = yes 5 | 6 | finalhandler = require 'finalhandler' 7 | serveStatic = require 'serve-static' 8 | 9 | serve = serveStatic '/Users/tcoats/Open/dve/examples' 10 | 11 | http 12 | .createServer (req, res) -> 13 | serve req, res, finalhandler req, res 14 | .listen 8080 -------------------------------------------------------------------------------- /examples/server.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.9.2 2 | var finalhandler, fs, http, serve, serveStatic; 3 | 4 | fs = require('fs'); 5 | 6 | http = require('http'); 7 | 8 | finalhandler = require('finalhandler'); 9 | 10 | serveStatic = require('serve-static'); 11 | 12 | serve = serveStatic('/Users/tcoats/Open/dve/examples'); 13 | 14 | http.createServer(function(req, res) { 15 | return serve(req, res, finalhandler(req, res)); 16 | }).listen(8080); 17 | -------------------------------------------------------------------------------- /examples/wsclient.coffee: -------------------------------------------------------------------------------- 1 | WebSocket = require 'ws' 2 | ws = new WebSocket 'ws://localhost:8081' 3 | 4 | ws.on 'open', -> 5 | console.log 'open' 6 | #ws.send 'REQUEST' 7 | 8 | ws.on 'message', (data, flags) -> 9 | console.log data 10 | #ws.send 'REQUEST' 11 | 12 | ws.on 'close', -> 13 | console.log 'close' -------------------------------------------------------------------------------- /examples/wsclient.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.9.2 2 | var WebSocket, ws; 3 | 4 | WebSocket = require('ws'); 5 | 6 | ws = new WebSocket('ws://localhost:8081'); 7 | 8 | ws.on('open', function() { 9 | return console.log('open'); 10 | }); 11 | 12 | ws.on('message', function(data, flags) { 13 | return console.log(data); 14 | }); 15 | 16 | ws.on('close', function() { 17 | return console.log('close'); 18 | }); 19 | -------------------------------------------------------------------------------- /examples/wshttp.coffee: -------------------------------------------------------------------------------- 1 | os = require 'os' 2 | http = require 'http' 3 | 4 | port = 8082 5 | server = http.createServer (req, res) -> 6 | process.stdout.write '.' 7 | result = 8 | server: os.hostname() 9 | port: port 10 | host: req.headers['host'] 11 | method: req.method 12 | url: req.url 13 | res.end JSON.stringify result, null, 2 14 | 15 | server.listen port, -> 16 | console.log "Home grown http server running on port #{port}" -------------------------------------------------------------------------------- /examples/wshttp.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.9.2 2 | var http, os, port, server; 3 | 4 | os = require('os'); 5 | 6 | http = require('http'); 7 | 8 | port = 8082; 9 | 10 | server = http.createServer(function(req, res) { 11 | var result; 12 | process.stdout.write('.'); 13 | result = { 14 | server: os.hostname(), 15 | port: port, 16 | host: req.headers['host'], 17 | method: req.method, 18 | url: req.url 19 | }; 20 | return res.end(JSON.stringify(result, null, 2)); 21 | }); 22 | 23 | server.listen(port, function() { 24 | return console.log("Home grown http server running on port " + port); 25 | }); 26 | -------------------------------------------------------------------------------- /examples/wsproxy.coffee: -------------------------------------------------------------------------------- 1 | Redwire = require '../' 2 | 3 | options = 4 | http: 5 | port: 8081 6 | websockets: yes 7 | redwire = new Redwire options 8 | redwire 9 | .httpWs 'localhost:8081' 10 | .use redwire.proxyWs 'http://localhost:8080' 11 | redwire 12 | .http 'localhost:8081' 13 | .use redwire.proxy 'http://localhost:8082' -------------------------------------------------------------------------------- /examples/wsproxy.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.9.2 2 | var Redwire, options, redwire; 3 | 4 | Redwire = require('../'); 5 | 6 | options = { 7 | http: { 8 | port: 8081, 9 | websockets: true 10 | } 11 | }; 12 | 13 | redwire = new Redwire(options); 14 | 15 | redwire.httpWs('localhost:8081').use(redwire.proxyWs('http://localhost:8080')); 16 | 17 | redwire.http('localhost:8081').use(redwire.proxy('http://localhost:8082')); 18 | -------------------------------------------------------------------------------- /examples/wsserver.coffee: -------------------------------------------------------------------------------- 1 | WebSocketServer = require('ws').Server 2 | wss = new WebSocketServer port: 8080 3 | 4 | wss.on 'connection', (ws) -> 5 | ws.on 'message', (message) -> 6 | console.log message 7 | 8 | handle = setInterval -> 9 | ws.send 'something' 10 | , 1000 11 | ws.on 'close', -> 12 | clearInterval handle -------------------------------------------------------------------------------- /examples/wsserver.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.9.2 2 | var WebSocketServer, wss; 3 | 4 | WebSocketServer = require('ws').Server; 5 | 6 | wss = new WebSocketServer({ 7 | port: 8080 8 | }); 9 | 10 | wss.on('connection', function(ws) { 11 | var handle; 12 | ws.on('message', function(message) { 13 | return console.log(message); 14 | }); 15 | handle = setInterval(function() { 16 | return ws.send('something'); 17 | }, 1000); 18 | return ws.on('close', function() { 19 | return clearInterval(handle); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /index.coffee: -------------------------------------------------------------------------------- 1 | module.exports = require './src/redwire' -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.8.0 2 | module.exports = require('./src/redwire'); 3 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redwire", 3 | "version": "2.2.0", 4 | "description": "A dynamic, high performance, load balancing reverse proxy.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha test/*.js --reporter spec" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git://github.com/metocean/redwire.git" 12 | }, 13 | "keywords": [ 14 | "reverse-proxy", 15 | "proxy", 16 | "load-balancer" 17 | ], 18 | "author": "Thomas Coats", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/metocean/redwire/issues" 22 | }, 23 | "homepage": "https://github.com/metocean/redwire", 24 | "dependencies": { 25 | "http-proxy": "1.11.2", 26 | "http2": "3.2.0" 27 | }, 28 | "devDependencies": { 29 | "chai": "1.*", 30 | "mocha": "2.*" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/bindings.coffee: -------------------------------------------------------------------------------- 1 | parse_url = require('url').parse 2 | 3 | DispatchNode = require './dispatch-node' 4 | UseNode = require './use-node' 5 | 6 | module.exports = class Bindings 7 | constructor: (redwire) -> 8 | @_redwire = redwire 9 | @_http = new DispatchNode() 10 | @_https = new DispatchNode() 11 | @_http2 = new DispatchNode() 12 | @_httpWs = new DispatchNode() 13 | @_httpsWs = new DispatchNode() 14 | @_tcp = new UseNode() 15 | @_tls = new UseNode() 16 | 17 | http: (url, target) => 18 | url = "http://#{url}" if url isnt '*' and url.indexOf('http://') isnt 0 19 | result = @_http.match url 20 | return result if !target? 21 | 22 | return result.use @_redwire.proxy target if typeof target is 'string' 23 | return result.use target if typeof target is 'function' 24 | 25 | throw Error 'target not a known type' 26 | 27 | https: (url, target) => 28 | url = "https://#{url}" if url isnt '*' and url.indexOf('https://') isnt 0 29 | result = @_https.match url 30 | return result if !target? 31 | 32 | return result.use @_redwire.proxy target if typeof target is 'string' 33 | return result.use target if typeof target is 'function' 34 | 35 | throw Error 'target not a known type' 36 | 37 | http2: (url, target) => 38 | url = "https://#{url}" if url isnt '*' and url.indexOf('https://') isnt 0 39 | result = @_http2.match url 40 | return result if !target? 41 | 42 | return result.use @_redwire.proxy target if typeof target is 'string' 43 | return result.use target if typeof target is 'function' 44 | 45 | throw Error 'target not a known type' 46 | 47 | httpWs: (url, target) => 48 | url = "http://#{url}" if url isnt '*' and url.indexOf('http://') isnt 0 49 | result = @_httpWs.match url 50 | return result if !target? 51 | 52 | return result.use @_redwire.proxyWs target if typeof target is 'string' 53 | return result.use target if typeof target is 'function' 54 | 55 | throw Error 'target not a known type' 56 | 57 | httpsWs: (url, target) => 58 | url = "https://#{url}" if url isnt '*' and url.indexOf('https://') isnt 0 59 | result = @_httpsWs.match url 60 | return result if !target? 61 | 62 | return result.use @_redwire.proxyWs target if typeof target is 'string' 63 | return result.use target if typeof target is 'function' 64 | 65 | throw Error 'target not a known type' 66 | 67 | tcp: (target) => 68 | return @_tcp if !target? 69 | return @_tcp.use @_redwire.proxyTcp target if typeof target is 'string' 70 | return @_tcp.use target if typeof target is 'function' 71 | 72 | throw Error 'target not a known type' 73 | 74 | tls: (options, target) => 75 | throw Error 'target not defined' if !target? 76 | return @_tls.use @_redwire.proxyTls target if typeof target is 'string' 77 | return @_tls.use target if typeof target is 'function' 78 | 79 | throw Error 'target not a known type' 80 | 81 | removeHttp: (url) => @_http.remove url 82 | removeHttps: (url) => @_https.remove url 83 | removeHttp2: (url) => @_http2.remove url 84 | removeHttpWs: (url) => @_httpWs.remove url 85 | removeHttpsWs: (url) => @_httpsWs.remove url 86 | 87 | clearHttp: => @_http.clear() 88 | clearHttps: => @_https.clear() 89 | clearHttp2: => @_http2.clear() 90 | clearHttpWs: => @_httpWs.clear() 91 | clearHttpsWs: => @_httpsWs.clear() 92 | clearTcp: => @_tcp.clear() 93 | clearTls: => @_tls.clear() 94 | 95 | clear: => 96 | @clearHttp() 97 | @clearHttps() 98 | @clearHttp2() 99 | @clearHttpWs() 100 | @clearHttpsWs() 101 | @clearTcp() 102 | @clearTls() -------------------------------------------------------------------------------- /src/bindings.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.9.2 2 | var Bindings, DispatchNode, UseNode, parse_url, 3 | bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; 4 | 5 | parse_url = require('url').parse; 6 | 7 | DispatchNode = require('./dispatch-node'); 8 | 9 | UseNode = require('./use-node'); 10 | 11 | module.exports = Bindings = (function() { 12 | function Bindings(redwire) { 13 | this.clear = bind(this.clear, this); 14 | this.clearTls = bind(this.clearTls, this); 15 | this.clearTcp = bind(this.clearTcp, this); 16 | this.clearHttpsWs = bind(this.clearHttpsWs, this); 17 | this.clearHttpWs = bind(this.clearHttpWs, this); 18 | this.clearHttp2 = bind(this.clearHttp2, this); 19 | this.clearHttps = bind(this.clearHttps, this); 20 | this.clearHttp = bind(this.clearHttp, this); 21 | this.removeHttpsWs = bind(this.removeHttpsWs, this); 22 | this.removeHttpWs = bind(this.removeHttpWs, this); 23 | this.removeHttp2 = bind(this.removeHttp2, this); 24 | this.removeHttps = bind(this.removeHttps, this); 25 | this.removeHttp = bind(this.removeHttp, this); 26 | this.tls = bind(this.tls, this); 27 | this.tcp = bind(this.tcp, this); 28 | this.httpsWs = bind(this.httpsWs, this); 29 | this.httpWs = bind(this.httpWs, this); 30 | this.http2 = bind(this.http2, this); 31 | this.https = bind(this.https, this); 32 | this.http = bind(this.http, this); 33 | this._redwire = redwire; 34 | this._http = new DispatchNode(); 35 | this._https = new DispatchNode(); 36 | this._http2 = new DispatchNode(); 37 | this._httpWs = new DispatchNode(); 38 | this._httpsWs = new DispatchNode(); 39 | this._tcp = new UseNode(); 40 | this._tls = new UseNode(); 41 | } 42 | 43 | Bindings.prototype.http = function(url, target) { 44 | var result; 45 | if (url !== '*' && url.indexOf('http://') !== 0) { 46 | url = "http://" + url; 47 | } 48 | result = this._http.match(url); 49 | if (target == null) { 50 | return result; 51 | } 52 | if (typeof target === 'string') { 53 | return result.use(this._redwire.proxy(target)); 54 | } 55 | if (typeof target === 'function') { 56 | return result.use(target); 57 | } 58 | throw Error('target not a known type'); 59 | }; 60 | 61 | Bindings.prototype.https = function(url, target) { 62 | var result; 63 | if (url !== '*' && url.indexOf('https://') !== 0) { 64 | url = "https://" + url; 65 | } 66 | result = this._https.match(url); 67 | if (target == null) { 68 | return result; 69 | } 70 | if (typeof target === 'string') { 71 | return result.use(this._redwire.proxy(target)); 72 | } 73 | if (typeof target === 'function') { 74 | return result.use(target); 75 | } 76 | throw Error('target not a known type'); 77 | }; 78 | 79 | Bindings.prototype.http2 = function(url, target) { 80 | var result; 81 | if (url !== '*' && url.indexOf('https://') !== 0) { 82 | url = "https://" + url; 83 | } 84 | result = this._http2.match(url); 85 | if (target == null) { 86 | return result; 87 | } 88 | if (typeof target === 'string') { 89 | return result.use(this._redwire.proxy(target)); 90 | } 91 | if (typeof target === 'function') { 92 | return result.use(target); 93 | } 94 | throw Error('target not a known type'); 95 | }; 96 | 97 | Bindings.prototype.httpWs = function(url, target) { 98 | var result; 99 | if (url !== '*' && url.indexOf('http://') !== 0) { 100 | url = "http://" + url; 101 | } 102 | result = this._httpWs.match(url); 103 | if (target == null) { 104 | return result; 105 | } 106 | if (typeof target === 'string') { 107 | return result.use(this._redwire.proxyWs(target)); 108 | } 109 | if (typeof target === 'function') { 110 | return result.use(target); 111 | } 112 | throw Error('target not a known type'); 113 | }; 114 | 115 | Bindings.prototype.httpsWs = function(url, target) { 116 | var result; 117 | if (url !== '*' && url.indexOf('https://') !== 0) { 118 | url = "https://" + url; 119 | } 120 | result = this._httpsWs.match(url); 121 | if (target == null) { 122 | return result; 123 | } 124 | if (typeof target === 'string') { 125 | return result.use(this._redwire.proxyWs(target)); 126 | } 127 | if (typeof target === 'function') { 128 | return result.use(target); 129 | } 130 | throw Error('target not a known type'); 131 | }; 132 | 133 | Bindings.prototype.tcp = function(target) { 134 | if (target == null) { 135 | return this._tcp; 136 | } 137 | if (typeof target === 'string') { 138 | return this._tcp.use(this._redwire.proxyTcp(target)); 139 | } 140 | if (typeof target === 'function') { 141 | return this._tcp.use(target); 142 | } 143 | throw Error('target not a known type'); 144 | }; 145 | 146 | Bindings.prototype.tls = function(options, target) { 147 | if (target == null) { 148 | throw Error('target not defined'); 149 | } 150 | if (typeof target === 'string') { 151 | return this._tls.use(this._redwire.proxyTls(target)); 152 | } 153 | if (typeof target === 'function') { 154 | return this._tls.use(target); 155 | } 156 | throw Error('target not a known type'); 157 | }; 158 | 159 | Bindings.prototype.removeHttp = function(url) { 160 | return this._http.remove(url); 161 | }; 162 | 163 | Bindings.prototype.removeHttps = function(url) { 164 | return this._https.remove(url); 165 | }; 166 | 167 | Bindings.prototype.removeHttp2 = function(url) { 168 | return this._http2.remove(url); 169 | }; 170 | 171 | Bindings.prototype.removeHttpWs = function(url) { 172 | return this._httpWs.remove(url); 173 | }; 174 | 175 | Bindings.prototype.removeHttpsWs = function(url) { 176 | return this._httpsWs.remove(url); 177 | }; 178 | 179 | Bindings.prototype.clearHttp = function() { 180 | return this._http.clear(); 181 | }; 182 | 183 | Bindings.prototype.clearHttps = function() { 184 | return this._https.clear(); 185 | }; 186 | 187 | Bindings.prototype.clearHttp2 = function() { 188 | return this._http2.clear(); 189 | }; 190 | 191 | Bindings.prototype.clearHttpWs = function() { 192 | return this._httpWs.clear(); 193 | }; 194 | 195 | Bindings.prototype.clearHttpsWs = function() { 196 | return this._httpsWs.clear(); 197 | }; 198 | 199 | Bindings.prototype.clearTcp = function() { 200 | return this._tcp.clear(); 201 | }; 202 | 203 | Bindings.prototype.clearTls = function() { 204 | return this._tls.clear(); 205 | }; 206 | 207 | Bindings.prototype.clear = function() { 208 | this.clearHttp(); 209 | this.clearHttps(); 210 | this.clearHttp2(); 211 | this.clearHttpWs(); 212 | this.clearHttpsWs(); 213 | this.clearTcp(); 214 | return this.clearTls(); 215 | }; 216 | 217 | return Bindings; 218 | 219 | })(); 220 | -------------------------------------------------------------------------------- /src/certificate-store.coffee: -------------------------------------------------------------------------------- 1 | fs = require 'fs' 2 | tls = require 'tls' 3 | crypto = require 'crypto' 4 | 5 | module.exports = class CertificateStore 6 | constructor: -> 7 | @_certs = {} 8 | @_secureContexts = {} 9 | 10 | add: (hostname, options) => 11 | scOpts = 12 | key: @_getCertData options.key 13 | cert: @_getCertData options.cert 14 | scOpts.ca = @_getCertBundleData options.ca if options.ca 15 | @_secureContexts[hostname] = tls.createSecureContext scOpts 16 | 17 | isAvailable: (hostname) => @_secureContexts[hostname]? 18 | 19 | getHttpsOptions: (options) => 20 | result = 21 | SNICallback: (hostname, callback) => callback(null, @_secureContexts[hostname]) 22 | key: @_getCertData options.key 23 | cert: @_getCertData options.cert 24 | result.ca = [@_getCertData options.ca] if options.ca 25 | result 26 | 27 | getTlsOptions: (options) => 28 | result = 29 | key: @_getCertData options.key 30 | cert: @_getCertData options.cert 31 | result.ca = [@_getCertData options.ca] if options.ca 32 | result 33 | 34 | _getCertBundleData: (pathname) => 35 | ca = [] 36 | chain = fs.readFileSync pathname, 'utf8' 37 | chain = chain.split '\n' 38 | cert = [] 39 | for line in chain 40 | if line.length == 0 41 | continue 42 | cert.push line 43 | if line.match /-END CERTIFICATE-/ 44 | ca.push cert.join('\n') 45 | cert = [] 46 | ca 47 | 48 | _getCertData: (pathname) => 49 | if pathname 50 | if pathname instanceof Array 51 | for path in pathname 52 | @_getCertData path 53 | else if fs.existsSync pathname 54 | fs.readFileSync pathname, 'utf8' 55 | -------------------------------------------------------------------------------- /src/certificate-store.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.4.0 2 | (function() { 3 | var CertificateStore, crypto, fs, tls, 4 | __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; 5 | 6 | fs = require('fs'); 7 | 8 | tls = require('tls'); 9 | 10 | crypto = require('crypto'); 11 | 12 | module.exports = CertificateStore = (function() { 13 | 14 | function CertificateStore() { 15 | this._getCertData = __bind(this._getCertData, this); 16 | 17 | this._getCertBundleData = __bind(this._getCertBundleData, this); 18 | 19 | this.getTlsOptions = __bind(this.getTlsOptions, this); 20 | 21 | this.getHttpsOptions = __bind(this.getHttpsOptions, this); 22 | 23 | this.isAvailable = __bind(this.isAvailable, this); 24 | 25 | this.add = __bind(this.add, this); 26 | this._certs = {}; 27 | this._secureContexts = {}; 28 | } 29 | 30 | CertificateStore.prototype.add = function(hostname, options) { 31 | var scOpts; 32 | scOpts = { 33 | key: this._getCertData(options.key), 34 | cert: this._getCertData(options.cert) 35 | }; 36 | if (options.ca) { 37 | scOpts.ca = this._getCertBundleData(options.ca); 38 | } 39 | return this._secureContexts[hostname] = tls.createSecureContext(scOpts); 40 | }; 41 | 42 | CertificateStore.prototype.isAvailable = function(hostname) { 43 | return this._secureContexts[hostname] != null; 44 | }; 45 | 46 | CertificateStore.prototype.getHttpsOptions = function(options) { 47 | var result, 48 | _this = this; 49 | result = { 50 | SNICallback: function(hostname, callback) { 51 | return callback(null, _this._secureContexts[hostname]); 52 | }, 53 | key: this._getCertData(options.key), 54 | cert: this._getCertData(options.cert) 55 | }; 56 | if (options.ca) { 57 | result.ca = [this._getCertData(options.ca)]; 58 | } 59 | return result; 60 | }; 61 | 62 | CertificateStore.prototype.getTlsOptions = function(options) { 63 | var result; 64 | result = { 65 | key: this._getCertData(options.key), 66 | cert: this._getCertData(options.cert) 67 | }; 68 | if (options.ca) { 69 | result.ca = [this._getCertData(options.ca)]; 70 | } 71 | return result; 72 | }; 73 | 74 | CertificateStore.prototype._getCertBundleData = function(pathname) { 75 | var ca, cert, chain, line, _i, _len; 76 | ca = []; 77 | chain = fs.readFileSync(pathname, 'utf8'); 78 | chain = chain.split('\n'); 79 | cert = []; 80 | for (_i = 0, _len = chain.length; _i < _len; _i++) { 81 | line = chain[_i]; 82 | if (line.length === 0) { 83 | continue; 84 | } 85 | cert.push(line); 86 | if (line.match(/-END CERTIFICATE-/)) { 87 | ca.push(cert.join('\n')); 88 | cert = []; 89 | } 90 | } 91 | return ca; 92 | }; 93 | 94 | CertificateStore.prototype._getCertData = function(pathname) { 95 | var path, _i, _len, _results; 96 | if (pathname) { 97 | if (pathname instanceof Array) { 98 | _results = []; 99 | for (_i = 0, _len = pathname.length; _i < _len; _i++) { 100 | path = pathname[_i]; 101 | _results.push(this._getCertData(path)); 102 | } 103 | return _results; 104 | } else if (fs.existsSync(pathname)) { 105 | return fs.readFileSync(pathname, 'utf8'); 106 | } 107 | } 108 | }; 109 | 110 | return CertificateStore; 111 | 112 | })(); 113 | 114 | }).call(this); 115 | -------------------------------------------------------------------------------- /src/dispatch-node.coffee: -------------------------------------------------------------------------------- 1 | # Recursively dispatch a url based matching execution 2 | # Provides next methods to each handler that require no arguments 3 | module.exports = class DispatchNode 4 | constructor: (url, createNode) -> 5 | if !createNode? 6 | createNode = (url, createNode) -> new DispatchNode url, createNode 7 | @_createNode = createNode 8 | 9 | @_url = url 10 | @_handlers = [] 11 | @_listeners = [] 12 | @_wildcard = null 13 | 14 | _find: (url) => 15 | for listener in @_listeners 16 | return listener if listener.url is url 17 | null 18 | 19 | match: (url) => 20 | if url is '*' 21 | if !@_wildcard? 22 | @_wildcard = @_createNode '*' 23 | return @_wildcard 24 | 25 | listener = @_find url 26 | if !listener? 27 | listener = url: url, node: @_createNode url 28 | @_listeners.push listener 29 | # listeners are dispatched most specific to least specific 30 | @_listeners.sort (a, b) -> b.url.length - a.url.length 31 | listener.node 32 | 33 | remove: (url) => 34 | listener = @_find url 35 | if listener? 36 | index = @_listeners.indexOf listener 37 | @_listeners.splice index, 1 38 | @ 39 | 40 | clear: => 41 | @_handlers = [] 42 | @_listeners = [] 43 | @_wildcard = null 44 | 45 | use: (handler) => 46 | if Array.isArray handler 47 | @use h for h in handler 48 | else 49 | @_handlers.push handler 50 | @ 51 | 52 | _dispatch: (items, args, next, method) -> 53 | # copy so we aren't confused by asyc changes 54 | items = items[..] 55 | index = 0 56 | exec = -> 57 | return next args... if index >= items.length 58 | item = items[index] 59 | index++ 60 | method item, args, exec 61 | exec() 62 | 63 | _dispatchHandlers: (url, args..., next) => 64 | @_dispatch @_handlers, args, next, (item, args, next) => 65 | item @_url, url, args..., next 66 | 67 | _dispatchListeners: (url, args..., next) => 68 | @_dispatch @_listeners, args, next, (item, args, next) -> 69 | # Match against the url 70 | return next() unless url.indexOf(item.url) is 0 71 | item.node.exec url, args..., next 72 | 73 | exec: (url, args..., next) => 74 | @_dispatchHandlers url, args..., => 75 | @_dispatchListeners url, args..., => 76 | return next args... if !@_wildcard? 77 | @_wildcard.exec url, args..., next -------------------------------------------------------------------------------- /src/dispatch-node.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.9.2 2 | var DispatchNode, 3 | bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, 4 | slice = [].slice; 5 | 6 | module.exports = DispatchNode = (function() { 7 | function DispatchNode(url, createNode) { 8 | this.exec = bind(this.exec, this); 9 | this._dispatchListeners = bind(this._dispatchListeners, this); 10 | this._dispatchHandlers = bind(this._dispatchHandlers, this); 11 | this.use = bind(this.use, this); 12 | this.clear = bind(this.clear, this); 13 | this.remove = bind(this.remove, this); 14 | this.match = bind(this.match, this); 15 | this._find = bind(this._find, this); 16 | if (createNode == null) { 17 | createNode = function(url, createNode) { 18 | return new DispatchNode(url, createNode); 19 | }; 20 | } 21 | this._createNode = createNode; 22 | this._url = url; 23 | this._handlers = []; 24 | this._listeners = []; 25 | this._wildcard = null; 26 | } 27 | 28 | DispatchNode.prototype._find = function(url) { 29 | var i, len, listener, ref; 30 | ref = this._listeners; 31 | for (i = 0, len = ref.length; i < len; i++) { 32 | listener = ref[i]; 33 | if (listener.url === url) { 34 | return listener; 35 | } 36 | } 37 | return null; 38 | }; 39 | 40 | DispatchNode.prototype.match = function(url) { 41 | var listener; 42 | if (url === '*') { 43 | if (this._wildcard == null) { 44 | this._wildcard = this._createNode('*'); 45 | } 46 | return this._wildcard; 47 | } 48 | listener = this._find(url); 49 | if (listener == null) { 50 | listener = { 51 | url: url, 52 | node: this._createNode(url) 53 | }; 54 | this._listeners.push(listener); 55 | this._listeners.sort(function(a, b) { 56 | return b.url.length - a.url.length; 57 | }); 58 | } 59 | return listener.node; 60 | }; 61 | 62 | DispatchNode.prototype.remove = function(url) { 63 | var index, listener; 64 | listener = this._find(url); 65 | if (listener != null) { 66 | index = this._listeners.indexOf(listener); 67 | this._listeners.splice(index, 1); 68 | } 69 | return this; 70 | }; 71 | 72 | DispatchNode.prototype.clear = function() { 73 | this._handlers = []; 74 | this._listeners = []; 75 | return this._wildcard = null; 76 | }; 77 | 78 | DispatchNode.prototype.use = function(handler) { 79 | var h, i, len; 80 | if (Array.isArray(handler)) { 81 | for (i = 0, len = handler.length; i < len; i++) { 82 | h = handler[i]; 83 | this.use(h); 84 | } 85 | } else { 86 | this._handlers.push(handler); 87 | } 88 | return this; 89 | }; 90 | 91 | DispatchNode.prototype._dispatch = function(items, args, next, method) { 92 | var exec, index; 93 | items = items.slice(0); 94 | index = 0; 95 | exec = function() { 96 | var item; 97 | if (index >= items.length) { 98 | return next.apply(null, args); 99 | } 100 | item = items[index]; 101 | index++; 102 | return method(item, args, exec); 103 | }; 104 | return exec(); 105 | }; 106 | 107 | DispatchNode.prototype._dispatchHandlers = function() { 108 | var args, i, next, url; 109 | url = arguments[0], args = 3 <= arguments.length ? slice.call(arguments, 1, i = arguments.length - 1) : (i = 1, []), next = arguments[i++]; 110 | return this._dispatch(this._handlers, args, next, (function(_this) { 111 | return function(item, args, next) { 112 | return item.apply(null, [_this._url, url].concat(slice.call(args), [next])); 113 | }; 114 | })(this)); 115 | }; 116 | 117 | DispatchNode.prototype._dispatchListeners = function() { 118 | var args, i, next, url; 119 | url = arguments[0], args = 3 <= arguments.length ? slice.call(arguments, 1, i = arguments.length - 1) : (i = 1, []), next = arguments[i++]; 120 | return this._dispatch(this._listeners, args, next, function(item, args, next) { 121 | var ref; 122 | if (url.indexOf(item.url) !== 0) { 123 | return next(); 124 | } 125 | return (ref = item.node).exec.apply(ref, [url].concat(slice.call(args), [next])); 126 | }); 127 | }; 128 | 129 | DispatchNode.prototype.exec = function() { 130 | var args, i, next, url; 131 | url = arguments[0], args = 3 <= arguments.length ? slice.call(arguments, 1, i = arguments.length - 1) : (i = 1, []), next = arguments[i++]; 132 | return this._dispatchHandlers.apply(this, [url].concat(slice.call(args), [(function(_this) { 133 | return function() { 134 | return _this._dispatchListeners.apply(_this, [url].concat(slice.call(args), [function() { 135 | var ref; 136 | if (_this._wildcard == null) { 137 | return next.apply(null, args); 138 | } 139 | return (ref = _this._wildcard).exec.apply(ref, [url].concat(slice.call(args), [next])); 140 | }])); 141 | }; 142 | })(this)])); 143 | }; 144 | 145 | return DispatchNode; 146 | 147 | })(); 148 | -------------------------------------------------------------------------------- /src/load-balancer.coffee: -------------------------------------------------------------------------------- 1 | module.exports = class LoadBalancer 2 | constructor: (options) -> 3 | # Default options 4 | @_options = 5 | method: 'roundrobin' 6 | @_options.method = options.method if options?.method? 7 | 8 | @_servers = [] 9 | @_index = 0 10 | 11 | add: (target) => 12 | if target.indexOf('http://') isnt 0 and target.indexOf('https://') isnt 0 13 | target = "http://#{target}" 14 | @_servers.push target 15 | @ 16 | 17 | remove: (target) => 18 | if target.indexOf('http://') isnt 0 and target.indexOf('https://') isnt 0 19 | target = "http://#{target}" 20 | @_servers.remove target 21 | @ 22 | 23 | next: => 24 | @_index = @_index % @_servers.length 25 | result = @_servers[@_index] 26 | @_index++ 27 | result 28 | 29 | distribute: => (mount, url, req, res, next) => 30 | req.target = @next() 31 | next() -------------------------------------------------------------------------------- /src/load-balancer.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.8.0 2 | var LoadBalancer, 3 | __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; 4 | 5 | module.exports = LoadBalancer = (function() { 6 | function LoadBalancer(options) { 7 | this.distribute = __bind(this.distribute, this); 8 | this.next = __bind(this.next, this); 9 | this.remove = __bind(this.remove, this); 10 | this.add = __bind(this.add, this); 11 | this._options = { 12 | method: 'roundrobin' 13 | }; 14 | if ((options != null ? options.method : void 0) != null) { 15 | this._options.method = options.method; 16 | } 17 | this._servers = []; 18 | this._index = 0; 19 | } 20 | 21 | LoadBalancer.prototype.add = function(target) { 22 | if (target.indexOf('http://') !== 0 && target.indexOf('https://') !== 0) { 23 | target = "http://" + target; 24 | } 25 | this._servers.push(target); 26 | return this; 27 | }; 28 | 29 | LoadBalancer.prototype.remove = function(target) { 30 | if (target.indexOf('http://') !== 0 && target.indexOf('https://') !== 0) { 31 | target = "http://" + target; 32 | } 33 | this._servers.remove(target); 34 | return this; 35 | }; 36 | 37 | LoadBalancer.prototype.next = function() { 38 | var result; 39 | this._index = this._index % this._servers.length; 40 | result = this._servers[this._index]; 41 | this._index++; 42 | return result; 43 | }; 44 | 45 | LoadBalancer.prototype.distribute = function() { 46 | return (function(_this) { 47 | return function(mount, url, req, res, next) { 48 | req.target = _this.next(); 49 | return next(); 50 | }; 51 | })(this); 52 | }; 53 | 54 | return LoadBalancer; 55 | 56 | })(); 57 | -------------------------------------------------------------------------------- /src/redwire.coffee: -------------------------------------------------------------------------------- 1 | Bindings = require './bindings' 2 | WebProxy = require './web-proxy' 3 | TcpProxy = require './tcp-proxy' 4 | 5 | # Copy all of the properties on source to target, recurse if an object 6 | copy = (source, target) -> 7 | for key, value of source 8 | if typeof value is 'object' 9 | target[key] = {} if !target[key]? or typeof target[key] isnt 'object' 10 | copy value, target[key] 11 | else 12 | target[key] = value 13 | 14 | module.exports = class RedWire 15 | constructor: (options) -> 16 | # Default options 17 | @_options = 18 | http: no 19 | https: no 20 | http2: no 21 | tcp: no 22 | tls: no 23 | proxy: 24 | xfwd: yes 25 | prependPath: no 26 | log: 27 | debug: -> 28 | notice: -> 29 | error: (err) -> 30 | if err.stack 31 | console.error err.stack 32 | else 33 | console.error err 34 | 35 | copy options, @_options 36 | 37 | @_bindings = @createNewBindings() 38 | if @_options.http? or @_options.https? or @_options.http2? 39 | @_webProxy = new WebProxy @_options, => @_bindings 40 | if @_options.tcp? or @_options.tls? 41 | @_tcpProxy = new TcpProxy @_options, => @_bindings 42 | 43 | # Expose middleware 44 | setHost: (args...) => @_webProxy.setHost args... 45 | sslRedirect: (args...) => @_webProxy.sslRedirect args... 46 | loadBalancer: (args...) => @_webProxy.loadBalancer args... 47 | cors: (args...) => @_webProxy.cors args... 48 | error404: (args...) => @_webProxy.error404 args... 49 | error500: (args...) => @_webProxy.error500 args... 50 | redirect301: (args...) => @_webProxy.redirect301 args... 51 | redirect302: (args...) => @_webProxy.redirect302 args... 52 | redirect301relative: (args...) => @_webProxy.redirect301relative args... 53 | redirect302relative: (args...) => @_webProxy.redirect302relative args... 54 | 55 | # Expose proxy endpoints 56 | proxy: (args...) => @_webProxy.proxy args... 57 | proxyWs: (args...) => @_webProxy.proxyWs args... 58 | proxyTcp: (args...) => @_tcpProxy.proxyTcp args... 59 | proxyTls: (args...) => @_tcpProxy.proxyTls args... 60 | 61 | # Register bindings 62 | http: (args...) => @_bindings.http args... 63 | https: (args...) => @_bindings.https args... 64 | http2: (args...) => @_bindings.http2 args... 65 | httpWs: (args...) => @_bindings.httpWs args... 66 | httpsWs: (args...) => @_bindings.httpsWs args... 67 | tcp: (args...) => @_bindings.tcp args... 68 | tls: (args...) => @_bindings.tls args... 69 | 70 | # Manage bindings 71 | removeHttp: (args...) => @_bindings.removeHttp args... 72 | removeHttps: (args...) => @_bindings.removeHttps args... 73 | removeHttp2: (args...) => @_bindings.removeHttp2 args... 74 | removeHttpWs: (args...) => @_bindings.removeHttpWs args... 75 | removeHttpsWs: (args...) => @_bindings.removeHttpsWs args... 76 | clearHttp: => @_bindings.clearHttp() 77 | clearHttps: => @_bindings.clearHttps() 78 | clearHttp2: => @_bindings.clearHttp2() 79 | clearHttpWs: => @_bindings.clearHttpWs() 80 | clearHttpsWs: => @_bindings.clearHttpsWs() 81 | clearTcp: => @_bindings.clearTcp() 82 | clearTls: => @_bindings.clearTls() 83 | clear: => @_bindings.clear() 84 | createNewBindings: => new Bindings @ 85 | setBindings: (bindings) => @_bindings = bindings 86 | getBindings: => @_bindings 87 | 88 | close: (cb) => 89 | try 90 | @_webProxy.close() if @_webProxy? 91 | @_tcpProxy.close() if @_tcpProxy? 92 | catch e 93 | cb() if cb? 94 | -------------------------------------------------------------------------------- /src/redwire.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.9.2 2 | var Bindings, RedWire, TcpProxy, WebProxy, copy, 3 | bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, 4 | slice = [].slice; 5 | 6 | Bindings = require('./bindings'); 7 | 8 | WebProxy = require('./web-proxy'); 9 | 10 | TcpProxy = require('./tcp-proxy'); 11 | 12 | copy = function(source, target) { 13 | var key, results, value; 14 | results = []; 15 | for (key in source) { 16 | value = source[key]; 17 | if (typeof value === 'object') { 18 | if ((target[key] == null) || typeof target[key] !== 'object') { 19 | target[key] = {}; 20 | } 21 | results.push(copy(value, target[key])); 22 | } else { 23 | results.push(target[key] = value); 24 | } 25 | } 26 | return results; 27 | }; 28 | 29 | module.exports = RedWire = (function() { 30 | function RedWire(options) { 31 | this.close = bind(this.close, this); 32 | this.getBindings = bind(this.getBindings, this); 33 | this.setBindings = bind(this.setBindings, this); 34 | this.createNewBindings = bind(this.createNewBindings, this); 35 | this.clear = bind(this.clear, this); 36 | this.clearTls = bind(this.clearTls, this); 37 | this.clearTcp = bind(this.clearTcp, this); 38 | this.clearHttpsWs = bind(this.clearHttpsWs, this); 39 | this.clearHttpWs = bind(this.clearHttpWs, this); 40 | this.clearHttp2 = bind(this.clearHttp2, this); 41 | this.clearHttps = bind(this.clearHttps, this); 42 | this.clearHttp = bind(this.clearHttp, this); 43 | this.removeHttpsWs = bind(this.removeHttpsWs, this); 44 | this.removeHttpWs = bind(this.removeHttpWs, this); 45 | this.removeHttp2 = bind(this.removeHttp2, this); 46 | this.removeHttps = bind(this.removeHttps, this); 47 | this.removeHttp = bind(this.removeHttp, this); 48 | this.tls = bind(this.tls, this); 49 | this.tcp = bind(this.tcp, this); 50 | this.httpsWs = bind(this.httpsWs, this); 51 | this.httpWs = bind(this.httpWs, this); 52 | this.http2 = bind(this.http2, this); 53 | this.https = bind(this.https, this); 54 | this.http = bind(this.http, this); 55 | this.proxyTls = bind(this.proxyTls, this); 56 | this.proxyTcp = bind(this.proxyTcp, this); 57 | this.proxyWs = bind(this.proxyWs, this); 58 | this.proxy = bind(this.proxy, this); 59 | this.redirect302relative = bind(this.redirect302relative, this); 60 | this.redirect301relative = bind(this.redirect301relative, this); 61 | this.redirect302 = bind(this.redirect302, this); 62 | this.redirect301 = bind(this.redirect301, this); 63 | this.error500 = bind(this.error500, this); 64 | this.error404 = bind(this.error404, this); 65 | this.cors = bind(this.cors, this); 66 | this.loadBalancer = bind(this.loadBalancer, this); 67 | this.sslRedirect = bind(this.sslRedirect, this); 68 | this.setHost = bind(this.setHost, this); 69 | this._options = { 70 | http: false, 71 | https: false, 72 | http2: false, 73 | tcp: false, 74 | tls: false, 75 | proxy: { 76 | xfwd: true, 77 | prependPath: false 78 | }, 79 | log: { 80 | debug: function() {}, 81 | notice: function() {}, 82 | error: function(err) { 83 | if (err.stack) { 84 | return console.error(err.stack); 85 | } else { 86 | return console.error(err); 87 | } 88 | } 89 | } 90 | }; 91 | copy(options, this._options); 92 | this._bindings = this.createNewBindings(); 93 | if ((this._options.http != null) || (this._options.https != null) || (this._options.http2 != null)) { 94 | this._webProxy = new WebProxy(this._options, (function(_this) { 95 | return function() { 96 | return _this._bindings; 97 | }; 98 | })(this)); 99 | } 100 | if ((this._options.tcp != null) || (this._options.tls != null)) { 101 | this._tcpProxy = new TcpProxy(this._options, (function(_this) { 102 | return function() { 103 | return _this._bindings; 104 | }; 105 | })(this)); 106 | } 107 | } 108 | 109 | RedWire.prototype.setHost = function() { 110 | var args, ref; 111 | args = 1 <= arguments.length ? slice.call(arguments, 0) : []; 112 | return (ref = this._webProxy).setHost.apply(ref, args); 113 | }; 114 | 115 | RedWire.prototype.sslRedirect = function() { 116 | var args, ref; 117 | args = 1 <= arguments.length ? slice.call(arguments, 0) : []; 118 | return (ref = this._webProxy).sslRedirect.apply(ref, args); 119 | }; 120 | 121 | RedWire.prototype.loadBalancer = function() { 122 | var args, ref; 123 | args = 1 <= arguments.length ? slice.call(arguments, 0) : []; 124 | return (ref = this._webProxy).loadBalancer.apply(ref, args); 125 | }; 126 | 127 | RedWire.prototype.cors = function() { 128 | var args, ref; 129 | args = 1 <= arguments.length ? slice.call(arguments, 0) : []; 130 | return (ref = this._webProxy).cors.apply(ref, args); 131 | }; 132 | 133 | RedWire.prototype.error404 = function() { 134 | var args, ref; 135 | args = 1 <= arguments.length ? slice.call(arguments, 0) : []; 136 | return (ref = this._webProxy).error404.apply(ref, args); 137 | }; 138 | 139 | RedWire.prototype.error500 = function() { 140 | var args, ref; 141 | args = 1 <= arguments.length ? slice.call(arguments, 0) : []; 142 | return (ref = this._webProxy).error500.apply(ref, args); 143 | }; 144 | 145 | RedWire.prototype.redirect301 = function() { 146 | var args, ref; 147 | args = 1 <= arguments.length ? slice.call(arguments, 0) : []; 148 | return (ref = this._webProxy).redirect301.apply(ref, args); 149 | }; 150 | 151 | RedWire.prototype.redirect302 = function() { 152 | var args, ref; 153 | args = 1 <= arguments.length ? slice.call(arguments, 0) : []; 154 | return (ref = this._webProxy).redirect302.apply(ref, args); 155 | }; 156 | 157 | RedWire.prototype.redirect301relative = function() { 158 | var args, ref; 159 | args = 1 <= arguments.length ? slice.call(arguments, 0) : []; 160 | return (ref = this._webProxy).redirect301relative.apply(ref, args); 161 | }; 162 | 163 | RedWire.prototype.redirect302relative = function() { 164 | var args, ref; 165 | args = 1 <= arguments.length ? slice.call(arguments, 0) : []; 166 | return (ref = this._webProxy).redirect302relative.apply(ref, args); 167 | }; 168 | 169 | RedWire.prototype.proxy = function() { 170 | var args, ref; 171 | args = 1 <= arguments.length ? slice.call(arguments, 0) : []; 172 | return (ref = this._webProxy).proxy.apply(ref, args); 173 | }; 174 | 175 | RedWire.prototype.proxyWs = function() { 176 | var args, ref; 177 | args = 1 <= arguments.length ? slice.call(arguments, 0) : []; 178 | return (ref = this._webProxy).proxyWs.apply(ref, args); 179 | }; 180 | 181 | RedWire.prototype.proxyTcp = function() { 182 | var args, ref; 183 | args = 1 <= arguments.length ? slice.call(arguments, 0) : []; 184 | return (ref = this._tcpProxy).proxyTcp.apply(ref, args); 185 | }; 186 | 187 | RedWire.prototype.proxyTls = function() { 188 | var args, ref; 189 | args = 1 <= arguments.length ? slice.call(arguments, 0) : []; 190 | return (ref = this._tcpProxy).proxyTls.apply(ref, args); 191 | }; 192 | 193 | RedWire.prototype.http = function() { 194 | var args, ref; 195 | args = 1 <= arguments.length ? slice.call(arguments, 0) : []; 196 | return (ref = this._bindings).http.apply(ref, args); 197 | }; 198 | 199 | RedWire.prototype.https = function() { 200 | var args, ref; 201 | args = 1 <= arguments.length ? slice.call(arguments, 0) : []; 202 | return (ref = this._bindings).https.apply(ref, args); 203 | }; 204 | 205 | RedWire.prototype.http2 = function() { 206 | var args, ref; 207 | args = 1 <= arguments.length ? slice.call(arguments, 0) : []; 208 | return (ref = this._bindings).http2.apply(ref, args); 209 | }; 210 | 211 | RedWire.prototype.httpWs = function() { 212 | var args, ref; 213 | args = 1 <= arguments.length ? slice.call(arguments, 0) : []; 214 | return (ref = this._bindings).httpWs.apply(ref, args); 215 | }; 216 | 217 | RedWire.prototype.httpsWs = function() { 218 | var args, ref; 219 | args = 1 <= arguments.length ? slice.call(arguments, 0) : []; 220 | return (ref = this._bindings).httpsWs.apply(ref, args); 221 | }; 222 | 223 | RedWire.prototype.tcp = function() { 224 | var args, ref; 225 | args = 1 <= arguments.length ? slice.call(arguments, 0) : []; 226 | return (ref = this._bindings).tcp.apply(ref, args); 227 | }; 228 | 229 | RedWire.prototype.tls = function() { 230 | var args, ref; 231 | args = 1 <= arguments.length ? slice.call(arguments, 0) : []; 232 | return (ref = this._bindings).tls.apply(ref, args); 233 | }; 234 | 235 | RedWire.prototype.removeHttp = function() { 236 | var args, ref; 237 | args = 1 <= arguments.length ? slice.call(arguments, 0) : []; 238 | return (ref = this._bindings).removeHttp.apply(ref, args); 239 | }; 240 | 241 | RedWire.prototype.removeHttps = function() { 242 | var args, ref; 243 | args = 1 <= arguments.length ? slice.call(arguments, 0) : []; 244 | return (ref = this._bindings).removeHttps.apply(ref, args); 245 | }; 246 | 247 | RedWire.prototype.removeHttp2 = function() { 248 | var args, ref; 249 | args = 1 <= arguments.length ? slice.call(arguments, 0) : []; 250 | return (ref = this._bindings).removeHttp2.apply(ref, args); 251 | }; 252 | 253 | RedWire.prototype.removeHttpWs = function() { 254 | var args, ref; 255 | args = 1 <= arguments.length ? slice.call(arguments, 0) : []; 256 | return (ref = this._bindings).removeHttpWs.apply(ref, args); 257 | }; 258 | 259 | RedWire.prototype.removeHttpsWs = function() { 260 | var args, ref; 261 | args = 1 <= arguments.length ? slice.call(arguments, 0) : []; 262 | return (ref = this._bindings).removeHttpsWs.apply(ref, args); 263 | }; 264 | 265 | RedWire.prototype.clearHttp = function() { 266 | return this._bindings.clearHttp(); 267 | }; 268 | 269 | RedWire.prototype.clearHttps = function() { 270 | return this._bindings.clearHttps(); 271 | }; 272 | 273 | RedWire.prototype.clearHttp2 = function() { 274 | return this._bindings.clearHttp2(); 275 | }; 276 | 277 | RedWire.prototype.clearHttpWs = function() { 278 | return this._bindings.clearHttpWs(); 279 | }; 280 | 281 | RedWire.prototype.clearHttpsWs = function() { 282 | return this._bindings.clearHttpsWs(); 283 | }; 284 | 285 | RedWire.prototype.clearTcp = function() { 286 | return this._bindings.clearTcp(); 287 | }; 288 | 289 | RedWire.prototype.clearTls = function() { 290 | return this._bindings.clearTls(); 291 | }; 292 | 293 | RedWire.prototype.clear = function() { 294 | return this._bindings.clear(); 295 | }; 296 | 297 | RedWire.prototype.createNewBindings = function() { 298 | return new Bindings(this); 299 | }; 300 | 301 | RedWire.prototype.setBindings = function(bindings) { 302 | return this._bindings = bindings; 303 | }; 304 | 305 | RedWire.prototype.getBindings = function() { 306 | return this._bindings; 307 | }; 308 | 309 | RedWire.prototype.close = function(cb) { 310 | var e; 311 | try { 312 | if (this._webProxy != null) { 313 | this._webProxy.close(); 314 | } 315 | if (this._tcpProxy != null) { 316 | this._tcpProxy.close(); 317 | } 318 | } catch (_error) { 319 | e = _error; 320 | } 321 | if (cb != null) { 322 | return cb(); 323 | } 324 | }; 325 | 326 | return RedWire; 327 | 328 | })(); 329 | -------------------------------------------------------------------------------- /src/tcp-proxy.coffee: -------------------------------------------------------------------------------- 1 | net = require 'net' 2 | tls = require 'tls' 3 | parse_url = require('url').parse 4 | 5 | module.exports = class TcpProxy 6 | constructor: (options, bindings) -> 7 | @_options = options 8 | @_bindings = bindings 9 | 10 | @_startTcp() if @_options.tcp 11 | @_startTls() if @_options.tls 12 | 13 | if @_options.tcp?.dest? 14 | setTimeout => 15 | @_bindings().tcp @_options.tcp.dest 16 | , 1 17 | 18 | if @_options.tls?.dest? 19 | setTimeout => 20 | @_bindings().tls @_options.tls.dest 21 | , 1 22 | 23 | _startTcp: => 24 | @_tcpServer = net.createServer (socket) => 25 | socket.on 'error', (args...) => @_tcpServer.emit 'error', args... 26 | @_bindings()._tcp.exec {}, socket, @tcpError 'No rules caught tcp connection' 27 | 28 | @_tcpServer.on 'error', @_options.log.error 29 | 30 | @_tcpServer.listen @_options.tcp.port 31 | @_options.log.notice "tcp server listening on port #{@_options.tcp.port}" 32 | 33 | _startTls: => 34 | @_tlsServer = tls.createServer @certificates.getTlsOptions(@_options.tls), (socket) => 35 | socket.on 'error', (args...) => @_tlsServer.emit 'error', args... 36 | @_bindings()._tls.exec {}, socket, @tlsError 'No rules caught tls connection' 37 | 38 | @_tlsServer.on 'error', @_options.log.error 39 | 40 | @_tlsServer.listen @_options.tls.port 41 | @_options.log.notice "tls server listening on port #{@_options.tls.port}" 42 | 43 | proxyTcp: (target) => (req, socket, next) => 44 | t = target 45 | t = req.target if !t? 46 | if t? and typeof t is 'string' and t.indexOf('tcp://') 47 | t = "tcp://#{t}" 48 | return next() if !t? 49 | 50 | url = parse_url t 51 | url = 52 | host: url.hostname 53 | port: url.port 54 | 55 | proxySock = net 56 | .connect url 57 | .on 'error', (args...) => @_tcpServer.emit 'error', args... 58 | .on 'end', => socket.end() 59 | proxySock.pipe(socket).pipe(proxySock) 60 | socket.on 'end', => proxySock.end() 61 | 62 | proxyTls: (options, target) => (req, socket, next) => 63 | if !target? 64 | target = options 65 | options = null 66 | 67 | t = target 68 | t = req.target if !t? 69 | if t? and t.indexOf('tls://') 70 | t = "tls://#{t}" 71 | return next() if !t? 72 | 73 | options = req if !options? 74 | url = parse_url t 75 | url = 76 | host: url.hostname 77 | port: url.port 78 | 79 | proxySock = tls 80 | .connect options, url 81 | .on 'error', (args...) => @_tlsServer.emit 'error', args... 82 | proxySock.pipe(socket).pipe(proxySock) 83 | 84 | _tcpError: (req, socket, message) => 85 | @_options.log.error message 86 | socket.destroy() 87 | 88 | tcpError: (message) => (req, socket, next) => 89 | @_tcpError req, socket, message 90 | 91 | _tlsError: (req, socket, message) => 92 | @_options.log.error message 93 | socket.destroy() 94 | 95 | tlsError: (message) => (req, socket, next) => 96 | @_tlsError req, socket, message 97 | 98 | close: (cb) => 99 | @_tcpServer.close() if @_tcpServer? 100 | @_tlsServer.close() if @_tlsServer? 101 | cb() if cb? -------------------------------------------------------------------------------- /src/tcp-proxy.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.9.2 2 | var TcpProxy, net, parse_url, tls, 3 | bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, 4 | slice = [].slice; 5 | 6 | net = require('net'); 7 | 8 | tls = require('tls'); 9 | 10 | parse_url = require('url').parse; 11 | 12 | module.exports = TcpProxy = (function() { 13 | function TcpProxy(options, bindings) { 14 | this.close = bind(this.close, this); 15 | this.tlsError = bind(this.tlsError, this); 16 | this._tlsError = bind(this._tlsError, this); 17 | this.tcpError = bind(this.tcpError, this); 18 | this._tcpError = bind(this._tcpError, this); 19 | this.proxyTls = bind(this.proxyTls, this); 20 | this.proxyTcp = bind(this.proxyTcp, this); 21 | this._startTls = bind(this._startTls, this); 22 | this._startTcp = bind(this._startTcp, this); 23 | var ref, ref1; 24 | this._options = options; 25 | this._bindings = bindings; 26 | if (this._options.tcp) { 27 | this._startTcp(); 28 | } 29 | if (this._options.tls) { 30 | this._startTls(); 31 | } 32 | if (((ref = this._options.tcp) != null ? ref.dest : void 0) != null) { 33 | setTimeout((function(_this) { 34 | return function() { 35 | return _this._bindings().tcp(_this._options.tcp.dest); 36 | }; 37 | })(this), 1); 38 | } 39 | if (((ref1 = this._options.tls) != null ? ref1.dest : void 0) != null) { 40 | setTimeout((function(_this) { 41 | return function() { 42 | return _this._bindings().tls(_this._options.tls.dest); 43 | }; 44 | })(this), 1); 45 | } 46 | } 47 | 48 | TcpProxy.prototype._startTcp = function() { 49 | this._tcpServer = net.createServer((function(_this) { 50 | return function(socket) { 51 | socket.on('error', function() { 52 | var args, ref; 53 | args = 1 <= arguments.length ? slice.call(arguments, 0) : []; 54 | return (ref = _this._tcpServer).emit.apply(ref, ['error'].concat(slice.call(args))); 55 | }); 56 | return _this._bindings()._tcp.exec({}, socket, _this.tcpError('No rules caught tcp connection')); 57 | }; 58 | })(this)); 59 | this._tcpServer.on('error', this._options.log.error); 60 | this._tcpServer.listen(this._options.tcp.port); 61 | return this._options.log.notice("tcp server listening on port " + this._options.tcp.port); 62 | }; 63 | 64 | TcpProxy.prototype._startTls = function() { 65 | this._tlsServer = tls.createServer(this.certificates.getTlsOptions(this._options.tls), (function(_this) { 66 | return function(socket) { 67 | socket.on('error', function() { 68 | var args, ref; 69 | args = 1 <= arguments.length ? slice.call(arguments, 0) : []; 70 | return (ref = _this._tlsServer).emit.apply(ref, ['error'].concat(slice.call(args))); 71 | }); 72 | return _this._bindings()._tls.exec({}, socket, _this.tlsError('No rules caught tls connection')); 73 | }; 74 | })(this)); 75 | this._tlsServer.on('error', this._options.log.error); 76 | this._tlsServer.listen(this._options.tls.port); 77 | return this._options.log.notice("tls server listening on port " + this._options.tls.port); 78 | }; 79 | 80 | TcpProxy.prototype.proxyTcp = function(target) { 81 | return (function(_this) { 82 | return function(req, socket, next) { 83 | var proxySock, t, url; 84 | t = target; 85 | if (t == null) { 86 | t = req.target; 87 | } 88 | if ((t != null) && typeof t === 'string' && t.indexOf('tcp://')) { 89 | t = "tcp://" + t; 90 | } 91 | if (t == null) { 92 | return next(); 93 | } 94 | url = parse_url(t); 95 | url = { 96 | host: url.hostname, 97 | port: url.port 98 | }; 99 | proxySock = net.connect(url).on('error', function() { 100 | var args, ref; 101 | args = 1 <= arguments.length ? slice.call(arguments, 0) : []; 102 | return (ref = _this._tcpServer).emit.apply(ref, ['error'].concat(slice.call(args))); 103 | }).on('end', function() { 104 | return socket.end(); 105 | }); 106 | proxySock.pipe(socket).pipe(proxySock); 107 | return socket.on('end', function() { 108 | return proxySock.end(); 109 | }); 110 | }; 111 | })(this); 112 | }; 113 | 114 | TcpProxy.prototype.proxyTls = function(options, target) { 115 | return (function(_this) { 116 | return function(req, socket, next) { 117 | var proxySock, t, url; 118 | if (target == null) { 119 | target = options; 120 | options = null; 121 | } 122 | t = target; 123 | if (t == null) { 124 | t = req.target; 125 | } 126 | if ((t != null) && t.indexOf('tls://')) { 127 | t = "tls://" + t; 128 | } 129 | if (t == null) { 130 | return next(); 131 | } 132 | if (options == null) { 133 | options = req; 134 | } 135 | url = parse_url(t); 136 | url = { 137 | host: url.hostname, 138 | port: url.port 139 | }; 140 | proxySock = tls.connect(options, url).on('error', function() { 141 | var args, ref; 142 | args = 1 <= arguments.length ? slice.call(arguments, 0) : []; 143 | return (ref = _this._tlsServer).emit.apply(ref, ['error'].concat(slice.call(args))); 144 | }); 145 | return proxySock.pipe(socket).pipe(proxySock); 146 | }; 147 | })(this); 148 | }; 149 | 150 | TcpProxy.prototype._tcpError = function(req, socket, message) { 151 | this._options.log.error(message); 152 | return socket.destroy(); 153 | }; 154 | 155 | TcpProxy.prototype.tcpError = function(message) { 156 | return (function(_this) { 157 | return function(req, socket, next) { 158 | return _this._tcpError(req, socket, message); 159 | }; 160 | })(this); 161 | }; 162 | 163 | TcpProxy.prototype._tlsError = function(req, socket, message) { 164 | this._options.log.error(message); 165 | return socket.destroy(); 166 | }; 167 | 168 | TcpProxy.prototype.tlsError = function(message) { 169 | return (function(_this) { 170 | return function(req, socket, next) { 171 | return _this._tlsError(req, socket, message); 172 | }; 173 | })(this); 174 | }; 175 | 176 | TcpProxy.prototype.close = function(cb) { 177 | if (this._tcpServer != null) { 178 | this._tcpServer.close(); 179 | } 180 | if (this._tlsServer != null) { 181 | this._tlsServer.close(); 182 | } 183 | if (cb != null) { 184 | return cb(); 185 | } 186 | }; 187 | 188 | return TcpProxy; 189 | 190 | })(); 191 | -------------------------------------------------------------------------------- /src/testhttp.coffee: -------------------------------------------------------------------------------- 1 | os = require 'os' 2 | http = require 'http' 3 | 4 | args = process.argv.slice 2 5 | port = args[0] 6 | server = http.createServer (req, res) -> 7 | process.stdout.write '.' 8 | result = 9 | server: os.hostname() 10 | port: port 11 | host: req.headers['host'] 12 | method: req.method 13 | url: req.url 14 | res.end JSON.stringify result, null, 2 15 | 16 | server.listen port, -> 17 | console.log "Home grown http server running on port #{port}" -------------------------------------------------------------------------------- /src/testhttp.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.8.0 2 | var args, http, os, port, server; 3 | 4 | os = require('os'); 5 | 6 | http = require('http'); 7 | 8 | args = process.argv.slice(2); 9 | 10 | port = args[0]; 11 | 12 | server = http.createServer(function(req, res) { 13 | var result; 14 | process.stdout.write('.'); 15 | result = { 16 | server: os.hostname(), 17 | port: port, 18 | host: req.headers['host'], 19 | method: req.method, 20 | url: req.url 21 | }; 22 | return res.end(JSON.stringify(result, null, 2)); 23 | }); 24 | 25 | server.listen(port, function() { 26 | return console.log("Home grown http server running on port " + port); 27 | }); 28 | -------------------------------------------------------------------------------- /src/use-node.coffee: -------------------------------------------------------------------------------- 1 | # Recursively dispatch a url based matching execution 2 | # Provides next methods to each handler that require no arguments 3 | module.exports = class DispatchNode 4 | constructor: -> 5 | @_handlers = [] 6 | 7 | use: (handler) => 8 | if Array.isArray handler 9 | @use h for h in handler 10 | else 11 | @_handlers.push handler 12 | @ 13 | 14 | clear: => 15 | @_handlers = [] 16 | 17 | exec: (args..., next) => 18 | # copy so we aren't confused by asyc changes 19 | items = @_handlers[..] 20 | index = 0 21 | exec = -> 22 | return next args... if index >= items.length 23 | item = items[index] 24 | index++ 25 | item args..., exec 26 | exec() -------------------------------------------------------------------------------- /src/use-node.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.8.0 2 | var DispatchNode, 3 | __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, 4 | __slice = [].slice; 5 | 6 | module.exports = DispatchNode = (function() { 7 | function DispatchNode() { 8 | this.exec = __bind(this.exec, this); 9 | this.clear = __bind(this.clear, this); 10 | this.use = __bind(this.use, this); 11 | this._handlers = []; 12 | } 13 | 14 | DispatchNode.prototype.use = function(handler) { 15 | var h, _i, _len; 16 | if (Array.isArray(handler)) { 17 | for (_i = 0, _len = handler.length; _i < _len; _i++) { 18 | h = handler[_i]; 19 | this.use(h); 20 | } 21 | } else { 22 | this._handlers.push(handler); 23 | } 24 | return this; 25 | }; 26 | 27 | DispatchNode.prototype.clear = function() { 28 | return this._handlers = []; 29 | }; 30 | 31 | DispatchNode.prototype.exec = function() { 32 | var args, exec, index, items, next, _i; 33 | args = 2 <= arguments.length ? __slice.call(arguments, 0, _i = arguments.length - 1) : (_i = 0, []), next = arguments[_i++]; 34 | items = this._handlers.slice(0); 35 | index = 0; 36 | exec = function() { 37 | var item; 38 | if (index >= items.length) { 39 | return next.apply(null, args); 40 | } 41 | item = items[index]; 42 | index++; 43 | return item.apply(null, __slice.call(args).concat([exec])); 44 | }; 45 | return exec(); 46 | }; 47 | 48 | return DispatchNode; 49 | 50 | })(); 51 | -------------------------------------------------------------------------------- /src/web-proxy.coffee: -------------------------------------------------------------------------------- 1 | http = require 'http' 2 | https = require 'https' 3 | http2 = require 'http2' 4 | http_proxy = require 'http-proxy' 5 | parse_url = require('url').parse 6 | format_url = require('url').format 7 | 8 | CertificateStore = require './certificate-store' 9 | LoadBalancer = require './load-balancer' 10 | 11 | module.exports = class WebProxy 12 | constructor: (options, bindings) -> 13 | @_options = options 14 | @_bindings = bindings 15 | 16 | @_startHttp() if @_options.http 17 | @_startHttps() if @_options.https 18 | @_startHttp2() if @_options.http2 19 | @_startProxy() if @_options.proxy 20 | 21 | if @_options.http?.routes? 22 | setTimeout => 23 | for source, target of @_options.http.routes 24 | @_bindings().http source, target 25 | , 1 26 | 27 | if @_options.https?.routes? 28 | setTimeout => 29 | for source, target of @_options.https.routes 30 | @_bindings().https source, target 31 | , 1 32 | 33 | if @_options.http2?.routes? 34 | setTimeout => 35 | for source, target of @_options.http2.routes 36 | @_bindings().http2 source, target 37 | , 1 38 | 39 | _parseSource: (req, protocol, hostname, port) => 40 | source = parse_url req.url 41 | source.protocol = protocol 42 | source.host = req.headers.host 43 | if source.host 44 | chunks = source.host.split ':' 45 | else 46 | chunks = [ hostname, port ] 47 | source.hostname = chunks[0] 48 | source.port = chunks[1] or null 49 | source.href = "#{source.protocol}//#{source.host}#{source.path}" 50 | source.slashes = yes 51 | source 52 | 53 | _parseHostPort: (options, defaulthost, defaultport) => 54 | result = 55 | port: defaultport 56 | hostname: defaulthost 57 | if options.port? 58 | if typeof options.port is 'string' and options.port.indexOf(':') isnt -1 59 | chunks = options.port.split ':' 60 | result.hostname = chunks[0] 61 | result.port = chunks[1] 62 | else 63 | result.port = options.port 64 | 65 | if options.hostname? 66 | if typeof options.hostname is 'string' and options.hostname.indexOf(':') isnt -1 67 | chunks = options.hostname.split ':' 68 | result.hostname = chunks[0] 69 | result.port = chunks[1] 70 | else 71 | result.hostname = options.hostname 72 | 73 | result 74 | 75 | _translateUrl: (mount, target, url) => 76 | mount = parse_url mount 77 | target = parse_url target 78 | url = parse_url url 79 | "#{target.pathname}#{url.path[mount.pathname.length..]}" 80 | 81 | _startHttp: => 82 | bind = @_parseHostPort @_options.http, '0.0.0.0', 8080 83 | @_options.http.port = bind.port 84 | @_options.http.hostname = bind.hostname 85 | 86 | @_httpServer = http.createServer (req, res) => 87 | req.source = @_parseSource req, 'http:', @_options.http.hostname, @_options.http.port 88 | @_bindings()._http.exec req.source.href, req, res, @_error404 89 | 90 | if @_options.http.websockets 91 | @_options.log.notice 'http server configured for websockets' 92 | @_httpServer.on 'upgrade', (req, socket, head) => 93 | req.source = @_parseSource req, 'http:', @_options.http.hostname, @_options.http.port 94 | @_bindings()._httpWs.exec req.source.href, req, socket, head, @_error404 95 | 96 | @_httpServer.on 'error', (err, req, res) => 97 | @_error500 req, res, err if req? and res? 98 | @_options.log.error err 99 | try res.end() if res? 100 | 101 | @_httpServer.listen @_options.http.port, @_options.http.hostname 102 | @_options.log.notice "http server listening on #{@_options.http.hostname}:#{@_options.http.port}" 103 | 104 | _startHttps: => 105 | @certificates = new CertificateStore() 106 | 107 | bind = @_parseHostPort @_options.https, '0.0.0.0', 8443 108 | @_options.https.port = bind.port 109 | @_options.https.hostname = bind.hostname 110 | 111 | @_httpsServer = https.createServer @certificates.getHttpsOptions(@_options.https), (req, res) => 112 | req.source = @_parseSource req, 'https:', @_options.https.hostname, @_options.https.port 113 | @_bindings()._https.exec req.source.href, req, res, @_error404 114 | 115 | if @_options.https.websockets 116 | @_options.log.notice "https server configured for websockets" 117 | @_httpsServer.on 'upgrade', (req, socket, head) => 118 | req.source = @_parseSource req, 'https:', @_options.https.hostname, @_options.https.port 119 | @_bindings()._httpsWs.exec req.source.href, req, socket, head, @_error404 120 | 121 | @_httpsServer.on 'error', (err, req, res) => 122 | @_error500 req, res, err if req? and res? 123 | @_options.log.error err 124 | try res.end() if res? 125 | 126 | @_httpsServer.listen @_options.https.port, @_options.https.hostname 127 | @_options.log.notice "https server listening on #{@_options.https.hostname}:#{@_options.https.port}" 128 | 129 | _startHttp2: => 130 | @certificates = new CertificateStore() 131 | 132 | bind = @_parseHostPort @_options.http2, '0.0.0.0', 8443 133 | @_options.http2.port = bind.port 134 | @_options.http2.hostname = bind.hostname 135 | 136 | @_http2Server = http2.createServer @certificates.getHttpsOptions(@_options.http2), (req, res) => 137 | req.connection = 138 | encrypted = yes 139 | req.source = @_parseSource req, 'https:', @_options.http2.hostname, @_options.http2.port 140 | @_bindings()._http2.exec req.source.href, req, res, @_error404 141 | 142 | @_http2Server.on 'error', (err, req, res) => 143 | @_error500 req, res, err if req? and res? 144 | @_options.log.error err 145 | try res.end() if res? 146 | 147 | @_http2Server.listen @_options.http2.port, @_options.http2.hostname 148 | @_options.log.notice "http2 server listening on #{@_options.http2.hostname}:#{@_options.http2.port}" 149 | 150 | _startProxy: => 151 | @_proxy = http_proxy.createProxyServer @_options.proxy 152 | @_proxy.on 'proxyReq', (p, req, res, options) => 153 | p.setHeader 'connection', 'keep-alive' if @_options.proxy?.keepAlive 154 | p.setHeader 'host', req.host if req.host? 155 | @_proxy.on 'proxyRes', (p, req, res) => 156 | if req.httpVersionMajor is 2 157 | delete p.headers.connection 158 | @_proxy.on 'error', (err, req, res) => 159 | @_error500 req, res, err if req? and res? !res.headersSent 160 | @_options.log.error err 161 | try res.end() if res? 162 | 163 | proxy: (target) => (mount, url, req, res, next) => 164 | t = target 165 | if t? and t.indexOf('http://') isnt 0 and t.indexOf('https://') isnt 0 166 | t = "http://#{t}" 167 | t = req.target if !t? 168 | return next() if !t? 169 | url = @_translateUrl mount, t, url 170 | @_options.log.notice "#{mount} proxy #{req.url} url" 171 | req.url = url 172 | @_proxy.web req, res, target: t 173 | 174 | proxyWs: (target) => (mount, url, req, socket, head, next) => 175 | t = target 176 | if t? and t.indexOf('http://') isnt 0 and t.indexOf('https://') isnt 0 177 | t = "http://#{t}" 178 | t = req.target if !t? 179 | return next() if !t? 180 | url = @_translateUrl mount, t, url 181 | @_options.log.notice "#{mount} proxy #{req.url} url" 182 | req.url = url 183 | @_proxy.ws req, socket, head, target: t 184 | 185 | setHost: (host) => (mount, url, req, args..., next) => 186 | req.host = host 187 | next() 188 | 189 | loadBalancer: (options) => new LoadBalancer options 190 | 191 | sslRedirect: (port) => (mount, url, req, res, next) => 192 | target = parse_url req.url 193 | target.port = port if port? 194 | target.port = @_options.https.port if @_options.https.port? 195 | target.hostname = req.source.hostname 196 | target.protocol = 'https:' 197 | res.writeHead 302, Location: format_url target 198 | res.end() 199 | 200 | cors: (allowedHosts) => (mount, url, req, res, next) => 201 | referer = req.headers.referer 202 | return next() if !referer? 203 | referer = parse_url referer 204 | referer = format_url 205 | protocol: referer.protocol 206 | hostname: referer.hostname 207 | port: referer.port 208 | return next() unless referer in allowedHosts 209 | res.setHeader 'Access-Control-Allow-Origin', referer 210 | res.setHeader 'Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE' 211 | res.setHeader 'Access-Control-Allow-Headers', 'Content-Type' 212 | next() 213 | 214 | _error404: (req, res) => 215 | result = message: "No http proxy setup for #{req.source.href}" 216 | res.writeHead 404, 'Content-Type': 'application/json' 217 | res.write JSON.stringify result, null, 2 218 | res.end() 219 | 220 | error404: => (mount, url, req, res, next) => @_error404 req, res 221 | 222 | _error500: (req, res, err) => 223 | result = message: "Internal error for #{req.source.href}", error: err 224 | res.writeHead 500, 'Content-Type': 'application/json' 225 | res.write JSON.stringify result, null, 2 226 | res.end() 227 | 228 | error500: => (mount, url, req, res, next) => @_error500 req, res, '' 229 | 230 | _redirectParseUrl: (url) => 231 | if url.indexOf('http://') isnt 0 and url.indexOf('https://') isnt 0 232 | url = "http://#{url}" 233 | url 234 | 235 | _redirect: (req, res, code, location) => 236 | res.writeHead code, Location: location 237 | res.end() 238 | 239 | _redirect301absolute: (req, res, location) => 240 | @_redirect req, res, 301, @_redirectParseUrl location 241 | 242 | redirect301absolute: (location) => (mount, url, req, res, next) => 243 | @_redirect req, res, 301, @_redirectParseUrl location 244 | 245 | _redirect302absolute: (req, res, location) => 246 | @_redirect req, res, 302, @_redirectParseUrl location 247 | 248 | redirect302absolute: (location) => (mount, url, req, res, next) => 249 | @_redirect req, res, 302, @_redirectParseUrl location 250 | 251 | _redirectParseRel: (location, url) => 252 | target = @_redirectParseUrl location 253 | target += url 254 | target 255 | 256 | _redirect301: (req, res, location) => 257 | @_redirect req, res, 301, @_redirectParseRel location, req.url 258 | 259 | redirect301: (location) => (mount, url, req, res, next) => 260 | @_redirect req, res, 301, @_redirectParseRel location, req.url 261 | 262 | _redirect302: (req, res, location) => 263 | @_redirect req, res, 302, @_redirectParseRel location, req.url 264 | 265 | redirect302: (location) => (mount, url, req, res, next) => 266 | @_redirect req, res, 302, @_redirectParseRel location, req.url 267 | 268 | close: (cb) => 269 | @_httpServer.close() if @_httpServer? 270 | @_httpsServer.close() if @_httpsServer? 271 | @_http2Server.close() if @_http2Server? 272 | @_proxy.close() if @_proxy? 273 | cb() if cb? 274 | -------------------------------------------------------------------------------- /src/web-proxy.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.9.2 2 | var CertificateStore, LoadBalancer, WebProxy, format_url, http, http2, http_proxy, https, parse_url, 3 | bind1 = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, 4 | slice = [].slice, 5 | indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; 6 | 7 | http = require('http'); 8 | 9 | https = require('https'); 10 | 11 | http2 = require('http2'); 12 | 13 | http_proxy = require('http-proxy'); 14 | 15 | parse_url = require('url').parse; 16 | 17 | format_url = require('url').format; 18 | 19 | CertificateStore = require('./certificate-store'); 20 | 21 | LoadBalancer = require('./load-balancer'); 22 | 23 | module.exports = WebProxy = (function() { 24 | function WebProxy(options, bindings) { 25 | this.close = bind1(this.close, this); 26 | this.redirect302 = bind1(this.redirect302, this); 27 | this._redirect302 = bind1(this._redirect302, this); 28 | this.redirect301 = bind1(this.redirect301, this); 29 | this._redirect301 = bind1(this._redirect301, this); 30 | this._redirectParseRel = bind1(this._redirectParseRel, this); 31 | this.redirect302absolute = bind1(this.redirect302absolute, this); 32 | this._redirect302absolute = bind1(this._redirect302absolute, this); 33 | this.redirect301absolute = bind1(this.redirect301absolute, this); 34 | this._redirect301absolute = bind1(this._redirect301absolute, this); 35 | this._redirect = bind1(this._redirect, this); 36 | this._redirectParseUrl = bind1(this._redirectParseUrl, this); 37 | this.error500 = bind1(this.error500, this); 38 | this._error500 = bind1(this._error500, this); 39 | this.error404 = bind1(this.error404, this); 40 | this._error404 = bind1(this._error404, this); 41 | this.cors = bind1(this.cors, this); 42 | this.sslRedirect = bind1(this.sslRedirect, this); 43 | this.loadBalancer = bind1(this.loadBalancer, this); 44 | this.setHost = bind1(this.setHost, this); 45 | this.proxyWs = bind1(this.proxyWs, this); 46 | this.proxy = bind1(this.proxy, this); 47 | this._startProxy = bind1(this._startProxy, this); 48 | this._startHttp2 = bind1(this._startHttp2, this); 49 | this._startHttps = bind1(this._startHttps, this); 50 | this._startHttp = bind1(this._startHttp, this); 51 | this._translateUrl = bind1(this._translateUrl, this); 52 | this._parseHostPort = bind1(this._parseHostPort, this); 53 | this._parseSource = bind1(this._parseSource, this); 54 | var ref, ref1, ref2; 55 | this._options = options; 56 | this._bindings = bindings; 57 | if (this._options.http) { 58 | this._startHttp(); 59 | } 60 | if (this._options.https) { 61 | this._startHttps(); 62 | } 63 | if (this._options.http2) { 64 | this._startHttp2(); 65 | } 66 | if (this._options.proxy) { 67 | this._startProxy(); 68 | } 69 | if (((ref = this._options.http) != null ? ref.routes : void 0) != null) { 70 | setTimeout((function(_this) { 71 | return function() { 72 | var ref1, results, source, target; 73 | ref1 = _this._options.http.routes; 74 | results = []; 75 | for (source in ref1) { 76 | target = ref1[source]; 77 | results.push(_this._bindings().http(source, target)); 78 | } 79 | return results; 80 | }; 81 | })(this), 1); 82 | } 83 | if (((ref1 = this._options.https) != null ? ref1.routes : void 0) != null) { 84 | setTimeout((function(_this) { 85 | return function() { 86 | var ref2, results, source, target; 87 | ref2 = _this._options.https.routes; 88 | results = []; 89 | for (source in ref2) { 90 | target = ref2[source]; 91 | results.push(_this._bindings().https(source, target)); 92 | } 93 | return results; 94 | }; 95 | })(this), 1); 96 | } 97 | if (((ref2 = this._options.http2) != null ? ref2.routes : void 0) != null) { 98 | setTimeout((function(_this) { 99 | return function() { 100 | var ref3, results, source, target; 101 | ref3 = _this._options.http2.routes; 102 | results = []; 103 | for (source in ref3) { 104 | target = ref3[source]; 105 | results.push(_this._bindings().http2(source, target)); 106 | } 107 | return results; 108 | }; 109 | })(this), 1); 110 | } 111 | } 112 | 113 | WebProxy.prototype._parseSource = function(req, protocol, hostname, port) { 114 | var chunks, source; 115 | source = parse_url(req.url); 116 | source.protocol = protocol; 117 | source.host = req.headers.host; 118 | if (source.host) { 119 | chunks = source.host.split(':'); 120 | } else { 121 | chunks = [hostname, port]; 122 | } 123 | source.hostname = chunks[0]; 124 | source.port = chunks[1] || null; 125 | source.href = source.protocol + "//" + source.host + source.path; 126 | source.slashes = true; 127 | return source; 128 | }; 129 | 130 | WebProxy.prototype._parseHostPort = function(options, defaulthost, defaultport) { 131 | var chunks, result; 132 | result = { 133 | port: defaultport, 134 | hostname: defaulthost 135 | }; 136 | if (options.port != null) { 137 | if (typeof options.port === 'string' && options.port.indexOf(':') !== -1) { 138 | chunks = options.port.split(':'); 139 | result.hostname = chunks[0]; 140 | result.port = chunks[1]; 141 | } else { 142 | result.port = options.port; 143 | } 144 | } 145 | if (options.hostname != null) { 146 | if (typeof options.hostname === 'string' && options.hostname.indexOf(':') !== -1) { 147 | chunks = options.hostname.split(':'); 148 | result.hostname = chunks[0]; 149 | result.port = chunks[1]; 150 | } else { 151 | result.hostname = options.hostname; 152 | } 153 | } 154 | return result; 155 | }; 156 | 157 | WebProxy.prototype._translateUrl = function(mount, target, url) { 158 | mount = parse_url(mount); 159 | target = parse_url(target); 160 | url = parse_url(url); 161 | return "" + target.pathname + url.path.slice(mount.pathname.length); 162 | }; 163 | 164 | WebProxy.prototype._startHttp = function() { 165 | var bind; 166 | bind = this._parseHostPort(this._options.http, '0.0.0.0', 8080); 167 | this._options.http.port = bind.port; 168 | this._options.http.hostname = bind.hostname; 169 | this._httpServer = http.createServer((function(_this) { 170 | return function(req, res) { 171 | req.source = _this._parseSource(req, 'http:', _this._options.http.hostname, _this._options.http.port); 172 | return _this._bindings()._http.exec(req.source.href, req, res, _this._error404); 173 | }; 174 | })(this)); 175 | if (this._options.http.websockets) { 176 | this._options.log.notice('http server configured for websockets'); 177 | this._httpServer.on('upgrade', (function(_this) { 178 | return function(req, socket, head) { 179 | req.source = _this._parseSource(req, 'http:', _this._options.http.hostname, _this._options.http.port); 180 | return _this._bindings()._httpWs.exec(req.source.href, req, socket, head, _this._error404); 181 | }; 182 | })(this)); 183 | } 184 | this._httpServer.on('error', (function(_this) { 185 | return function(err, req, res) { 186 | if ((req != null) && (res != null)) { 187 | _this._error500(req, res, err); 188 | } 189 | _this._options.log.error(err); 190 | try { 191 | if (res != null) { 192 | return res.end(); 193 | } 194 | } catch (_error) {} 195 | }; 196 | })(this)); 197 | this._httpServer.listen(this._options.http.port, this._options.http.hostname); 198 | return this._options.log.notice("http server listening on " + this._options.http.hostname + ":" + this._options.http.port); 199 | }; 200 | 201 | WebProxy.prototype._startHttps = function() { 202 | var bind; 203 | this.certificates = new CertificateStore(); 204 | bind = this._parseHostPort(this._options.https, '0.0.0.0', 8443); 205 | this._options.https.port = bind.port; 206 | this._options.https.hostname = bind.hostname; 207 | this._httpsServer = https.createServer(this.certificates.getHttpsOptions(this._options.https), (function(_this) { 208 | return function(req, res) { 209 | req.source = _this._parseSource(req, 'https:', _this._options.https.hostname, _this._options.https.port); 210 | return _this._bindings()._https.exec(req.source.href, req, res, _this._error404); 211 | }; 212 | })(this)); 213 | if (this._options.https.websockets) { 214 | this._options.log.notice("https server configured for websockets"); 215 | this._httpsServer.on('upgrade', (function(_this) { 216 | return function(req, socket, head) { 217 | req.source = _this._parseSource(req, 'https:', _this._options.https.hostname, _this._options.https.port); 218 | return _this._bindings()._httpsWs.exec(req.source.href, req, socket, head, _this._error404); 219 | }; 220 | })(this)); 221 | } 222 | this._httpsServer.on('error', (function(_this) { 223 | return function(err, req, res) { 224 | if ((req != null) && (res != null)) { 225 | _this._error500(req, res, err); 226 | } 227 | _this._options.log.error(err); 228 | try { 229 | if (res != null) { 230 | return res.end(); 231 | } 232 | } catch (_error) {} 233 | }; 234 | })(this)); 235 | this._httpsServer.listen(this._options.https.port, this._options.https.hostname); 236 | return this._options.log.notice("https server listening on " + this._options.https.hostname + ":" + this._options.https.port); 237 | }; 238 | 239 | WebProxy.prototype._startHttp2 = function() { 240 | var bind; 241 | this.certificates = new CertificateStore(); 242 | bind = this._parseHostPort(this._options.http2, '0.0.0.0', 8443); 243 | this._options.http2.port = bind.port; 244 | this._options.http2.hostname = bind.hostname; 245 | this._http2Server = http2.createServer(this.certificates.getHttpsOptions(this._options.http2), (function(_this) { 246 | return function(req, res) { 247 | var encrypted; 248 | req.connection = encrypted = true; 249 | req.source = _this._parseSource(req, 'https:', _this._options.http2.hostname, _this._options.http2.port); 250 | return _this._bindings()._http2.exec(req.source.href, req, res, _this._error404); 251 | }; 252 | })(this)); 253 | this._http2Server.on('error', (function(_this) { 254 | return function(err, req, res) { 255 | if ((req != null) && (res != null)) { 256 | _this._error500(req, res, err); 257 | } 258 | _this._options.log.error(err); 259 | try { 260 | if (res != null) { 261 | return res.end(); 262 | } 263 | } catch (_error) {} 264 | }; 265 | })(this)); 266 | this._http2Server.listen(this._options.http2.port, this._options.http2.hostname); 267 | return this._options.log.notice("http2 server listening on " + this._options.http2.hostname + ":" + this._options.http2.port); 268 | }; 269 | 270 | WebProxy.prototype._startProxy = function() { 271 | this._proxy = http_proxy.createProxyServer(this._options.proxy); 272 | this._proxy.on('proxyReq', (function(_this) { 273 | return function(p, req, res, options) { 274 | var ref; 275 | if ((ref = _this._options.proxy) != null ? ref.keepAlive : void 0) { 276 | p.setHeader('connection', 'keep-alive'); 277 | } 278 | if (req.host != null) { 279 | return p.setHeader('host', req.host); 280 | } 281 | }; 282 | })(this)); 283 | this._proxy.on('proxyRes', (function(_this) { 284 | return function(p, req, res) { 285 | if (req.httpVersionMajor === 2) { 286 | return delete p.headers.connection; 287 | } 288 | }; 289 | })(this)); 290 | return this._proxy.on('error', (function(_this) { 291 | return function(err, req, res) { 292 | if ((req != null) && (typeof res === "function" ? res(!res.headersSent) : void 0)) { 293 | _this._error500(req, res, err); 294 | } 295 | _this._options.log.error(err); 296 | try { 297 | if (res != null) { 298 | return res.end(); 299 | } 300 | } catch (_error) {} 301 | }; 302 | })(this)); 303 | }; 304 | 305 | WebProxy.prototype.proxy = function(target) { 306 | return (function(_this) { 307 | return function(mount, url, req, res, next) { 308 | var t; 309 | t = target; 310 | if ((t != null) && t.indexOf('http://') !== 0 && t.indexOf('https://') !== 0) { 311 | t = "http://" + t; 312 | } 313 | if (t == null) { 314 | t = req.target; 315 | } 316 | if (t == null) { 317 | return next(); 318 | } 319 | url = _this._translateUrl(mount, t, url); 320 | _this._options.log.notice(mount + " proxy " + req.url + " url"); 321 | req.url = url; 322 | return _this._proxy.web(req, res, { 323 | target: t 324 | }); 325 | }; 326 | })(this); 327 | }; 328 | 329 | WebProxy.prototype.proxyWs = function(target) { 330 | return (function(_this) { 331 | return function(mount, url, req, socket, head, next) { 332 | var t; 333 | t = target; 334 | if ((t != null) && t.indexOf('http://') !== 0 && t.indexOf('https://') !== 0) { 335 | t = "http://" + t; 336 | } 337 | if (t == null) { 338 | t = req.target; 339 | } 340 | if (t == null) { 341 | return next(); 342 | } 343 | url = _this._translateUrl(mount, t, url); 344 | _this._options.log.notice(mount + " proxy " + req.url + " url"); 345 | req.url = url; 346 | return _this._proxy.ws(req, socket, head, { 347 | target: t 348 | }); 349 | }; 350 | })(this); 351 | }; 352 | 353 | WebProxy.prototype.setHost = function(host) { 354 | return (function(_this) { 355 | return function() { 356 | var args, i, mount, next, req, url; 357 | mount = arguments[0], url = arguments[1], req = arguments[2], args = 5 <= arguments.length ? slice.call(arguments, 3, i = arguments.length - 1) : (i = 3, []), next = arguments[i++]; 358 | req.host = host; 359 | return next(); 360 | }; 361 | })(this); 362 | }; 363 | 364 | WebProxy.prototype.loadBalancer = function(options) { 365 | return new LoadBalancer(options); 366 | }; 367 | 368 | WebProxy.prototype.sslRedirect = function(port) { 369 | return (function(_this) { 370 | return function(mount, url, req, res, next) { 371 | var target; 372 | target = parse_url(req.url); 373 | if (port != null) { 374 | target.port = port; 375 | } 376 | if (_this._options.https.port != null) { 377 | target.port = _this._options.https.port; 378 | } 379 | target.hostname = req.source.hostname; 380 | target.protocol = 'https:'; 381 | res.writeHead(302, { 382 | Location: format_url(target) 383 | }); 384 | return res.end(); 385 | }; 386 | })(this); 387 | }; 388 | 389 | WebProxy.prototype.cors = function(allowedHosts) { 390 | return (function(_this) { 391 | return function(mount, url, req, res, next) { 392 | var referer; 393 | referer = req.headers.referer; 394 | if (referer == null) { 395 | return next(); 396 | } 397 | referer = parse_url(referer); 398 | referer = format_url({ 399 | protocol: referer.protocol, 400 | hostname: referer.hostname, 401 | port: referer.port 402 | }); 403 | if (indexOf.call(allowedHosts, referer) < 0) { 404 | return next(); 405 | } 406 | res.setHeader('Access-Control-Allow-Origin', referer); 407 | res.setHeader('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE'); 408 | res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); 409 | return next(); 410 | }; 411 | })(this); 412 | }; 413 | 414 | WebProxy.prototype._error404 = function(req, res) { 415 | var result; 416 | result = { 417 | message: "No http proxy setup for " + req.source.href 418 | }; 419 | res.writeHead(404, { 420 | 'Content-Type': 'application/json' 421 | }); 422 | res.write(JSON.stringify(result, null, 2)); 423 | return res.end(); 424 | }; 425 | 426 | WebProxy.prototype.error404 = function() { 427 | return (function(_this) { 428 | return function(mount, url, req, res, next) { 429 | return _this._error404(req, res); 430 | }; 431 | })(this); 432 | }; 433 | 434 | WebProxy.prototype._error500 = function(req, res, err) { 435 | var result; 436 | result = { 437 | message: "Internal error for " + req.source.href, 438 | error: err 439 | }; 440 | res.writeHead(500, { 441 | 'Content-Type': 'application/json' 442 | }); 443 | res.write(JSON.stringify(result, null, 2)); 444 | return res.end(); 445 | }; 446 | 447 | WebProxy.prototype.error500 = function() { 448 | return (function(_this) { 449 | return function(mount, url, req, res, next) { 450 | return _this._error500(req, res, ''); 451 | }; 452 | })(this); 453 | }; 454 | 455 | WebProxy.prototype._redirectParseUrl = function(url) { 456 | if (url.indexOf('http://') !== 0 && url.indexOf('https://') !== 0) { 457 | url = "http://" + url; 458 | } 459 | return url; 460 | }; 461 | 462 | WebProxy.prototype._redirect = function(req, res, code, location) { 463 | res.writeHead(code, { 464 | Location: location 465 | }); 466 | return res.end(); 467 | }; 468 | 469 | WebProxy.prototype._redirect301absolute = function(req, res, location) { 470 | return this._redirect(req, res, 301, this._redirectParseUrl(location)); 471 | }; 472 | 473 | WebProxy.prototype.redirect301absolute = function(location) { 474 | return (function(_this) { 475 | return function(mount, url, req, res, next) { 476 | return _this._redirect(req, res, 301, _this._redirectParseUrl(location)); 477 | }; 478 | })(this); 479 | }; 480 | 481 | WebProxy.prototype._redirect302absolute = function(req, res, location) { 482 | return this._redirect(req, res, 302, this._redirectParseUrl(location)); 483 | }; 484 | 485 | WebProxy.prototype.redirect302absolute = function(location) { 486 | return (function(_this) { 487 | return function(mount, url, req, res, next) { 488 | return _this._redirect(req, res, 302, _this._redirectParseUrl(location)); 489 | }; 490 | })(this); 491 | }; 492 | 493 | WebProxy.prototype._redirectParseRel = function(location, url) { 494 | var target; 495 | target = this._redirectParseUrl(location); 496 | target += url; 497 | return target; 498 | }; 499 | 500 | WebProxy.prototype._redirect301 = function(req, res, location) { 501 | return this._redirect(req, res, 301, this._redirectParseRel(location, req.url)); 502 | }; 503 | 504 | WebProxy.prototype.redirect301 = function(location) { 505 | return (function(_this) { 506 | return function(mount, url, req, res, next) { 507 | return _this._redirect(req, res, 301, _this._redirectParseRel(location, req.url)); 508 | }; 509 | })(this); 510 | }; 511 | 512 | WebProxy.prototype._redirect302 = function(req, res, location) { 513 | return this._redirect(req, res, 302, this._redirectParseRel(location, req.url)); 514 | }; 515 | 516 | WebProxy.prototype.redirect302 = function(location) { 517 | return (function(_this) { 518 | return function(mount, url, req, res, next) { 519 | return _this._redirect(req, res, 302, _this._redirectParseRel(location, req.url)); 520 | }; 521 | })(this); 522 | }; 523 | 524 | WebProxy.prototype.close = function(cb) { 525 | if (this._httpServer != null) { 526 | this._httpServer.close(); 527 | } 528 | if (this._httpsServer != null) { 529 | this._httpsServer.close(); 530 | } 531 | if (this._http2Server != null) { 532 | this._http2Server.close(); 533 | } 534 | if (this._proxy != null) { 535 | this._proxy.close(); 536 | } 537 | if (cb != null) { 538 | return cb(); 539 | } 540 | }; 541 | 542 | return WebProxy; 543 | 544 | })(); 545 | -------------------------------------------------------------------------------- /test/cors.coffee: -------------------------------------------------------------------------------- 1 | expect = require('chai').expect 2 | RedWire = require '../' 3 | http = require 'http' 4 | 5 | describe 'CORS', -> 6 | testServer = (port, cb) -> 7 | server = http.createServer (req, res) -> 8 | res.write '' 9 | res.end() 10 | cb req 11 | server.close() 12 | server.listen port 13 | 14 | it 'should not be present when no servers are provided', (done) -> 15 | redwire = new RedWire http: port: 53437 16 | 17 | redwire 18 | .http 'http://localhost:53437' 19 | .use redwire.cors([]) 20 | .use redwire.proxy 'http://localhost:54677' 21 | 22 | passed = no 23 | testServer 54677, (req) -> passed = yes 24 | 25 | options = 26 | hostname: 'localhost' 27 | port: 53437 28 | headers: referer: 'http://example.com' 29 | 30 | http.get options, (res) -> 31 | expect(res.headers['access-control-allow-origin']).to.be.undefined() 32 | expect(passed).to.be.true() 33 | redwire.close() 34 | done() 35 | 36 | it 'should match the referer and return a single domain', (done) -> 37 | redwire = new RedWire http: port: 53438 38 | 39 | redwire 40 | .http 'http://localhost:53438' 41 | .use redwire.cors(['http://default.com', 'http://example.com', 'http://test.com']) 42 | .use redwire.proxy 'http://localhost:54678' 43 | 44 | passed = no 45 | testServer 54678, (req) -> passed = yes 46 | 47 | options = 48 | hostname: 'localhost' 49 | port: 53438 50 | headers: referer: 'http://example.com' 51 | 52 | http.get options, (res) -> 53 | expect(res.headers['access-control-allow-origin']).to.be.eql 'http://example.com' 54 | expect(passed).to.be.true() 55 | redwire.close() 56 | done() -------------------------------------------------------------------------------- /test/cors.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.8.0 2 | var RedWire, expect, http; 3 | 4 | expect = require('chai').expect; 5 | 6 | RedWire = require('../'); 7 | 8 | http = require('http'); 9 | 10 | describe('CORS', function() { 11 | var testServer; 12 | testServer = function(port, cb) { 13 | var server; 14 | server = http.createServer(function(req, res) { 15 | res.write(''); 16 | res.end(); 17 | cb(req); 18 | return server.close(); 19 | }); 20 | return server.listen(port); 21 | }; 22 | it('should not be present when no servers are provided', function(done) { 23 | var options, passed, redwire; 24 | redwire = new RedWire({ 25 | http: { 26 | port: 53437 27 | } 28 | }); 29 | redwire.http('http://localhost:53437').use(redwire.cors([])).use(redwire.proxy('http://localhost:54677')); 30 | passed = false; 31 | testServer(54677, function(req) { 32 | return passed = true; 33 | }); 34 | options = { 35 | hostname: 'localhost', 36 | port: 53437, 37 | headers: { 38 | referer: 'http://example.com' 39 | } 40 | }; 41 | return http.get(options, function(res) { 42 | expect(res.headers['access-control-allow-origin']).to.be.undefined(); 43 | expect(passed).to.be["true"](); 44 | redwire.close(); 45 | return done(); 46 | }); 47 | }); 48 | return it('should match the referer and return a single domain', function(done) { 49 | var options, passed, redwire; 50 | redwire = new RedWire({ 51 | http: { 52 | port: 53438 53 | } 54 | }); 55 | redwire.http('http://localhost:53438').use(redwire.cors(['http://default.com', 'http://example.com', 'http://test.com'])).use(redwire.proxy('http://localhost:54678')); 56 | passed = false; 57 | testServer(54678, function(req) { 58 | return passed = true; 59 | }); 60 | options = { 61 | hostname: 'localhost', 62 | port: 53438, 63 | headers: { 64 | referer: 'http://example.com' 65 | } 66 | }; 67 | return http.get(options, function(res) { 68 | expect(res.headers['access-control-allow-origin']).to.be.eql('http://example.com'); 69 | expect(passed).to.be["true"](); 70 | redwire.close(); 71 | return done(); 72 | }); 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /test/displatch-node.coffee: -------------------------------------------------------------------------------- 1 | expect = require('chai').expect 2 | DispatchNode = require '../src/dispatch-node' 3 | 4 | describe 'Dispatcher Node', -> 5 | it 'should exec with no nodes', -> 6 | node = new DispatchNode() 7 | node.exec 'http://localhost/', -> 8 | 9 | it 'should exec all top level handlers', -> 10 | node = new DispatchNode() 11 | count = 0 12 | 13 | node.use (mount, url, next) -> 14 | count++ 15 | next() 16 | 17 | node.use (mount, url, next) -> 18 | count++ 19 | next() 20 | 21 | node.exec 'http://localhost/', -> 22 | expect(count).to.be.eql 2 23 | 24 | it 'should match urls', -> 25 | node = new DispatchNode() 26 | count = 0 27 | 28 | node.match('http://localhost/').use (mount, url, next) -> 29 | count++ 30 | next() 31 | 32 | node.match('http://example.com/').use (mount, url, next) -> 33 | count++ 34 | next() 35 | 36 | node.exec 'http://localhost/', -> 37 | expect(count).to.be.eql 1 38 | 39 | it 'should recursively match urls', -> 40 | node = new DispatchNode() 41 | count = 0 42 | 43 | node.match('http://localhost/').match('http://localhost/').use (mount, url, next) -> 44 | count++ 45 | next() 46 | 47 | node.match('http://example.com/').use (mount, url, next) -> 48 | count++ 49 | next() 50 | 51 | node.exec 'http://localhost/', -> 52 | expect(count).to.be.eql 1 53 | 54 | it 'should match specific urls first', -> 55 | node = new DispatchNode() 56 | count = 0 57 | 58 | node 59 | .match('http://localhost/') 60 | .use (mount, url, next) -> 61 | count++ 62 | expect(count).to.be.eql 2 63 | next() 64 | 65 | node 66 | .match('http://localhost/specific') 67 | .use (mount, url, next) -> 68 | count++ 69 | expect(count).to.be.eql 1 70 | next() 71 | 72 | node.match('http://example.com/').use (mount, url, next) -> 73 | count++ 74 | next() 75 | 76 | node.exec 'http://localhost/specific', -> 77 | expect(count).to.be.eql 2 78 | 79 | it 'should run top level handlers first', -> 80 | node = new DispatchNode() 81 | count = 0 82 | 83 | node.use (mount, url, next) -> 84 | count++ 85 | expect(count).to.be.eql 1 86 | next() 87 | 88 | node 89 | .match('http://localhost/specific') 90 | .use (mount, url, next) -> 91 | count++ 92 | expect(count).to.be.eql 2 93 | next() 94 | 95 | node.match('http://example.com/').use (mount, url, next) -> 96 | count++ 97 | next() 98 | 99 | node.exec 'http://localhost/specific', -> 100 | expect(count).to.be.eql 2 101 | 102 | it 'should allow arrays of handlers', -> 103 | node = new DispatchNode() 104 | count = 0 105 | 106 | handler = (mount, url, next) -> 107 | count++ 108 | next() 109 | 110 | node.use [handler, handler] 111 | 112 | node.exec 'http://localhost/', -> 113 | expect(count).to.be.eql 2 -------------------------------------------------------------------------------- /test/displatch-node.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.8.0 2 | var DispatchNode, expect; 3 | 4 | expect = require('chai').expect; 5 | 6 | DispatchNode = require('../src/dispatch-node'); 7 | 8 | describe('Dispatcher Node', function() { 9 | it('should exec with no nodes', function() { 10 | var node; 11 | node = new DispatchNode(); 12 | return node.exec('http://localhost/', function() {}); 13 | }); 14 | it('should exec all top level handlers', function() { 15 | var count, node; 16 | node = new DispatchNode(); 17 | count = 0; 18 | node.use(function(mount, url, next) { 19 | count++; 20 | return next(); 21 | }); 22 | node.use(function(mount, url, next) { 23 | count++; 24 | return next(); 25 | }); 26 | node.exec('http://localhost/', function() {}); 27 | return expect(count).to.be.eql(2); 28 | }); 29 | it('should match urls', function() { 30 | var count, node; 31 | node = new DispatchNode(); 32 | count = 0; 33 | node.match('http://localhost/').use(function(mount, url, next) { 34 | count++; 35 | return next(); 36 | }); 37 | node.match('http://example.com/').use(function(mount, url, next) { 38 | count++; 39 | return next(); 40 | }); 41 | node.exec('http://localhost/', function() {}); 42 | return expect(count).to.be.eql(1); 43 | }); 44 | it('should recursively match urls', function() { 45 | var count, node; 46 | node = new DispatchNode(); 47 | count = 0; 48 | node.match('http://localhost/').match('http://localhost/').use(function(mount, url, next) { 49 | count++; 50 | return next(); 51 | }); 52 | node.match('http://example.com/').use(function(mount, url, next) { 53 | count++; 54 | return next(); 55 | }); 56 | node.exec('http://localhost/', function() {}); 57 | return expect(count).to.be.eql(1); 58 | }); 59 | it('should match specific urls first', function() { 60 | var count, node; 61 | node = new DispatchNode(); 62 | count = 0; 63 | node.match('http://localhost/').use(function(mount, url, next) { 64 | count++; 65 | expect(count).to.be.eql(2); 66 | return next(); 67 | }); 68 | node.match('http://localhost/specific').use(function(mount, url, next) { 69 | count++; 70 | expect(count).to.be.eql(1); 71 | return next(); 72 | }); 73 | node.match('http://example.com/').use(function(mount, url, next) { 74 | count++; 75 | return next(); 76 | }); 77 | node.exec('http://localhost/specific', function() {}); 78 | return expect(count).to.be.eql(2); 79 | }); 80 | it('should run top level handlers first', function() { 81 | var count, node; 82 | node = new DispatchNode(); 83 | count = 0; 84 | node.use(function(mount, url, next) { 85 | count++; 86 | expect(count).to.be.eql(1); 87 | return next(); 88 | }); 89 | node.match('http://localhost/specific').use(function(mount, url, next) { 90 | count++; 91 | expect(count).to.be.eql(2); 92 | return next(); 93 | }); 94 | node.match('http://example.com/').use(function(mount, url, next) { 95 | count++; 96 | return next(); 97 | }); 98 | node.exec('http://localhost/specific', function() {}); 99 | return expect(count).to.be.eql(2); 100 | }); 101 | return it('should allow arrays of handlers', function() { 102 | var count, handler, node; 103 | node = new DispatchNode(); 104 | count = 0; 105 | handler = function(mount, url, next) { 106 | count++; 107 | return next(); 108 | }; 109 | node.use([handler, handler]); 110 | return node.exec('http://localhost/', function() { 111 | return expect(count).to.be.eql(2); 112 | }); 113 | }); 114 | }); 115 | -------------------------------------------------------------------------------- /test/load-balancer.coffee: -------------------------------------------------------------------------------- 1 | expect = require('chai').expect 2 | LoadBalancer = require '../src/load-balancer' 3 | 4 | describe 'Load Balancer', -> 5 | it 'should return undefined if no servers are configured', -> 6 | load = new LoadBalancer() 7 | expect(load.next()).to.be.eql `undefined` 8 | 9 | it 'should round robin by default', -> 10 | load = new LoadBalancer() 11 | load.add 'http://localhost:6000/' 12 | load.add 'http://localhost:6001/' 13 | load.add 'http://localhost:6002/' 14 | 15 | expect(load.next()).to.be.eql 'http://localhost:6000/' 16 | expect(load.next()).to.be.eql 'http://localhost:6001/' 17 | expect(load.next()).to.be.eql 'http://localhost:6002/' 18 | expect(load.next()).to.be.eql 'http://localhost:6000/' -------------------------------------------------------------------------------- /test/load-balancer.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.8.0 2 | var LoadBalancer, expect; 3 | 4 | expect = require('chai').expect; 5 | 6 | LoadBalancer = require('../src/load-balancer'); 7 | 8 | describe('Load Balancer', function() { 9 | it('should return undefined if no servers are configured', function() { 10 | var load; 11 | load = new LoadBalancer(); 12 | return expect(load.next()).to.be.eql(undefined); 13 | }); 14 | return it('should round robin by default', function() { 15 | var load; 16 | load = new LoadBalancer(); 17 | load.add('http://localhost:6000/'); 18 | load.add('http://localhost:6001/'); 19 | load.add('http://localhost:6002/'); 20 | expect(load.next()).to.be.eql('http://localhost:6000/'); 21 | expect(load.next()).to.be.eql('http://localhost:6001/'); 22 | expect(load.next()).to.be.eql('http://localhost:6002/'); 23 | return expect(load.next()).to.be.eql('http://localhost:6000/'); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /test/redwire.coffee: -------------------------------------------------------------------------------- 1 | expect = require('chai').expect 2 | RedWire = require '../src/redwire' 3 | http = require 'http' 4 | net = require 'net' 5 | 6 | describe 'RedWire', -> 7 | testHttpServer = (port, cb) -> 8 | server = http.createServer (req, res) -> 9 | res.write '' 10 | res.end() 11 | cb req 12 | server.close() 13 | server.listen port 14 | 15 | testTcpServer = (port, cb) -> 16 | server = net.createServer (socket) -> 17 | socket.write 'success' 18 | socket.end() 19 | server.close() 20 | cb() 21 | server.listen port 22 | 23 | it 'should have sensible defaults', -> 24 | redwire = new RedWire() 25 | redwire.close() 26 | 27 | it 'should autoprefix source urls with http:// if absent', -> 28 | redwire = new RedWire http: port: 53435 29 | passed = no 30 | redwire.http 'example.com', (mount, url, req, res, next) -> 31 | expect(url).to.be.eql 'http://example.com/test' 32 | passed = yes 33 | redwire.http('example.com').exec 'http://example.com/test' 34 | expect(passed).to.be.eql yes 35 | 36 | it 'should autoprefix target urls with http:// if absent', (done) -> 37 | redwire = new RedWire http: port: 53436 38 | 39 | redwire.http 'localhost:53436', 'localhost:54676' 40 | 41 | testHttpServer 54676, (req) -> 42 | expect(req.headers['host']).to.be.eql 'localhost:53436' 43 | 44 | http.get 'http://localhost:53436', (res) -> 45 | redwire.close() 46 | done() 47 | 48 | it 'should pass through query strings', (done) -> 49 | redwire = new RedWire http: port: 53439 50 | 51 | redwire.http 'localhost:53439', 'localhost:54679' 52 | 53 | testHttpServer 54679, (req) -> 54 | expect(req.headers['host']).to.be.eql 'localhost:53439' 55 | expect(req.url).to.be.eql '/query?string=should&work' 56 | 57 | http.get 'http://localhost:53439/query?string=should&work', (res) -> 58 | redwire.close() 59 | done() 60 | 61 | it 'should proxy tcp', (done) -> 62 | redwire = new RedWire tcp: port: 63433 63 | 64 | redwire.tcp 'localhost:63423' 65 | 66 | failed1 = yes 67 | failed2 = yes 68 | testTcpServer 63423, -> failed1 = no 69 | 70 | client = net.connect { port: 63433 }, -> 71 | client.setEncoding 'utf8' 72 | client.on 'data', (data) -> 73 | expect(data).to.eql 'success' 74 | failed2 = no 75 | client.on 'end', (data) -> 76 | expect(failed1).to.be.false() 77 | expect(failed2).to.be.false() 78 | done() -------------------------------------------------------------------------------- /test/redwire.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.8.0 2 | var RedWire, expect, http, net; 3 | 4 | expect = require('chai').expect; 5 | 6 | RedWire = require('../src/redwire'); 7 | 8 | http = require('http'); 9 | 10 | net = require('net'); 11 | 12 | describe('RedWire', function() { 13 | var testHttpServer, testTcpServer; 14 | testHttpServer = function(port, cb) { 15 | var server; 16 | server = http.createServer(function(req, res) { 17 | res.write(''); 18 | res.end(); 19 | cb(req); 20 | return server.close(); 21 | }); 22 | return server.listen(port); 23 | }; 24 | testTcpServer = function(port, cb) { 25 | var server; 26 | server = net.createServer(function(socket) { 27 | socket.write('success'); 28 | socket.end(); 29 | server.close(); 30 | return cb(); 31 | }); 32 | return server.listen(port); 33 | }; 34 | it('should have sensible defaults', function() { 35 | var redwire; 36 | redwire = new RedWire(); 37 | return redwire.close(); 38 | }); 39 | it('should autoprefix source urls with http:// if absent', function() { 40 | var passed, redwire; 41 | redwire = new RedWire({ 42 | http: { 43 | port: 53435 44 | } 45 | }); 46 | passed = false; 47 | redwire.http('example.com', function(mount, url, req, res, next) { 48 | expect(url).to.be.eql('http://example.com/test'); 49 | return passed = true; 50 | }); 51 | redwire.http('example.com').exec('http://example.com/test'); 52 | return expect(passed).to.be.eql(true); 53 | }); 54 | it('should autoprefix target urls with http:// if absent', function(done) { 55 | var redwire; 56 | redwire = new RedWire({ 57 | http: { 58 | port: 53436 59 | } 60 | }); 61 | redwire.http('localhost:53436', 'localhost:54676'); 62 | testHttpServer(54676, function(req) { 63 | return expect(req.headers['host']).to.be.eql('localhost:53436'); 64 | }); 65 | return http.get('http://localhost:53436', function(res) { 66 | redwire.close(); 67 | return done(); 68 | }); 69 | }); 70 | it('should pass through query strings', function(done) { 71 | var redwire; 72 | redwire = new RedWire({ 73 | http: { 74 | port: 53439 75 | } 76 | }); 77 | redwire.http('localhost:53439', 'localhost:54679'); 78 | testHttpServer(54679, function(req) { 79 | expect(req.headers['host']).to.be.eql('localhost:53439'); 80 | return expect(req.url).to.be.eql('/query?string=should&work'); 81 | }); 82 | return http.get('http://localhost:53439/query?string=should&work', function(res) { 83 | redwire.close(); 84 | return done(); 85 | }); 86 | }); 87 | return it('should proxy tcp', function(done) { 88 | var client, failed1, failed2, redwire; 89 | redwire = new RedWire({ 90 | tcp: { 91 | port: 63433 92 | } 93 | }); 94 | redwire.tcp('localhost:63423'); 95 | failed1 = true; 96 | failed2 = true; 97 | testTcpServer(63423, function() { 98 | return failed1 = false; 99 | }); 100 | return client = net.connect({ 101 | port: 63433 102 | }, function() { 103 | client.setEncoding('utf8'); 104 | client.on('data', function(data) { 105 | expect(data).to.eql('success'); 106 | return failed2 = false; 107 | }); 108 | return client.on('end', function(data) { 109 | expect(failed1).to.be["false"](); 110 | expect(failed2).to.be["false"](); 111 | return done(); 112 | }); 113 | }); 114 | }); 115 | }); 116 | -------------------------------------------------------------------------------- /test/set-host.coffee: -------------------------------------------------------------------------------- 1 | expect = require('chai').expect 2 | RedWire = require '../src/redwire' 3 | http = require 'http' 4 | 5 | describe 'Set Host', -> 6 | testServer = (port, cb) -> 7 | server = http.createServer (req, res) -> 8 | res.write '' 9 | res.end() 10 | cb req 11 | server.close() 12 | server.listen port 13 | 14 | it 'should not apply by default', (done) -> 15 | redwire = new RedWire http: port: 53433 16 | 17 | redwire 18 | .http 'http://localhost:53433' 19 | .use redwire.proxy 'http://localhost:54674' 20 | 21 | testServer 54674, (req) -> 22 | expect(req.headers['host']).to.be.eql 'localhost:53433' 23 | 24 | http.get 'http://localhost:53433', (res) -> 25 | redwire.close() 26 | done() 27 | 28 | it 'should apply when configured', (done) -> 29 | redwire = new RedWire http: port: 53434 30 | 31 | redwire 32 | .http 'http://localhost:53434' 33 | .use redwire.setHost 'example.com' 34 | .use redwire.proxy 'http://localhost:54675' 35 | 36 | testServer 54675, (req) -> 37 | expect(req.headers['host']).to.be.eql 'example.com' 38 | 39 | http.get 'http://localhost:53434', (res) -> 40 | redwire.close() 41 | done() 42 | -------------------------------------------------------------------------------- /test/set-host.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.8.0 2 | var RedWire, expect, http; 3 | 4 | expect = require('chai').expect; 5 | 6 | RedWire = require('../src/redwire'); 7 | 8 | http = require('http'); 9 | 10 | describe('Set Host', function() { 11 | var testServer; 12 | testServer = function(port, cb) { 13 | var server; 14 | server = http.createServer(function(req, res) { 15 | res.write(''); 16 | res.end(); 17 | cb(req); 18 | return server.close(); 19 | }); 20 | return server.listen(port); 21 | }; 22 | it('should not apply by default', function(done) { 23 | var redwire; 24 | redwire = new RedWire({ 25 | http: { 26 | port: 53433 27 | } 28 | }); 29 | redwire.http('http://localhost:53433').use(redwire.proxy('http://localhost:54674')); 30 | testServer(54674, function(req) { 31 | return expect(req.headers['host']).to.be.eql('localhost:53433'); 32 | }); 33 | return http.get('http://localhost:53433', function(res) { 34 | redwire.close(); 35 | return done(); 36 | }); 37 | }); 38 | return it('should apply when configured', function(done) { 39 | var redwire; 40 | redwire = new RedWire({ 41 | http: { 42 | port: 53434 43 | } 44 | }); 45 | redwire.http('http://localhost:53434').use(redwire.setHost('example.com')).use(redwire.proxy('http://localhost:54675')); 46 | testServer(54675, function(req) { 47 | return expect(req.headers['host']).to.be.eql('example.com'); 48 | }); 49 | return http.get('http://localhost:53434', function(res) { 50 | redwire.close(); 51 | return done(); 52 | }); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /test/use-node.coffee: -------------------------------------------------------------------------------- 1 | expect = require('chai').expect 2 | UseNode = require '../src/use-node' 3 | 4 | describe 'Use Node', -> 5 | it 'should exec with no nodes', -> 6 | node = new UseNode() 7 | node.exec -> 8 | 9 | it 'should exec with no args', -> 10 | node = new UseNode() 11 | failed = yes 12 | node.use (next) -> 13 | failed = no 14 | next() 15 | node.exec -> 16 | expect(failed).to.be.false() 17 | 18 | it 'should exec with args', -> 19 | node = new UseNode() 20 | failed = yes 21 | node.use (arg, next) -> 22 | failed = no 23 | expect(arg).to.be.eql 'yup' 24 | next() 25 | node.exec 'yup', -> 26 | expect(failed).to.be.false() 27 | 28 | it 'should cascade', -> 29 | node = new UseNode() 30 | failed1 = yes 31 | node.use (arg, next) -> 32 | failed1 = no 33 | expect(arg).to.be.eql 'yup' 34 | next() 35 | failed2 = yes 36 | node.use (arg, next) -> 37 | failed2 = no 38 | expect(arg).to.be.eql 'yup' 39 | next() 40 | node.exec 'yup', -> 41 | expect(failed1).to.be.false() 42 | expect(failed2).to.be.false() -------------------------------------------------------------------------------- /test/use-node.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.8.0 2 | var UseNode, expect; 3 | 4 | expect = require('chai').expect; 5 | 6 | UseNode = require('../src/use-node'); 7 | 8 | describe('Use Node', function() { 9 | it('should exec with no nodes', function() { 10 | var node; 11 | node = new UseNode(); 12 | return node.exec(function() {}); 13 | }); 14 | it('should exec with no args', function() { 15 | var failed, node; 16 | node = new UseNode(); 17 | failed = true; 18 | node.use(function(next) { 19 | failed = false; 20 | return next(); 21 | }); 22 | node.exec(function() {}); 23 | return expect(failed).to.be["false"](); 24 | }); 25 | it('should exec with args', function() { 26 | var failed, node; 27 | node = new UseNode(); 28 | failed = true; 29 | node.use(function(arg, next) { 30 | failed = false; 31 | expect(arg).to.be.eql('yup'); 32 | return next(); 33 | }); 34 | node.exec('yup', function() {}); 35 | return expect(failed).to.be["false"](); 36 | }); 37 | return it('should cascade', function() { 38 | var failed1, failed2, node; 39 | node = new UseNode(); 40 | failed1 = true; 41 | node.use(function(arg, next) { 42 | failed1 = false; 43 | expect(arg).to.be.eql('yup'); 44 | return next(); 45 | }); 46 | failed2 = true; 47 | node.use(function(arg, next) { 48 | failed2 = false; 49 | expect(arg).to.be.eql('yup'); 50 | return next(); 51 | }); 52 | node.exec('yup', function() {}); 53 | expect(failed1).to.be["false"](); 54 | return expect(failed2).to.be["false"](); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /test/wildcard-match.coffee: -------------------------------------------------------------------------------- 1 | expect = require('chai').expect 2 | RedWire = require '../src/redwire' 3 | http = require 'http' 4 | 5 | describe 'RedWire wildcard', -> 6 | testHttpServer = (port, cb) -> 7 | server = http.createServer (req, res) -> 8 | res.write '' 9 | res.end() 10 | cb req 11 | server.close() 12 | server.listen port 13 | 14 | it 'should allow wildcards', (done) -> 15 | redwire = new RedWire http: port: 63436 16 | 17 | redwire.http '*', 'localhost:64676' 18 | 19 | sawit = no 20 | testHttpServer 64676, (req) -> sawit = yes 21 | 22 | http.get 'http://localhost:63436', (res) -> 23 | redwire.close() 24 | expect(sawit).to.be.true() 25 | done() -------------------------------------------------------------------------------- /test/wildcard-match.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.9.2 2 | var RedWire, expect, http; 3 | 4 | expect = require('chai').expect; 5 | 6 | RedWire = require('../src/redwire'); 7 | 8 | http = require('http'); 9 | 10 | describe('RedWire wildcard', function() { 11 | var testHttpServer; 12 | testHttpServer = function(port, cb) { 13 | var server; 14 | server = http.createServer(function(req, res) { 15 | res.write(''); 16 | res.end(); 17 | cb(req); 18 | return server.close(); 19 | }); 20 | return server.listen(port); 21 | }; 22 | return it('should allow wildcards', function(done) { 23 | var redwire, sawit; 24 | redwire = new RedWire({ 25 | http: { 26 | port: 63436 27 | } 28 | }); 29 | redwire.http('*', 'localhost:64676'); 30 | sawit = false; 31 | testHttpServer(64676, function(req) { 32 | return sawit = true; 33 | }); 34 | return http.get('http://localhost:63436', function(res) { 35 | redwire.close(); 36 | expect(sawit).to.be["true"](); 37 | return done(); 38 | }); 39 | }); 40 | }); 41 | --------------------------------------------------------------------------------