diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/README.md b/README.md new file mode 100644 index 0000000..08735f2 --- /dev/null +++ b/README.md @@ -0,0 +1,81 @@ +virtualhost +=========== + +Make your HTTP server hostname-aware **very simply**. + +You define the handler for each server name, and that will return the final handler to be passed to your HTTP server. + +Works fine with Express. + +Installation +------------ + +`npm install virtualhost` + +Usage +----- + +```javascript +var virtualhost = require('virtualhost'); +var server = http.createServer(virtualhost(servers, catchAll)); +``` + +* `servers` is a hash of server's configuration, each one having following options: + * `pattern` can be a string (hostnames will simply be compared for equality), or a regular expression (you could use `/^hello\.(fr|com)$/i` for example to use this handler for `hello.fr` and `hello.com`, or `/\.domain.tld$/` to match all subdomains of `domain.tld`). Think about anchors (`^` and `$`) when using regular expression as pattern. + * `handler` is a `function (req, res)`. Request matching pattern will simply be forwarded to this handler. + * `with_port` will include the port in the comparison. Default comparison ignores it, which means `pattern: "domain.tld" will match `domain.tld:8080` and `domain.tld:3000` the same way. If you enable this option, you **have to** include port in your pattern. +* `catchAll` is the default handler used when no server matched hostname. It's not mandatory, and defaults to a simple 404. + +### Shorter usage + +`servers` can also be a simple hash of the form `pattern: handler`. + +For example: + +```javascript +virtualhost({ + "one.mydomain.tld": function (req, res) {…}, + "two.mydomain.tld": function (req, res) {…} +}); +``` + +is strictly equivalent to + +```javascript +virtualhost({ + "one.mydomain.tld": { + pattern: "one.mydomain.tld", + handler: function (req, res) {…} + }, + "two.mydomain.tld": { + pattern: "two.mydomain.tld", + handler: function (req, res) {…} + } +}); +``` + +Of course you can mix both syntax. + +Sample usage +------------ + +```javascript +// Example of standard handler +// This one will simply write "handler1" at "sub.domain.tld/*" +var handler1 = function (req, res) { res.end('handler1') }; + +// Example of Express 3.x app +// Good guy Express now simply returns standard handler, which makes this directly usable in virtualhost :) +// This one will write "handler2 (www.)" at "www.domain.tld/" and "handler2 (undefined)" at "domain.tld/" +var handler2 = express().get('/', function (req, res) { res.end('handler2 (' + req.virtualhost.match[1] + ')' }); + +// Example of virtualhost configuration +var apps = { + // Shortcut hostname→handler + sub.domain.tld: handler1, + // Full config with RegExp pattern + express: { pattern: /^(www\.)?domain\.tld$/, handler: handler2 } +}; + +http.createServer(virtualhost(apps)).listen(); +``` diff --git a/package.json b/package.json new file mode 100644 index 0000000..a39999d --- /dev/null +++ b/package.json @@ -0,0 +1,18 @@ +{ + "author": "Nicolas Chambrier (http://naholyr.fr)", + "name": "virtualhost", + "description": "Dispatch HTTP request to a handler depending on hostname", + "version": "0.0.1", + "homepage": "https://github.com/lmtm/node-virtualhost", + "repository": { + "type": "git", + "url": "git://github.com/lmtm/node-virtualhost.git" + }, + "main": "virtualhost.js", + "dependencies": {}, + "devDependencies": {}, + "optionalDependencies": {}, + "engines": { + "node": "*" + } +} diff --git a/virtualhost.js b/virtualhost.js new file mode 100644 index 0000000..4e4b6e6 --- /dev/null +++ b/virtualhost.js @@ -0,0 +1,65 @@ + +var url = require('url'); + +module.exports = handler; + +function handler (servers, catchAll) { + catchAll = catchAll || _catchAll; + + // Check catch-all + if (typeof catchAll !== 'function') throw new Error('[virtualhost] Catch-all should be a valid callback'); + // Check servers configuration + for (var server in servers) { + var conf = servers[server]; + if (typeof conf === 'function') { + // Direct function assignment + servers[server] = { pattern: server, handler: conf }; + } else if (typeof conf === 'object') { + // Check options + if (typeof conf.handler !== 'function') throw new Error('[virtualhost] Invalid configuration for server "' + server + '": "handler" should be a valid callback'); + if (!conf.pattern || (typeof conf.pattern !== 'string' && !(conf.pattern instanceof RegExp))) throw new Error('[virtualhost] Invalid configuration for server"' + server + '": "pattern" should be a string or a RegExp'); + } else { + // Invalid type + throw new Error('[virtualhost] Invalid configuration for server "' + server + '": object or function expected'); + } + } + + // Valid options, return meta-handler + return function (req, res) { + // Retrieve hostname + var location = url.parse('http://' + req.headers.host); + // Define "req.virtualhost" (can be used by user) + req.virtualhost = { + hostname: location.hostname || '', + port: location.port, + match: false + }; + // Browser available handlers and find the first one matching hostname + for (var server in servers) { + req.virtualhost.match = matchHost(req.virtualhost, servers[server]); + if (req.virtualhost.match) { + req.virtualhost.name = server; + return servers[server].handler.call(this, req, res); + } + } + // None found, fallback to catch-all + req.virtualhost.match = null; + return catchAll.call(this, req, res); + }; +} + +function matchHost (hostInfo, conf) { + var pattern = conf.pattern; + var host = hostInfo.hostname + (conf.with_port ? (':' + hostInfo.port) : ''); + if (conf.pattern instanceof RegExp) { + console.log(host, conf.pattern, host.match(conf.pattern)); + return host.match(conf.pattern); + } else { + return host === conf.pattern; + } +} + +function _catchAll (req, res) { + res.writeHead(404); + res.end('Invalid host name'); +}