MongoDB に保存した Mosquitto の payload を Server-Sent Events で表示してみる
前回の「Mosquitto の payload を MongoDB に保存してみる」の続篇です。
今回は、その MongoDB から取り出して、HTML5 の Server-Sent Events(EventSource)を利用してブラウザに表示していくプログラムを PHP で作成します。Server-Sent Events については、以前の記事「Server-Sent Events(EventSource)を試してみます」がベースとなります。
まず、ConoHa VPS の CentOS 6.5 の環境で、PHP から MongoDB にアクセスするため、MongoDB PHP ドライバをインストールします。
sudo pecl install mongo
そして、php.ini に次の行を追加します。
extension=mongo.so
次は、ブラウザ側の HTML です。
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script> </head> <body> <div id="out"> <?php // DBへ接続 $mongo = new Mongo(); // データベースを指定 $db = $mongo->selectDB("chattest"); // コレクションを指定 $con = $db->selectCollection("conn_log"); $col = $db->selectCollection("chat_log"); // コレクションのドキュメントを取得 $connid = time(); $lastid = 0; $wid = $lastid; $cursor = $col->find()->sort(array('created_at'=>-1))->limit(5); foreach ($cursor as $doc) { echo '<p>' . $doc["text"] . '</p>'; $wt = date('Y/m/d H:i:s', $doc["created_at"]); echo '<p>- ' . $wt . ' -</p>'; if ($wid == $lastid) { $wid = $doc["created_at"]; } } $con->insert(array('connid' => $connid, 'lastid' => $wid, 'user' => 'php')); // 接続の切断 $mongo->close(); ?> </div> <script> <?php echo " var lastid = '" . $wid . "';" . PHP_EOL; ?> function sse(){ if (typeof (EventSource) !== 'undefined') { <?php echo " sv = 'sse.php?id=" . $connid . "';" . PHP_EOL; ?> source = new EventSource(sv); source.addEventListener('messages', function(event){ if (event.lastEventId > lastid) { document.getElementById('out').innerHTML = event.data + document.getElementById('out').innerHTML ; lastid = event.lastEventId; } },false); source.addEventListener('end', function(event){ if (event.lastEventId > lastid) { document.getElementById('out').innerHTML = event.data + document.getElementById('out').innerHTML ; lastid = event.lastEventId; } source.close(); },false); source.onerror = function (event) { if (source.readyState === EventSource.CLOSED) { document.getElementById('out').innerHTML = '<p>終了しています。</p>' + document.getElementById('out').innerHTML ; } else if (source.readyState === EventSource.OPEN) { document.getElementById('out').innerHTML = '<p>終了します。</p>' + document.getElementById('out').innerHTML ; source.close(); } else if (source.readyState === EventSource.CONNECTING) { } } } else { document.getElementById('out').innerHTML = '<p>Server-Sent Events はサポートされていません。</p>' ; } } window.onload = sse; </script> </body> </html>
初期表示に最新の5件を表示するため、PHP で MongoDB にアクセスするコードが含まれています。
さらに、HTML5 の Server-Sent Events の実際のしくみを考慮して、接続ごとに Server-Sent Events で送信した最終ドキュメントの created_at を管理して、次回の送信データの判定に使用するしくみを追加しています。
いくつか方式を考えて試した痕跡がうっすらとコードに残ってしまいましたが、最終的には、EventSource に設定した url の sse.php に GET メソッドで 識別情報を渡すことにして、その内容には、最初にブラウザから接続した UNIXTIME をセットすることにしました。
この情報と最終ドキュメントの created_at を conn_log コレクションに保存して、送信データのダブりやモレを回避するようにしました。大量アクセスには向かない方法ですので検討の余地がありますが、先に進みます。
続いて、サーバー側です。
前回送信したドキュメント以降を検索しています。
<?php header('Content-Type: text/event-stream'); header('Cache-Control: no-cache'); $lastid = 0; $connid = 0; if (isset($_GET['id'])) { $connid = intval($_GET['id']); } // DBへ接続 $mongo = new Mongo(); // データベースを指定 $db = $mongo->selectDB("chattest"); // コレクションを指定 $con = $db->selectCollection("conn_log"); $col = $db->selectCollection("chat_log"); // コレクションのドキュメントを取得 $curcon = $con->findOne(array('connid' => $connid)); $lastid = $curcon["lastid"]; $wid = $lastid; $cursor = $col->find(array('created_at' => array('$gt' => $lastid)))->sort(array('created_at' => -1)); echo 'event: ' . 'messages' . PHP_EOL; foreach ($cursor as $doc) { if ($wid == $lastid) { $wid = $doc["created_at"]; } echo 'data: <p>' . $doc["text"] . '</p>' . PHP_EOL ; $wt = date('Y/m/d H:i:s', $doc["created_at"]); echo 'data: <p>- ' . $wt . ' -</p>' . PHP_EOL ; } echo 'id: ' . strval($wid) . PHP_EOL; $curcon["lastid"] = $wid; $con->save($curcon); // 接続の切断 $mongo->close(); $retry = 10000; if ( $retry == 0 ) { echo 'event: ' . 'end' . PHP_EOL; } else { echo 'retry: ' . $retry . '' . PHP_EOL; } echo PHP_EOL; ob_flush(); flush(); ?>
第1回の「Mosquitto の payload を MongoDB に保存してみる」では、Mosquitto の payload を MongoDB に保存しました。
今回の第2回は、HTML5 の Server-Sent Events と PHP でブラウザに表示しました。
次回の第3回は、Python で WebSocket を利用した簡単なチャットプログラムを参考に MongoDB アクセスを書き加えてみます。
「Python WebSocket のチャットサンプルに、MongoDB 保存を付け加えました」
第4回は、第3回の Python の WebSocket チャットプログラムを参考にしつつ、ブラウザから POST メソッドで Mosquitto へ Publish し、また、Server-Sent Events でブラウザに表示する方式を Python で試してみます。
「Mosquitto Publish と Server-Sent Events で Python チャットサンプルを改造」