TornadoとWebSocketとふれあう

前回の↓の記事の続きです。

naari.hatenablog.com

今回はTornadoを使い、WebSocketともふれあいます。

TornadoとWebSocket

WebSocket

WebSocketはHTTPとは違うプロトコルで、HTML5から使えるようになったらしいです。

双方向通信が可能で、Ajaxとは違い、一度コネクションを貼るだけで全てのデータの送受信が可能になります。

Tornado

前回の記事でも書きましたが、TornadoはノンブロッキングなWebフレームワークです。

非同期処理をさせるならTornadoが向いてるので、今回はTornadoでやっていこうと考えました。

今回作るアプリ

今回作ろうと思うものはWebSocketを使ったチャットですが、なんかつまらないので、受信した日本語の文字を形態素解析し、文節ごとに空白を挟んでからみんなに送信するようなものにしたい思います。

コードを書く

こんな感じで書きました。

# -*- coding: utf-8 -*-
import os
import random

from pypugjs.ext.tornado import patch_tornado

import tornado.httpserver
import tornado.ioloop
from tornado import template
import tornado.web
import tornado.websocket
from tornado.web import url

patch_tornado()

from janome.tokenizer import Tokenizer
import json


class IndexHandler(tornado.web.RequestHandler):
    def get(self):
        self.render("index.pug")


class TokenizeHandler(tornado.websocket.WebSocketHandler):

    users = set()

    def open(self):
        self.users.add(self)
        print('Session opened by {}'.format(self.request.remote_ip))

    def on_message(self, message):
        message = json.loads(message)
        t = Tokenizer()
        tokens = t.tokenize(message["text"])
        message["text"] = ""
        for token in tokens:
            message["text"] += "{} ".format(token.surface)
        for user in self.users:
            user.write_message(message)

    def on_close(self):
        self.users.remove(self)
        print('Session closed by {}'.format(self.request.remote_ip))

class Application(tornado.web.Application):
    def __init__(self):
        BASE_DIR = os.path.dirname(os.path.abspath(__file__))
        handlers = [
            url(r'/', IndexHandler, name='index'),
            url(r'/tokenize', TokenizeHandler, name='tokenize'),
        ]
        settings = dict(
            template_path=os.path.join(BASE_DIR, 'templates'),
            static_path=os.path.join(BASE_DIR, 'static'),
            debug=True,
        )
        tornado.web.Application.__init__(self, handlers, **settings)


if __name__ == '__main__':
    app = Application()
    http_server = tornado.httpserver.HTTPServer(app)
    http_server.listen(8000)
    tornado.ioloop.IOLoop.instance().start()

tornado.websocket.WebSocketHandlerを継承している、TokenizeHandlerが今回の主役です。

openメソッドはコネクションが確立したときに発火されるもので、on_messageメソッドはクライアントからのメッセージ受信時、on_closeメソッドはコネクションが閉じられたときに発火されるものです。

ユーザーからのテキストの形態素解析には簡単に使用できて手軽だったJanomeを使用しました。

ailaby.com

ハンドラーの中にusersという集合を設け、コネクションが来たらそのコネクションを追加しています。

誰かの発言を受け取ったところで処理をし、集合の中身をfor文で回して全員にテキストを送っています。

そんなTokenizeHandler/tokenizeで待ち受けておくというコードになっています。

また、write_messagesメソッドなんですが、文字列でないものが渡るとjsonの形になおしてから送ってくれるようです。便利ですね。

これだけです。とても簡単に書くことが出来ました。

あとはこれをクライアント側で受け取るコードを書くのですが、今回は割愛します。あとでgithubリポジトリを貼るので気になる方はそっちで見て下さい。

f:id:naari_3:20161205004034p:plain

おわりに

今回のGithubリポジトリです。Helloworldは前回の分です。

github.com

とても簡単にWebSoketのサーバーを書くことが出来ました。

便利な上にきれいなコードになるTornadoですが、けっこういい印象を持つことが出来ました。

小規模なアプリ開発でもTornado使ってしまっていいんじゃないか?と思えるくらいいい感じです。

他にもTornadoにはログイン処理に関する機能だったり、ログだったり、多機能かつ高機能らしいです。

またいろいろ使ってみてもいいかなーと思えました。