Summary
In this article we're going to see how to use Azure Web Apps as a reverse proxy to multiple hapi.js servers running inside a single Node.js application (not hosted in Azure). As usual, the code is in GitHub at https://github.com/TomChantler/HapiTest
Background
This article was prompted by a question I received on my article about using Azure Web Apps as a free reverse proxy.
There are several ways of hosting multiple node servers within a single application (with each one listening on a different port). For example, Hapi.js is one and Express is another.
When doing this, it's common to use a reverse proxy to redirect incoming requests to the different servers based on the incoming hostnames or urls. It's also quite common to use nginx for this purpose. Here are some instructions on how to use nginx as a reverse proxy.
In this article we're going to use Azure Web Apps as a reverse proxy to achieve this. Since we're going to use several different unique hostnames, we can't use the free tier (custom domains are a paid option), but if you didn't mind everything being under *.azurewebsites.net
you could still achieve this using the free tier.
Description
If you want to follow along from scratch, then first you need to install Node.js®. Go to https://nodejs.org/ and download 4.4.* LTS
NOTE: Right now I'm using version 4.4.7 LTS of Node.js® on my dev machine since it's the latest version supported by ghost, but this works fine with earlier versions.
It's also important to note the following about Azure Web Apps (taken from here):
- Port Bindings – Web Apps only supports Port 80 for HTTP and Port 443 for HTTPS traffic. Different port configurations will be ignored and traffic will be routed to 80 or 443
This means that you can't host your multiple hapi.js servers under a single Azure Web Apps instance. It also might explain why you can't use node-reverse-proxy to listen and route stuff internally based on the hostname and then host it all in Azure Web Apps; it doesn't actually run the node servers on separate ports internally.
First let's set up a simple hapi.js multiple server environment.
Simple hapi.js multiple servers test
This is really simple. Possibly too simple to put in GitHub, but you can find it at https://github.com/TomChantler/HapiTest nevertheless.
First install Node.js.
Now open a Node.js command prompt, create a new directory, navigate to it and run npm init
to create your new node project (you don't need to specify a git repository).
Now you want to install hapi and let the node project know that it's a dependency, which means it will automatically be installed if the binaries are not present. To do this run npm install --save hapi
.
Create your server.js file
For the purpose of this demo, I want to create several very simple servers listening on ports 3001 - 300n
. They're just going to return some very simple information: server uri (including port) and timestamp.
// By Tom Chantler - https://tomssl.com/2016/07/15/azure-web-apps-as-a-reverse-proxy-for-hapi-js-and-other-multiple-node-js-servers/
'use strict';
const Hapi = require('hapi');
const numberOfServers = 10;
for (var i=0; i<numberOfServers; i++) {
createServer(3001 + i);
};
function createServer(port){
const serverN = new Hapi.Server();
serverN.connection({host: 'localhost', port: port }); // either put your own hostname here or remove the host variable and it will use your machine name.
serverN.route({
method: 'get',
path: '/',
handler: function (request, reply) {
reply('Hello from <b>' + serverN.info.uri + '</b> - ' + new Date().toString());
}
});
serverN.start(() => {
console.log('server running at:', serverN.info.uri);
});
};
This code is fairly straightforward. I've created a simple function such that I can specify how many servers I want and they will be created on ports 3001
- 300n
.
Finally, run npm start
to start your server.
That's it. Check it's working by visiting these URLs:
- http://localhost:3001 and you should see something like
Hello from http://localhost:3001 at Fri Jul 15 2016 13:37:00 GMT+0100 (GMT Summer Time)
- ...
- http://localhost:3010 and you should see
Hello from http://localhost:3010 at Fri Jul 15 2016 13:37:00 GMT+0100 (GMT Summer Time)
NOTE: Remember to open the requisite ports in the Windows Firewall otherwise the reverse proxy won't be able to access your sites.
Note that whilst we are hosting in Windows, we are just using plain Node.js. We're not using iisnode or anything else clever.
A special treat for people without a spare server lying around
NOTE: If you just want to test the reverse proxy part and really can't be bothered to do the Node.js bit above, you can use my hapi.js servers at
- http://hapitest.giga.io:3001
- ...
- http://hapitest.giga.io:3010 (there are ten of them).
Now you're ready to do the reverse proxy bit.
Setting up the reverse proxy
The idea here is that you're going to link external hostnames to your various internal hapi.js servers.
In order to do that, you need to use IIS URL rewriting and Application Request Routing (ARR) and in order to use the latter in Azure Web Apps you need to unlock it by using an applicationHost.xdt
transform file (which needs to be uploaded into the site
directory which is one level above wwwroot
). I've written a lot more about all of that in my article about creating your own free reverse proxy using Azure Web Apps, so if you want more information, that's a good place to start.
applicationHost.xdt
<?xml version="1.0"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<system.webServer>
<proxy xdt:Transform="InsertIfMissing" enabled="true" preserveHostHeader="false" reverseRewriteHostInResponseHeaders="false" />
</system.webServer>
</configuration>
Let's take a contrived example first before looking at a real (also somewhat contrived) example.
Imagine you want the following to happen:
Requests to https://example.com/
should return data from http://myhapidomain.com:3001/
Requests to https://example.com/blog/
should return data from http://myhapidomain.com:3002/
Requests to https://otherexample.com/
should return data from http://myhapidomain.com:3003/
To do this, you'd simply place the following under the configuration
→ system.webServer
→ rewrite
node in your web.config file:
<rules>
<rule name="Reverse Proxy to myhapidomain.com:3001" stopProcessing="true">
<match url="(.*)" />
<conditions>
<add input="{HTTP_HOST}" pattern="^example.com(.*)" />
</conditions>
<action type="Rewrite" url="http://myhapidomain.com:3001{C:1}" />
</rule>
<rule name="Reverse Proxy to myhapidomain.com:3002" stopProcessing="true">
<match url="^blog/?(.*)" />
<action type="Rewrite" url="http://myhapidomain.com:3002/{R:1}" />
</rule>
<rule name="Reverse Proxy to myhapidomain.com:3003" stopProcessing="true">
<match url="(.*)" />
<conditions>
<add input="{HTTP_HOST}" pattern="^otherexample.com(.*)" />
</conditions>
<action type="Rewrite" url="http://myhapidomain.com:3003/{C:1}" />
</rule>
</rules>
A real example
Go to https://3001.tomssl.com/ and it will retrieve content from http://hapitest.giga.io:3001. Try it and see.
Due to the rather specific nature of what I'm doing here, I was able to write a really nice, short rewrite rule[1]. What I really want to say is, "When you give me a subdomain, use that as the port number when you go to the other website", so I ended up with this addition to the rewrite rules in the web.config
for this site:
<rule name="Proxy to HapiTest" stopProcessing="true">
<match url="(.*)" />
<conditions>
<add input="{HTTP_HOST}" pattern="^(300[1-9]|3010).tomssl.com(.*)" />
</conditions>
<action type="Rewrite" url="http://hapitest.giga.io:{C:1}" />
</rule>
All that remained was for me to add this single rule to the web.config for this website (amongst the other rules I've already got). The best place for it is just after the http to https redirect (which should be the first rule), but before everything else.
Note that I am matching any number in the range
3001
to3010
; if I were to change the range of hapi.js servers for this demo I'd need to remember to change the pattern in the reverse proxy part of theweb.config
and to open (or close) the extra ports in the firewall on the hapi.js web server.
Summary
Vanilla Azure Web Apps won't allow you to host applications on ports other than 80 (for http traffic) and 443 (for https traffic). This means that if you are using something like hapi.js with Node.js to host multiple applications on different ports, you can't use Azure Web Apps out of the box. However, you can create a reverse proxy using a single instance of Azure Web Apps to access all of your applications, regardless of which ports they're hosted on.
You can see it in action by visiting any of:
- https://3001.tomssl.com/
- ...
- https://3010.tomssl.com/ (there are ten in total)
If you're thinking I might be the sort of strangely diligent but lazy person who'd come up with this convoluted subdomain-matching-port idea to save myself a few keystrokes, you'd be right. ↩︎