I'm fooling around a little with Express and I'm wondering, what the "most correct" way is to handle multiple domains which are linked to the same server.
Lets assume we have
which all point to 111.222.333.444
. That machine is running NodeJS with Express. My current solution looks like this:
var express = require( 'express' ),
app = module.exports = express.createServer(),
// ... more lines ...
app.get( '/', routes.index.bind( app ) );
Thus far this is pretty straightforward. The only exception so far is in my app.configure
call, where I didn't make a call to .use( express.static() )
. Thats because the .routes.index()
method looks like so right now:
var fs = require( 'fs' ),
// ... more lines ...
exports.index = function( req, res ) {
var host = /(\w+\.)?(.*)\.\w+/.exec( req.header( 'host' ) ),
app = this;
switch( host[ 2 ] ) {
case 'foo':
app.use( express.static( '/var/www/foo' ) );
fs.readFile( '/var/www/foo/index.html', 'utf8', fileReadFoo );
break;
case 'bar':
app.use( express.static( '/var/www/bar' ) );
fs.readFile( '/var/www/bar/index.html', 'utf8', fileReadBar );
break;
case 'baz':
// ... lines ...
res.render( 'index', { title: 'Baz Title example' } );
break;
default:
res.send('Sorry, I do not know how to handle that domain.');
}
function fileReadFoo( err, text ) {
res.send( text );
}
function fileReadBar( err, text ) {
res.send( text );
}
};
What happens here is, that I analyse the req.header
for the host
entry and parse the domain name. Based on that, I call the .static()
method so Express can serve the right static resources etc., furthermore, I just simply read and send the contents of the index.html files. I tried to use Jade aswell for serving plain HTML files, but the include
directive in Jade only accepts relative pathes.
However, this indeed works, but I'm pretty unsure if that is a good practice.
Any advice / help welcome.
Update
I think I need to make this more clear. I'm not a beginner by any means. I'm very aware how ES works and other servers like NGINX. I'm looking for qualified answers on what the right thing with NodeJS/Express is. If it doesn't make any sense to use Node/Express for that, please elaborate. If there is a better way to do this with Node/Express, please explain.
Thanks :-)
Vadim was almost onto the right idea. You can configure how to respond to each domain with the vhost
middleware:
// `baz.com`
var app = express.createServer();
app.get( '/', routes.index );
// ...
express.createServer()
.use( express.vhost( 'foo.com', express.static( '/var/www/foo' ) ) )
.use( express.vhost( 'bar.net', express.static( '/var/www/bar' ) ) )
.use( express.vhost( 'baz.com', app ) )
.use( function( req, res ) {
res.send('Sorry, I do not know how to handle that domain.');
})
.listen( ... );
routes.index
can then be simplified to handle only baz.com
requests:
exports.index = function( req, res ) {
// ... lines ...
res.render( 'index', { title: 'Baz Title example' } );
};
Edit
As for comparisons:
The switch
would effectively be done first and would determine how to handle all requests based on the host
-- similar to:
express.createServer().use(function( req, res, next ) {
switch( req.host ) {
case 'foo.com': express.static( '/var/www/foo' )( req, res, next ); break;
case 'bar.net': express.static( '/var/www/bar' )( req, res, next ); break;
case 'baz.com': app.handle( req, res, next ); break;
default: res.send( ... );
}
}).listen( ... );
It allows you to set the stack on start so any middleware is available immediately:
server.stack = [
express.vhost( 'foo.com', ... ),
express.vhost( 'bar.net', ... ),
express.vhost( 'baz.com', ... ),
[Function]
];
These also reflect the 2 possible sources of issues you might have:
Same stack without filters
Each Application
only has 1 middleware stack, which all of the middleware you're using is being added directly to with app.use(...)
. Despite adding some under conditions, you're still getting:
app.stack = [
// ...,
app.router,
express.static( '/var/www/foo' ),
express.static( '/var/www/bar' )
];
And the condition won't change how the static
middlewares respond -- which is by req.path
, not req.host
-- only when they're in the stack to start responding.
State of the stack
And, if the static
middlewares aren't added until after another request has been made, then I take it they aren't available immediately:
// GET http://foo.com/file 404
app.stack = [ app.router ]
// GET http://foo.com/ 200
app.stack = [ app.router, express.static( '/var/www/foo' ) ]
// GET http://foo.com/file 200
app.stack = [ app.router, express.static( '/var/www/foo' ) ]
This may also mean the same static
middleware could be added to the stack multiple times:
// 3x GET http://foo.com/
app.stack = [
app.router,
express.static( '/var/www/foo' ),
express.static( '/var/www/foo' ),
express.static( '/var/www/foo' )
]
And having their addition depend on other requests also suggests a possible race conditions:
// was `foo.com` or `bar.net` first?
app.stack = [
app.router,
express.static( ? ),
express.static( ? )
]
I like to use bouncy as a front end reverse-proxy - this lets you run totally different express stacks as different server processes (each with different features and separated for robustness)...
You can then decide how to route to different ports, it works fine with WebSockets.
var bouncy = require('bouncy');
bouncy(function (req, bounce) {
if (req.headers.host === 'bouncy.example.com') {
bounce(8000);
}
else if (req.headers.host === 'trampoline.example.com') {
bounce(8001)
}
}).listen(80);
I use nginx as front-server with node.js. It's the best solution to organize domains, static content delivery, load control and many other powerful features. Absolutely no need to do it in node event loop. This will determine the speed of your application.
Going somewhat against the flow I would have to say that I don't see how doing something like that makes sense. Node.js has a one process design constrain. Throttling IO is hard work for one web application let alone a few. Trying to abstract that by having several applications will over complicate the code making it unreadable. A bug in a single application can impact all applications. This is a very unstable configuration.
If you're looking to see if you can do it, I guess the answer is yes. Something like vhost suggested here in another answer. On the other hand, I would probably go with some kind of separation of concerns and constrain my app. If it's to be placed in the same server box I would do the following:
In short, don't think about going big, think about going wide.
Drawback: It's a different way of scaling. There is only so much of juice you can get out of a single box. I'm not sure how to scale this idea when we are talking about a "multi-box" environment.
It's quite difficult to answer this without knowing the constraints that meant you must run the hosts in the same process, so I'll echo what others have said, but hopefully give some more context.
The "most correct" thing to do with node is to run the hosts in multiple processes and to reverse proxy the requests in another process. Running multiple sites in the same process is riddled with problems, not least of which is a crash in one, brings them all down and requires a restart of the whole process. Node's philosophy is very unix-like, it encourages keeping programs small and separate. If you separate the processes, you'll get a natural segregation of applications. If you pursue a monolithic design, you'll have to write and maintain logic to separate logging from the different sites and error handling logic will become more complicated. There are no doubt other cases where you'll need to branch logic based on host, but your application design will encourage rather than discourage that.
If you're averse to other technology stacks (or are concerned e.g. by nginx's current lack of support of websockets in the stable branch), then there are a few solid reverse proxys written in nodejs, including nodejitsu's http-proxy which is used in their production PaaS stack on Joyent's cloud and bouncy (mentioned in another answer) which has less features, but I believe is being used in production on browserling.
The author of bouncy actually goes as far as suggesting it as one of the most important architectural patterns in designing a nodejs system. You may also notice that his answer has been upvoted by some of the core node committers.
Since Express uses Connect, I'm pretty sure you can use Connect's virtual host middleware. It operates similar to other vhost modules on other products. I don't have multiple domains to test and show you proper code, but I would think it's something like this:
express.createServer()
.use(express.vhost('hostname1.com', require('/path/to/hostname1').app)
.use(express.vhost('hostname2.com', require('/path/to/hostname2').app)
.listen(80)
If you get to the point where one Express server isn't enough, then look into using the Node.Cluster from the API. If that also isn't enough, then the current practice is to put a asnyc reverse proxy such as Nginx in front of your Express servers and point the proxies to your Express servers.