Hapi.jsのREST APIが一通り動くようになったので次はHapi.jsにSocket.IOサーバーを作成してみます。Using hapi.js with Socket.ioにとても良いSocket.IOの解説があります。著者のMatt Harrison氏はManningでHapi.js in ActionをMEAPで執筆中です。ブログが非常にわかりやすいので著書も期待できます。
リソース
Hapi.jsとSocket.IOの使い方は以下のサイトを参考にして勉強します。
- Using hapi.js with Socket.io
- SOCKET.IO WITH HAPI.JS
- Hapi.js with Socket.io — Where is socket.io.js?
- Hapi + socket.io Example
- Understanding Socket.IO
プロジェクト
適当なディレクトリにプロジェクトを作成します。リポジトリはこちらです。
$ cd ~/node_apps/docker-hapi-socketio
$ tree -a -L 2
.
├── .dockerignore
├── .env
├── .gitignore
├── Dockerfile
├── README.md
├── app.js
├── docker-compose.yml
├── node_modules -> /dist/node_modules
├── npm-debug.log
├── package.json
└── templates
└── index.html
以下のバージョンのパッケージを使います。
{
"name": "docker-hapi-socketio",
"description": "docker-hapi-socketio",
"version": "0.0.1",
"private": true,
"dependencies": {
"hapi": "^8.6.1",
"socket.io": "^1.3.5",
"handlebars": "^3.0.3",
"dotenv": "^1.1.0"
},
"scripts": {"start": "node app.js"}
}
サーバーサイド
hapi serverのlisnterはhttp server
hapi server(var server = new Hapi.Server();)にconnectionを作成(server.connection({ port: 4000 });)すると、内部で新しいNode.jsのhttp server(var listener = require('http').createServer(handler);)が作成されます。このhttp serverインスタンスはlistenerプロパティ(var listener = server.listener;)になります。
Socket.IOに渡すappもhttp server
Socket.IOをrequireするときに渡すvar io = require('socket.io')(app);のapp変数もhapi serverのlistenerと同様にhttp server(var app = require('http').createServer(handler);)です。ただし以下のようにhapi serverにconnectionを作成して作成した8080ポートのhapi server.listener(node http server)をSocket.IOに渡すとセットアップ処理でnode http serverのrequestイベントのリスナーがすべて削除されてしまいます。
var Hapi = require('hapi')
var server = new Hapi.Server();
server.connection({ port: 8080 });
var io = require('socket.io')(server.listener);
io.on('connection', function (socket) {
console.log('connected');
});
server.start();
API用のhttp serverとSocket.IO用のhttp server
hapiはportを指定して内部で複数のnode http serverを起動することができます。下の例では1つのhapi serverでREST APIを8080ポートでLISTENするHTTPサーバーと、8080ポートでLISTENするSocket.IOサーバーの2つが起動しています。HTTPサーバーはいまのところ/からindex.htmlのエントリポイントを提供するだけでREST APIのRouteは実装していません。
'user strict';
var Hapi = require('hapi'),
server = new Hapi.Server(),
Path = require('path');
require('dotenv').load();
server.connection({ port: process.env.API_PORT, labels: ['api'] });
server.connection({ port: process.env.SOCKETIO_PORT, labels: ['twitter'] });
server.views({
engines: {
html: require('handlebars')
},
path: Path.join(__dirname, 'templates')
});
server.select('api').route({
method: 'GET',
path: '/',
handler: function (request, reply) {
reply.view('index',
{ socketio_host: (process.env.PUBLIC_IP+':'
+process.env.SOCKETIO_PORT)});
}
});
var io = require('socket.io')(server.select('twitter').listener);
io.on('connection', function (socket) {
console.log('connected!');
var tweet = {text: 'hello world!'};
var interval = setInterval(function () {
socket.emit('tweet', tweet);
}, 5000);
socket.on('disconnect', function () {
clearInterval(interval);
});
});
server.start();
クライアントサイド
クライアントサイドはSPAで実装します。HTTPサーバー(8000)がserveするindex.htmlがエントリポイントになります。index.htmlからSocker.IOサーバー(8080)がserveするsocket.io.jsのクライアントライブラリをロードしてSocket.IOサーバー(8080)に接続します。socket変数のconnectとtweetイベントにlistenerをアタッチします。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>tweet</title>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="http://{{socketio_host}}/socket.io/socket.io.js"></script>
<script>
$(function() {
var socket = io.connect("http://{{socketio_host}}");
socket.on("connect", function() {
console.log("Connected!");
});
socket.on("tweet", function(tweet){
console.log(tweet);
$("#tweet").prepend(tweet.text + "<br>");
});
});
</script>
</head>
<body>
<div id="tweet"></div>
</body>
</html>
このindex.htmlはHandlebars.jsのテンプレートです。{{socketio_host}}は.envに定義した環境変数をdotenvを使ってロードした値が入っています。
プログラムの実行
Docker Composeからhapiサービスをupします。ブラウザから.envファイルに定義したIPアドレスとポートに接続するとconnected!のログが出力されます。
$ docker-compose up
Recreating dockerhapisocketio_hapi_1...
Attaching to dockerhapisocketio_hapi_1
hapi_1 |
hapi_1 | > docker-hapi-socketio@0.0.1 start /app
hapi_1 | > node app.js
hapi_1 |
hapi_1 | connected!
サーバーサイドでは5秒間隔でメッセージをemitしています。
var tweet = {text: 'hello world!'};
var interval = setInterval(function () {
socket.emit('tweet', tweet);
}, 5000);
ブラウザサイドにも5秒間隔でメッセージがappendされていきます。
