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

APPW.jp 2014

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

前回の「Python WebSocket のチャットサンプルに、MongoDB 保存を付け加えました」の Python WebSocket チャットプログラムをベースに、ブラウザから POST メソッドで Mosquitto へ Publish し、また、Server-Sent Events でブラウザに表示する大改造を、今回は行います。

前回は、フレームワークに Flask を利用しましたが、今回は Bottle を利用します。WSGI Server に gevent を利用します。

次のプログラムは、mqtt-chat.py でセーブしました。

import sys,os
import gevent
from gevent import monkey; monkey.patch_all()
from gevent.pywsgi import WSGIServer
from bottle import get, post, route, view, request, response, redirect, Bottle
import time
from datetime import datetime

sys.path.append(os.path.dirname(os.path.abspath(__file__)) + '/../model')
from mchat import (ChatLog, ConnLog, connecter)
from mqttc import (MQTTpub)

app = Bottle(__name__)

def getlist(lastid):

    wlist = []
    wid = lastid

    if lastid == '0':
        posts = ChatLog.objects.order_by("-created_at")[:5]
    else:
        posts = ChatLog.objects(created_at__gt = int(lastid)).order_by("-created_at")

    for post in posts:
        wt = '<p>' + post.text + '</p>'
        vt = datetime.fromtimestamp(post.created_at)
        wt = wt + '<p>- ' + vt.strftime('%Y/%m/%d %H:%M:%S') + ' -</p>'
        wlist.append(wt)
        print 'text=%s' % post.text
        if wid == lastid:
            wid = str(post.created_at)

    return wid, wlist


@app.post('/post')
def post():

    message = request.forms.get('message')
    print "post: %s" % message
    MQTTpub(message)

@app.get('/stream')
def stream():

    lastid = '0'

    connid = request.query.id
    conns = ConnLog.objects(connid = int(connid)).order_by("-lastid")
    for conn in conns:
        if lastid == '0':
            lastid = str(conn.lastid)
            break

    response.content_type  = 'text/event-stream'
    response.cache_control = 'no-cache'
    
    wlist = []
    wid = lastid

    wid, wlist = getlist(lastid)

    if wid != lastid:
        conns.update(set__lastid=int(wid))

    yield 'event: messages\n'
    for value in wlist:
        yield 'data: %s\n' % value
    yield 'id: %s\n' % wid
    yield 'retry: 10000\n'
    yield '\n'

@app.route('/')
@view('mqtt-chat')
def index():

    lastid = '0'
    wlist = []
    wid = '0'

    wid, wlist = getlist(lastid)

    name = 'MQTTC'
    now = int(time.mktime(datetime.now().timetuple()))
    ConnLog(connid=now, lastid=int(wid), user=name).save() 

    return dict(data=wlist, id=str(now), lastid=str(wid))

if __name__ == '__main__':
    connecter()
    server = WSGIServer(('0.0.0.0', 8080), app)
    server.serve_forever()

Server-Sent Events を利用するにあたっては、第2回の「MongoDB に保存した Mosquitto の payload を Server-Sent Events で表示してみる」の方式を前提としています。

次はテンプレートです。

mqtt-chat.py をセーブしたディレクトリの直下に views ディレクトリを作成して mqtt-chat.html でセーブしました。

<!doctype html>
<title>chat</title>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
</head>
<body>
<h3>Chat</h3>
<p>Message: <input id="in" /></p>
<div id="out">
  % for item in data:
    {{!item}}
  % end
</div>
<script>
  $('#in').keyup(function(e){
    if (e.keyCode == 13) {
      $.post('/post', {'message': $(this).val()});
      $(this).val('');
    }
  });
</script>
<script>
  var lastid = {{lastid}};
  function sse(){
    var source;
    if (typeof (EventSource) !== 'undefined') {
        source = new EventSource('/stream?id={{id}}');

        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>

MVC のモデルにあたる mchat.py は、次のようにしました。

from mongoengine import *

class ChatLog(Document):
    text = StringField(required=True)
    user = StringField(required=True)
    created_at = LongField(required=True)

class ConnLog(Document):
    connid = LongField(required=True)
    lastid = LongField(required=True)
    user = StringField(required=True)
 
def connecter():
    con = connect('chattest')
    print con

さらに、Mosquitto への Publish の部分も別モジュールにして、mqttc.py でセーブしました。

import paho.mqtt.client as paho

def MQTTpub(msg):
    mqttc = paho.Client(client_id="gwclient", clean_session=True, protocol=paho.MQTTv311)
    mqttc.username_pw_set("mqtt", "mqttpasswd")
    mqttc.connect("127.0.0.1", 1883, 60)
    mqttc.publish("test/chat", msg, 0)
    mqttc.disconnect()

サーバー環境は、ConoHa VPS で、CentOS 6.5 です。

Android の VNC クライアントアプリ bVNC Pro から確認を行いました。

image

これまでの改造サンプルプログラムそのままで、サービス公開するようなことは、セキュリティなどからみても、ありえないことですが、Server-Sent Events の部分を公開ページに組み入れて、ひとりタイムライン公開のようなことはできそうです。

今回の Server-Sent Events では、約 10 秒間隔で retry 再接続するように設定しています。ブラウザからメッセージを POST してから、そのメッセージがブラウザに表示されるまで、そのぐらいの時間がかかります。

第1回の「Mosquitto の payload を MongoDB に保存してみる」では、Mosquitto の payload を MongoDB に保存しました。

第2回の「MongoDB に保存した Mosquitto の payload を Server-Sent Events で表示してみる」では、HTML5 の Server-Sent Events と PHP でブラウザに表示しました。

第3回の「Python WebSocket のチャットサンプルに、MongoDB 保存を付け加えました」では、Python で WebSocket を利用した簡単なチャットプログラムを参考に MongoDB アクセスを書き加えてみました。

今回の第4回は、第3回の Python の WebSocket チャットプログラムを参考にしつつ、ブラウザから POST メソッドで Mosquitto へ Publish し、また、Server-Sent Events でブラウザに表示する方式を Python で試してみました。

また、「Mosquitto へ SSL で接続してみる」では、SSL/TLS での接続にトライします。

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

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

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

Mosquitto の Bridge を SSL で試します

Mosquitto へ SSL で接続してみる

Mosquitto の SSL 対応を試してみる

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

MongoDB に保存した Mosquitto の payload を Server-Sent Events で表示してみる

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

Mosquitto を VPN で試しました

via IPv4

カテゴリー