Tornadoを使ってブラウザをPythonの表示器代わりに使うメモ

# TL:DR
Pythonで何かを表示したいときにGUIを組むほどでもないけど，コンソールだけだとさみしい．
そんなときにはTornadoを使ってwebsocketでブラウザにデータを送り，ブラウザに表示すると良い．

# なにがしたいか
Pythonからデータ等々をそれなりの手間でそれなりに見やすく整形して表示したい．

# なんでブラウザを使うの?
GUIツールキット（TkとかQt系，GTK系）を使って表示を作り込むのに比べて，

* 導入が楽．(Tornadoは純Pythonっぽいのでpipで入るし，ブラウザ入ってない環境は稀．)
* メモリ管理が楽．（GUI周りのメモリ管理はブラウザに丸投げ出来る．）
* 表示と実装を分離しやすい．
* ナウでヤングなWEB系テクノロジーの恩恵が受けやすい．

という利点がある気がしています．
特に，エンジニアといえばWEBエンジニアのことを指すことが多い昨今，WEB技術についてのライブラリや資料がかなり多いので少ない努力でそれっぽく出来ます．

# サーバ(Python+Tornado) 側
まず，Python側のソースコードです．
`tornado.websocket.WebSocketHandler`を継承したクラスの関数をオーバーライドすることで，websocketサーバの動作を定義していきます．
`open()`はwebsocketサーバに接続された時に呼ばれる関数のようなので，

* 接続元のIPアドレスの表示
* 繰り返し処理用インスタンスの用意
* websocketデータ送信の開始

をしています．同様に`on_close()`はクライアントとの接続が切れた時に呼ばれるっぽいので切れた旨表示するようにしてあります．

`check_origin()`は定義しないとクライアントからの接続がはじかれてしまいます．多分，クライアントのフィルタリングをするための関数なんでしょう．

`send_websocket()`内でブラウザに向かってデータを送っています．
Tornadoは`tornado.ioloop`を使うと非同期タイマっぽいことが出来るので，`send_websocket()`を0.1秒ごとに繰り返すようにしてあります．
ブラウザ側で処理する時にJSONになってると処理が楽なので二つのデータをJSON形式にして送ってます．
今回の例ではとりあえず乱数を2つ送ってます．

後は，今作ったクラスを登録してポート指定して走らせるだけです．

```simple_display.py
# -*- coding: utf-8 -*-
import json
import time

import random

import tornado.websocket
import tornado.web
import tornado.ioloop


class SendWebSocket(tornado.websocket.WebSocketHandler):
    def open(self):
        print ('Session Opened. IP:' + self.request.remote_ip)
        self.ioloop = tornado.ioloop.IOLoop.instance()
        self.send_websocket()

    def on_close(self):
        print("Session closed")

    def check_origin(self, origin):
        return True

    def send_websocket(self):
        self.ioloop.add_timeout(time.time() + 0.1, self.send_websocket)
        if self.ws_connection:
            message = json.dumps({
                'data1': random.randint(0, 100),
                'data2': random.randint(0, 100),
                })
            self.write_message(message)


app = tornado.web.Application([(r"/ws/display", SendWebSocket)])

if __name__ == "__main__":
    app.listen(8080)
    tornado.ioloop.IOLoop.current().start()
```

# クライアント側
正直，HTML+JavaScriptのことなどなにもわからないので多くを語れません・・・
が，'<div>'要素にidを振ることで中身をJavaScriptからいじれるようです．
`new WebSocket('ws://127.0.0.1:8080/ws/display')`でwebsocketサーバに接続して，帰ってきたオブジェクト(ここでは`connection`)の`onmessage`関数を定義してあげることで受信時の動作を指定できます．
ここでは，受信したJSONデータをパースしてそれぞれの'<div>'タグの中身に流し込んでます．

```display_test.html
<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <title>Display Demo</title>
</head>
<body>
    <h1> Display Demo </h1>

    <h2>Data1:</h2>
    <div id="display1"></div>
    <h2>Data2:</h2>
    <div id="display2"></div>

    <script>
        var connection = new WebSocket('ws://127.0.0.1:8080/ws/display');
        connection.onmessage = function (e) {
            document.getElementById("display1").textContent = JSON.parse(e.data)["data1"];
            document.getElementById("display2").textContent = JSON.parse(e.data)["data2"];
        };
    </script>
</body>
</html>
```

# 実行結果
![実行結果](https://qiita-image-store.s3.amazonaws.com/0/145065/07a96af4-5c05-23bb-f158-db744e0d282c.png)
Pythonのサーバを立ち上げた状態で，HTMLファイルを開けば↑みたいな表示が出るはずです．
Pythonから0.1秒ごとに乱数が飛んできてるので，それに合わせて表示が更新されます．
結構な速度で更新されるので，それなりの更新速度が実現出来そうです．

# 最後に
今回の記事ではなるべくシンプルにPython→ブラウザへのwebsocket通信を実現する方法を示したつもりです．
が，ブラウザで色々表示する利点は入力フォームやグラフライブラリが簡単に使える点だと思ってるので，その辺の説明も早く書きたいですね．
（前回の記事の最後にも似たようなことを書いて結局放り投げてますが・・・）