ファイナンス、情報通信技術のスキル・アグリゲーション・サイト
HTML5 Server-Sent Events( SSE、EventSource)のサンプルプログラムです。サーバ側が Python Tornado 版と PHP 版です。
サンプルプログラムでは、およそ 10 秒間隔でサーバ時刻を送信して、受信したブラウザ上でその時刻を更新します。
メッセージのフィールドについてです。定義されているフィールドは次の通りです。
メッセージは、UTF-8 を用いてエンコードされなければなりません。メッセージは、2つの改行文字で区切られます。
メッセージヘッダの Content-Type には、 text/event-stream を設定します。
ブラウザ側のスクリプトについてです。
EventSource オブジェクトを作成して、イベントを受け取るためにサーバへの接続を開始します。
addEventListener() を使用して対象のイベントメッセージを待ち受けます。
onerror コールバックは、エラーイベントを受けます。
Microsoft Internet Explorer は、Server-Sent Events をサポートしていません。
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import tornado.httpserver
import tornado.ioloop
import tornado.web
import os
from datetime import datetime
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.render("index.html")
class SSEHandler(tornado.web.RequestHandler):
@tornado.gen.coroutine
def get(self):
self.set_header("Content-Type", "text/event-stream")
self.set_header("Cache-Control", "no-cache")
yield self.write("event: messages\n")
value = datetime.now().strftime("%H:%M:%S")
yield self.write("data: %s\n" % value.encode( "utf-8" ))
yield self.write("retry: 10000\n")
yield self.write("\n")
yield self.flush()
settings = {
"template_path": os.path.join(os.path.dirname(__file__), "templates"),
}
application = tornado.web.Application([
(r"/sse", SSEHandler),
(r"/", MainHandler),
], **settings)
if __name__ == "__main__":
http_server = tornado.httpserver.HTTPServer(
application, ssl_options={
"certfile": "server.crt",
"keyfile": "server.key",})
http_server.listen(8888)
tornado.ioloop.IOLoop.instance().start()
プログラム例のテンプレートは、Pyhton プログラムを保存したディレクトリに templates ディレクトリを作成して index.html のファイル名で保存します。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Tornado SSE Demo</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script>
function sse() {
if (typeof (EventSource) !== "undefined") {
var source = new EventSource("/sse");
source.addEventListener("messages", function(event){
document.getElementById("msg").innerHTML = "<p>" + event.data + "</p>" ;
},false);
source.onerror = function (event) {
if (source.readyState === EventSource.CLOSED) {
document.getElementById("msg").innerHTML = "<p>終了しています。</p>" ;
}
else if (source.readyState === EventSource.OPEN) {
document.getElementById("msg").innerHTML = "<p>終了します。</p>" ;
source.close();
}
}
} else {
document.getElementById("msg").innerHTML = "<p>Server-Sent Events はサポートされていません。</p>" ;
}
}
</script>
</head>
<body>
<p>Message</p>
<div id="msg" style="border: 1px solid black">
</div>
<script>
window.onload = function() {
sse();
};
</script>
</body>
</html>
さて、参考として、 Bottle フレームワークでのプログラム例です。 apache httpd + mod_wsgi 環境で実行します。
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from bottle import get, post, route, view, request, response, redirect, Bottle
from datetime import datetime
app = Bottle(__name__)
@app.get("/sse")
def sse():
response.content_type = "text/event-stream"
response.cache_control = "no-cache"
yield "event: messages\n"
value = datetime.now().strftime("%H:%M:%S")
yield "data: %s\n" % value.encode( "utf-8" )
yield "retry: 10000\n"
yield "\n"
@app.route("/")
@view("index")
def index():
return
if __name__ == "__main__":
app.run()
テンプレートは、Tornado プログラム例の index.html をそのまま使用しますが、Pyhton プログラムのあるディレクトリに views ディレクトリを作成して保存します。
adapter.wsgi ファイルの例です。Pyhton プログラム(bsse.py とします)と同じディレクトリに保存します。
#!/usr/bin/env python
import sys, os ,bottle
os.environ["LANG"] = "ja_JP.UTF-8"
dirpath = os.path.dirname(os.path.abspath(__file__))
sys.path.append(dirpath)
os.chdir(dirpath)
from bsse import app
application = app
httpd.conf の VirtualHost などに追加する設定例です。
WSGIDaemonProcess wsgi-sse user=example group=example processes=1 threads=5 python-path=/usr/local/lib/python2.7/site-packages
WSGIScriptAlias / /home/example/sse/adapter.wsgi
<Directory /home/example/sse>
WSGIProcessGroup wsgi-sse
WSGIApplicationGroup %{GLOBAL}
Order deny,allow
Allow from all
</Directory>
event フィールドがあるメッセージの場合です。
<?php
header("Content-Type: text/event-stream");
header("Cache-Control: no-cache");
echo "event: " . "messages" . PHP_EOL;
echo "data: " . date(H:i:s", time()) . PHP_EOL;
echo "retry: 10000" . PHP_EOL;
echo PHP_EOL;
ob_flush();
flush();
?>
プログラム例では、ウェブサーバのドキュメントルートに PHP プログラムを sse.php のファイル名で保存し、HTML ファイルも保存します。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>PHP SSE Demo</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script>
function sse() {
if (typeof (EventSource) !== "undefined") {
var source = new EventSource("sse.php");
source.addEventListener("messages", function(event){
document.getElementById("msg").innerHTML = "<p>" + event.data + "</p>" ;
},false);
source.onerror = function (event) {
if (source.readyState === EventSource.CLOSED) {
document.getElementById("msg").innerHTML = "<p>終了しています。</p>" ;
}
else if (source.readyState === EventSource.OPEN) {
document.getElementById("msg").innerHTML = "<p>終了します。</p>" ;
source.close();
}
}
} else {
document.getElementById("msg").innerHTML = "<p>Server-Sent Events はサポートされていません。</p>" ;
}
}
</script>
</head>
<body>
<p>Message</p>
<div id="msg" style="border: 1px solid black">
</div>
<script>
window.onload = function() {
sse();
};
</script>
</body>
</html>
次に、 event フィールドのないメッセージの場合です。このとき、デフォルトの "message" が設定されます。
<?php
header("Content-Type: text/event-stream");
header("Cache-Control: no-cache");
echo "data: " . date("H:i:s", time()) . PHP_EOL;
echo "retry: 10000" . PHP_EOL;
echo PHP_EOL;
ob_flush();
flush();
?>
addEventListener() を使ってイベント "message" を待ち受けることもできますが、この場合、onmessage コールバックを使うこともできます。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Tornado SSE Demo</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script>
function sse() {
if (typeof (EventSource) !== "undefined") {
var source = new EventSource("sse.php");
source.onmessage = function (event){
document.getElementById("msg").innerHTML = "<p>" + event.data + "</p>" ;
}
source.onerror = function (event) {
if (source.readyState === EventSource.CLOSED) {
document.getElementById("msg").innerHTML = "<p>終了しています。</p>" ;
}
else if (source.readyState === EventSource.OPEN) {
document.getElementById("msg").innerHTML = "<p>終了します。</p>" ;
source.close();
}
}
} else {
document.getElementById("msg").innerHTML = "<p>Server-Sent Events はサポートされていません。</p>" ;
}
}
</script>
</head>
<body>
<p>Message</p>
<div id="msg" style="border: 1px solid black">
</div>
<script>
window.onload = function() {
sse();
};
</script>
</body>
</html>