スマートフォン・タブレットからインターネットサーバーオペレーション 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 でセーブしました。

[python]
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()
[/python]

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

次はテンプレートです。

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

[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>
[/html]

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

[python]
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
[/python]

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

[python]
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()
[/python]

サーバー環境は、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

カテゴリー