スマートフォン・タブレットからインターネットサーバーオペレーション 2014

APPW.jp 2014

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 チャットサンプルを改造

「さくらのVPS」2014年11月27日にリニューアル

SSL ボックス が SHA-2 の SSL 証明書発行に対応

Bottle – Cork サンプルを Apache mod_wsgi で試してみます

Mosquitto の Bridge を SSL で試します

Mosquitto へ SSL で接続してみる

Mosquitto の SSL 対応を試してみる

Mosquitto Publish と Server-Sent Events で Python チャットサンプルを改造

Python WebSocket のチャットサンプルに、MongoDB 保存を付け加えました

Mosquitto の payload を MongoDB に保存してみる

Mosquitto を VPN で試しました

via IPv4

カテゴリー