The other day, I posted a chunk of code called Switchboard, which was all about using Nodejitsu's node-http-proxy. I turns out there's a much easier way. HttpProxy.createServer will create an empty routing proxy, and when you ask for a backend handler that isn't present, node-http-proxy will automagically build one using internal defaults.
So, really, your code looks like this:
backends = [
{
name: 'Event Hub'
host: 'http://127.0.0.1:8024'
paths: ['^/socket.io/']
},
{
name: 'Django Server'
host: 'http://127.0.0.1:8022'
paths: ['^/api/']
}
]
standard_options = (t) ->
target:
host: t.hostname
port: t.port
https: (t.protocol == 'https:')
enable:
xforward: true
for backend in backends
backend.paths = (new RegExp(i) for i in backend.paths)
backend.options = standard_options(url.parse(backend.host))
find_backend = (req) ->
pathname = url.parse(req.url).pathname
for backend in backends
for path in backend.paths
m = path.exec(pathname)
if m
return backend
null
proxyserver = httpProxy.createServer (req, res, proxy) ->
backend = find_backend(req)
if backend
proxy.proxyRequest(req, res, backend.options)
return @
# Clean up if no backend found
try
res.writeHead 404
res.end()
catch error
console.error("res.writeHead/res.end error: %s", error.message)
undefined
proxyserver.listen(80)
Instead of all that silliness in the original, here I define my routes in a fairly standard human-readable format, and then translate them into the target style preferred by node-http-proxy. I have a simple function for looking up the back-end depending upon URL components and returning the first match. I let node-http-proxy do all the work of creating a connection to the back-end if it doesn't work.
All that crud from last week reduced to a couple of list comprehensions and translations. Sad, isn't it?
I should add that there's a bug in the current version of node-http-proxy that will cause the code above to fail. Node-http-proxy has an internal call, _getKey(), to look up that host to which it will route a request, that it should use, but does not, for the ad-hoc proxy creation. Until that bug is fixed (and yes, I have a patch submitted in), you'll have to patch the server by hand.
There is some additional magic not shown in this example. The rewriters is a trivial add-in, and neither example handles the case where a back-end issues an http:// re-direct, but that has to be mangled to read https:// before being forwarded to the client. Again, there's a bug in the original that prevents this from working right, and I have a patch in for that as well.
Finally, the code as checked out of npm has a bug that prevents websocket upgrades from working properly. My work-around was to disable that in the code and do it myself, but that's not a satisfactory hack and I haven't sent that forward to the nodejitsu team yet.
I will say that with all of these fixes in place, I have a working solution for replacing Nginx. It proxies nicely, it proxies websockets nicely, and it proxies large-file uploads without using hideously huge memory or file buffers on the proxy server, all through SSL. We will be evaluating its performance, stability, and cross-browser issues over the next couple of weeks to ensure it's a good solution.
I will also say that reading the Nginx source code was an eye-opening experience. The core and http modules are very well-written, clear and concise, and learning from their comments and code examples has made a big difference in how I'm approaching this proxying issue.
In theory, I could reduce Switchboard down to my routines for defining backends and frontends, add the rewriter and ad-hoc websocket upgrader. Maybe I will. It'll be a heck of a lot shorter than the current edition.