0%

Hapi.js with Socket.IO - Part1: Hello World!

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の使い方は以下のサイトを参考にして勉強します。

プロジェクト

適当なディレクトリにプロジェクトを作成します。リポジトリはこちらです。

$ 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

以下のバージョンのパッケージを使います。

~/node_apps/docker-hapi-socketio/package.json
{
"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は実装していません。

~/node_apps/docker-hapi-socketio/app.js
'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変数のconnecttweetイベントにlistenerをアタッチします。

~/node_apps/docker-hapi-socketio/templates/index.html
<!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しています。

~/node_apps/docker-hapi-socketio/app.js
var tweet = {text: 'hello world!'};
var interval = setInterval(function () {
socket.emit('tweet', tweet);
}, 5000);

ブラウザサイドにも5秒間隔でメッセージがappendされていきます。

hello-socketio.png