Safely switching domains with an Express reverse proxy

Daniel Palumbo
Pillow.codes
Published in
3 min readJul 5, 2017

Ever transition to a new domain, but needed some behavior from your original domain to be identical?

Recently we switched our website’s domain from www.pillowhomes.com to www.pillow.com. While this is a simple process, we had external services dependent on our old domain. For example, we expose certain endpoints (e.g. https://www.pillowhomes.com/api/services/endpoint) to Sendgrid or Checkr. One solution was to change every webhook url on each service simultaneously with the domain release. However, this route was not ideal as it depended on the third party services to implement changes on their side to work with us. Indeed, we were not able to go with this option because one of the services we use needed an entire week to implement the change to production. Besides, there’s also the additional risk of human error where we fail to update a particular webhook.

We also thought about redirecting the requests of the third party services. This option didn’t work either because the external services do not follow a 301 response from the server. They need a 200 response from our endpoints.

Another option we considered was pointing both www.pillowhomes.com and www.pillow.com to our application server. However, I didn’t want to complicate the app’s routing logic, and also the service in question is incompatible with our new SSL setup on www.pillow.com (SNI).

Our solution was to create a reverse proxy between our app and our dependent web services in order to make https://www.pillowhomes.com respond with data on behalf of our main domain. A reverse proxy can solve our problem because our original domain points to it, but it behaves like the application server to the outside world.

Original Source: https://en.wikipedia.org/wiki/Reverse_proxy#/media/File:Reverse_proxy_h2g2bob.svg

So this reverse proxy server needed to be responsible for:

  • Forwarding general web requests to our new domain
  • Acting as a reverse proxy with no forwarding for select endpoints

Setting up the reverse proxy

Our go-to hosting solution is Heroku, and it’s a great option for our reverse proxy. We set up a tiny Node.js Express web server to handle the needed responsibilities. It’s fast, lightweight, and easy to understand/deploy (and all of our developers code Javascript). Our main Rails application server (also hosted on Heroku) needs to have no knowledge of its existence.

Ultimately, we only needed a single index.js page of application code to get up and running on Heroku.

First, we initialize the Express app, as well as SuperAgent as our intermediate http client:

var express = require('express');
var app = express();
var agent = require('superagent');

Normally, Express parses incoming requests into Javascript objects. However, we need to prevent that in our case because we need to send the raw data from the service requesting https://www.pillowhomes.com to https://www.pillow.com. We want the job of parsing the requests to be up to our application server.

To do so, we set up the following code:

app.use(function(req, res, next) {
var data = '';
req.setEncoding('utf8');
req.on('data', function(chunk) {
data += chunk;
});
req.on('end', function() {
req.body = data;
next();
});
});
// Source: https://stackoverflow.com/a/9920700

This overwrites the body of the request with the raw body data as opposed to it being an object parsed by Express.

Then it’s a matter of intercepting the requests relevant to our webhooks (in this example, anything POST’ed to /api/services/*):

app.post('/api/services/*', function(request, response) {
agent
.post("https://www.pillow.com" + request.originalUrl)
.buffer(true)
.send(request.body)
.set(request.headers)
.end(function(err, res){
response.send(res.text)
})
});

We pass through the headers and body of the original request. Calling .buffer(true) enables response buffering and ensures the response event isn’t emitted without waiting for the entire body to be available. This is important because without it, we wouldn’t be able to intercept the body of the response from our application web server. Finally, we redirect everything else:

app.get('/*', function(request, response) {
response.redirect(301, 'https://www.pillow.com' + request.originalUrl);
});

Note, we put our www.pillowhomes.com SSL cert on this new reverse proxy server so https would continue working normally. Also, for completeness’s sake, know that we set up a Procfile for Heroku with web: node index.js and a package.json which includes our two dependencies (Express and SuperAgent). Then after a solitary git push heroku master, our Express proxy server is up and running as intended!

Thank you Carlos for all the help with the domain transition, and the Pillow team for reviewing this post!

--

--