Name-based Virtual Hosting とは

バーチャルホスト(Virtual Host)とは1つのサーバで複数のドメインを運用する技術のことで、Webサーバ、メールサーバなどで利用される。

(中略)

名前ベースバーチャルホストは、利用するドメイン名すべてに同じIPアドレスを使用し、1つのサーバコンピュータに必要なIPアドレスが1つで済む。
クライアントが接続したドメインにかかわらず同じIPアドレスへ接続してくるため、IP層の情報だけではドメインの判別ができない。 ドメインを判別するためにはアプリケーションプロトコルの側に接続先ドメインを判別する情報が含まれている必要がある。HTTPの場合はHostヘッダ、SMTPの場合はRcpt Toに含まれるメールアドレスのドメインで判別を行なう。

バーチャルホスト - Wikipedia

Apache では、 NameVirtualHost や VirtualHost ディレクティブを使えば実現できる。
名前ベースのバーチャルホスト - Apache HTTP サーバ バージョン 2.4

Node.js で Name-based Virtual Hosting を実現してくれそうなパッケージ4つ

Node.js ではどうやって実現するんだろうか、まさか自前で Host ヘッダを解析したりはしないだろう。と思って探してみたら、4つほどそれらしいパッケージが見つかった。

この4つの中では、 vhost の利用者が圧倒的に多そう。
昨日のダウンロード数は「30,316 downloads in the last day」と表示されている。また、 Express のサブプロジェクト的な扱いになっているようで、それだから人気が高いのかも。 Express で Name-based Virtual Hosting を実現するならこの vhost パッケージを使うのが良さそう。

bouncy はホスト名によって他のポート番号へリバースプロキシするモジュールらしい。こちらのパッケージの昨日のダウンロード数は「468 downloads in the last day」と表示されている。

他の2つはあまり使われていないように見える。

Node.js + Express 4 + vhost パッケージで Name-based Virtual Hosting を実現する

Express 3 と Express 4 では実現方法がちがうということを知った。

Migrating to Express 4 によると、 Express 3 では express.vhost という API で Name-based Virtual Hosting を実現していたらしく、また Express 4 では vhost モジュールを使えばいいらしい。

今回は Express 4 を使う。

今回の環境: Mac OS X Yosemite


$ uname -mrsv
Darwin 14.3.0 Darwin Kernel Version 14.3.0: Mon Mar 23 11:59:05 PDT 2015; root:xnu-2782.20.48~5/RELEASE_X86_64 x86_64

Node.js はインストール済み。


$ node --version
v0.10.29

$ npm --version
1.4.14

npm コマンドで Express と vhost パッケージをインストールする。


$ npm install express

$ npm install vhost

以下に、 vhost モジュールで Name-based Virtual Hosting を実現するサンプルコード。

それぞれのホスト名に対応する Express の app (Application) オブジェクトを生成し、vhost 関数でホスト名と app オブジェクトをマッピング、メインとなる app オブジェクトの use 関数でマウントする。

今回のサンプルでは foo.nilab.info, bar.nilab.info, その他のホスト名の3パターンに対応している。


$ cat app.js 

// Express.js 4
var express = require('express');

// module for Name-based Virtual Hosting
var vhost = require('vhost');

// create main app
var app = express();

// sub apps for Name-based Virtual Hosting
var foo = express();
var bar = express();
var etc = express();

// routing for Name-based Virtual Hosting
app.use(vhost('foo.nilab.info', foo))
app.use(vhost('bar.nilab.info', bar))
app.use(vhost('*', etc))

// for foo.nilab.info
foo.get('/', function(req, res){
  var msg = 'top of foo\n';
  console.log(msg);
  res.send(msg);
});

foo.get('/echo/:xxx', function(req, res){
  var msg = 'foo: ' + req.params.xxx + '\n';
  console.log(msg);
  res.send(msg);
});

// for bar.nilab.info
bar.get('/', function(req, res){
  var msg = 'top of bar\n';
  console.log(msg);
  res.send(msg);
});

bar.get('/echo/:xxx', function(req, res){
  var msg = 'bar: ' + req.params.xxx + '\n';
  console.log(msg);
  res.send(msg);
});

// for etc. domains
etc.get('/', function(req, res){
  var msg = 'top of etc\n';
  console.log(msg);
  res.send(msg);
});

etc.get('/echo/:xxx', function(req, res){
  var msg = 'etc: ' + req.params.xxx + '\n';
  console.log(msg);
  res.send(msg);
});

var server = app.listen(3000, function(){
  var host = server.address().address;
  var port = server.address().port;
  console.log('Example app listening at http://%s:%s', host, port);
});

node コマンドで Web サーバを起動。


$ node app.js 
Example app listening at http://0.0.0.0:3000

ターミナルを別に起動して、そこから Web サーバへアクセスして挙動をテストする。

まずは hosts ファイルにテスト用のドメインを追記。


$ vim sudo /private/etc/hosts

$ cat /private/etc/hosts

127.0.0.1	localhost
255.255.255.255	broadcasthost
::1             localhost 

127.0.0.1       foo.nilab.info
127.0.0.1       bar.nilab.info

curl コマンドで Host ヘッダを指定して、 Web サーバへアクセスする。


$ curl -H 'Host:foo.nilab.info' http://foo.nilab.info:3000/
top of foo

$ curl -H 'Host:foo.nilab.info' http://foo.nilab.info:3000/echo/aaa
foo: aaa

$ curl -H 'Host:bar.nilab.info' http://bar.nilab.info:3000/
top of bar

$ curl -H 'Host:bar.nilab.info' http://bar.nilab.info:3000/echo/aaa
bar: aaa

$ curl -H 'Host:localhost' http://localhost:3000/
top of etc

$ curl -H 'Host:localhost' http://localhost:3000/echo/aaa
etc: aaa

ちゃんと想定通りの挙動をしてくれた。

tags: node.js express

Posted by NI-Lab. (@nilab)