Thursday, April 14, 2016

node.js virtual hosts websites/apps on http and https

In this example I make 2 Express websites/apps (app1 & app2) and just give them some basic routing for "/".

"use strict";
var express = require("express");

/////////////////////////  
// certificates & apps //  
/////////////////////////  
var tls = require('tls');
var fs = require('fs');

var app1 = express();
app1.all("/", function(req, res) {
  res.send('Hello World, site #1');
});

var app2 = express();
app2.all("/", function(req, res) {
  res.send('Hello World, site #2');
});

In a next step I read their SSL keys from disk (app 1&2 .key and .crt) and store them each in a literal object together with references to the 2 apps.


const site1 = {
  app: app1,
  context: tls.createCredentials({
    key: fs.readFileSync('app1.key').toString(),
    cert: fs.readFileSync('app1.crt').toString()
  }).context  
};

const site2 = {
  app: app2,
  context: tls.createCredentials({
    key: fs.readFileSync('app2.key').toString(),
    cert: fs.readFileSync('app2.crt').toString()
  }).context  
};

I make life easy and connect these app/ssl pairs with the domain names in an object. Easy setup / config.

var sites = { "www.site1.com": site1, "site1.com": site1, "www.site2.com": site2, "site2.com": site2
};

I then make a global Express app caled "exp", to which I add the domain names (in this example with and without www) and connect them to the correct apps.
If you want to test this example out, put these 4 domain names (www.)site(1/2).com in your /etc/hosts file.

////////////////////////
// Global express app //
////////////////////////
var vhost = require("vhost");

var exp = express();
for (let s in sites) {
  console.log("add app for " + s);
  exp.use(vhost(s, sites[s].app));
}
Finally I create a http and https server with the global Express app as request listener.
//////////
// http //
//////////
var http = require('http');
var httpServer = http.createServer(exp);
httpServer.listen(8080, function () {
   console.log("Listening http on port: " + this.address().port);
});

For the https server I pass in an option object with a SNICallback to return for each domain the correct SSL certificate (as we are serving more than 1 https server on the same IP/port)

/////////// // https // /////////// var secureOpts = { SNICallback: function (domain, cb) { if (typeof sites[domain] === "undefined") { cb(new Error("domain not found"), null); console.log("Error: domain not found: " + domain); } else { cb(null, sites[domain].context); } }, key: fs.readFileSync('ws.key').toString(), cert: fs.readFileSync('ws.crt').toString() }; var https = require('https');
var httpsServer = https.createServer(secureOpts, exp);
httpsServer.listen(4433, function () {
   console.log("Listening https on port: " +  + this.address().port);
});