Tornadoを使ってみる

Hello World


こちらでPythonのWEBフレーム ワークのFlaskを紹介しました。
同様のWEBフレームワークにTornadoが有ります。
そこで、何回かに分けてTornadoを紹介ます。

当然最初はHello Worldです。


以下の手順でTornadoをインストールします。

ubuntu18.04ではpython3.6がインストールされていますが、このバージョンは2021年12月にサポートが切れています。
システムインストール時(デフォルト)のpython3をバージョンアップすると、aptがエラーになる事が有ります。
pyenv経由でpythonをインストールすると、システムインストール時(デフォルト)のpython3を変更することなく、
好きなバージョンのpythonをインストールすることができます。
pyenvについては色々なところで紹介されていますが、まっさらな環境にインストールするためには、
いくつかのパッケージを事前にインストールしておく必要が有ります。
こちらにまとめておきました。

aptでインストールできるpipはバージョンが古いので、一度古いバージョンを入れてからアップデートします。
pipでパッケージをインストールする時は、wheel形式のファイルをダウンロードします。
wheelモジュールが古いと、インストール時にワーニングやエラーになる事が有るので、こちらも更新しておきます。

TornadoのV5.0以上にはHTTPClientが動かない不具合が有ります。
こちらで色々 議論されていますが、結局V4.5.3をインストールするしか解決策はない様です。
pipでインストールできるTornadoのバージョン一覧は、こちらで 見ることができます。
condaでインストールすると、必ず6.0.3がインストールされてしまうので、注意が必要です。

5.0以上をインストールすると、一部のアプリで以下のエラーになります。
AttributeError: module 'tornado.web' has no attribute 'asynchronous'

$ python3 --version
Python3 3.7.3

$ sudo apt install python3-pip python3-setuptools

$ python3 -m pip -V
pip 18.1 from /usr/lib/python3/dist-packages/pip (python 3.7)

$ python3 -m pip install -U pip

$ python3 -m pip -V
pip 20.3.1 from /home/orangepi/.local/lib/python3.7/site-packages/pip (python 3.7)

$ python3 -m pip install -U wheel

$ python3 -m pip install -U Werkzeug

$ python3 -m pip install tornado==4.5.3

最初はHello Worldを表示するPythonコードです。
#
# Simple Web Server
#
import tornado.httpserver
import tornado.ioloop
import tornado.web
from tornado.options import define, options

define("port", default=8000, help="run on the given port", type=int)

class IndexHandler(tornado.web.RequestHandler):
    def get(self):
        param = self.get_argument('param', 'Hello World')
        self.write(param)

if __name__ == "__main__":
    tornado.options.parse_command_line()
    #app = tornado.web.Application(handlers=[(r"/", IndexHandler)],debug=True)
    app = tornado.web.Application([(r"/", IndexHandler)],debug=True)
    http_server = tornado.httpserver.HTTPServer(app)
    http_server.listen(options.port)
    tornado.ioloop.IOLoop.current().start()

このスクリプト(hello.py)を引数としてpythonを実行し、ブラウザーのアドレスバーでIPアドレスと8000番ポートを指定すると 上の表示となります。
$ python ./hello.py
[I 200415 11:31:05 web:2250] 200 GET / (192.168.10.107) 1.08ms
[W 200415 11:31:05 web:2250] 404 GET /favicon.ico (192.168.10.107) 2.08ms

URLパラメータを以下の様に指定すると、指定したパラメータを表示します。
http://192.168.10.41:8000/?param=abcdefg




引数無しでスクリプトを起動すると8000番のポートが使われますが、以下の様に起動するとポート番号を変更することができます。
これはなかなか便利な機能です。
$ python hello.py --port=9000





以下の様にappオブジェクトの初期化をmake_app関数で行っている例を見かけますが、結果は上のコードと同じです。
#
# Simple Web Server
#
import tornado.httpserver
import tornado.ioloop
import tornado.web
from tornado.options import define, options

define("port", default=8000, help="run on the given port", type=int)

class IndexHandler(tornado.web.RequestHandler):
    def get(self):
        param = self.get_argument('param', 'Hello World')
        self.write(param)

def make_app():
    return tornado.web.Application([
        (r"/", IndexHandler),
    ],debug=True)

if __name__ == "__main__":
    tornado.options.parse_command_line()
    app = make_app()
    #app = tornado.web.Application(handlers=[(r"/", IndexHandler)],debug=True)
    http_server = tornado.httpserver.HTTPServer(app)
    http_server.listen(options.port)
    tornado.ioloop.IOLoop.current().start()



appオブジェクトの初期化は以下の様にクラスを使って行うこともできます。
#
# Simple Web Server
#
import tornado.httpserver
import tornado.ioloop
import tornado.web
from tornado.options import define, options

define("port", default=8000, help="run on the given port", type=int)

class IndexHandler(tornado.web.RequestHandler):
    def get(self):
        param = self.get_argument('param', 'Hello World')
        self.write(param)

class Application(tornado.web.Application):
    def __init__(self):
        handlers = [
            (r"/", IndexHandler),
        ]
        settings = dict(
            debug=True,
            )
        tornado.web.Application.__init__(self, handlers, **settings)

if __name__ == "__main__":
    tornado.options.parse_command_line()
    #app = tornado.web.Application(handlers=[(r"/", IndexHandler)],debug=True)
    app = Application()
    http_server = tornado.httpserver.HTTPServer(app)
    http_server.listen(options.port)
    tornado.ioloop.IOLoop.current().start()



Applicationクラスにシグナルを受け取る関数を追加し、PeriodicCallback()で定期的にシグナルの有無を監視すること で、
コントロール+Cできれいに終わることができます。
#
# Simple Web Server
#
import tornado.httpserver
import tornado.ioloop
import tornado.web
from tornado.options import define, options

import signal

define("port", default=8000, help="run on the given port", type=int)

class IndexHandler(tornado.web.RequestHandler):
        def get(self):
                param = self.get_argument('param', 'Hello World')
                self.write(param)

class Application(tornado.web.Application):
        def __init__(self):
                handlers = [
                        (r"/", IndexHandler),
                ]
                settings = dict(
                        debug=True,
                        )
                tornado.web.Application.__init__(self, handlers, **settings)
                self.is_closing = False

        def signal_handler(self, signum, frame):
                print('exiting...')
                self.is_closing = True

        def try_exit(self):
                if self.is_closing:
                        # clean up here
                        tornado.ioloop.IOLoop.instance().stop()
                        print('exit success')

if __name__ == "__main__":
        print("Start options.port={}".format(options.port))
        tornado.options.parse_command_line()
        #app = tornado.web.Application(handlers=[(r"/", IndexHandler)],debug=True)
        #app = tornado.web.Application([(r"/", IndexHandler)],debug=True)
        app = Application()
        signal.signal(signal.SIGINT, app.signal_handler)
        http_server = tornado.httpserver.HTTPServer(app)
        http_server.listen(options.port)
        tornado.ioloop.PeriodicCallback(app.try_exit, 100).start()
        tornado.ioloop.IOLoop.current().start()



FlaskやBottleはURLに対応した関数を定義しますが、TornadoはURLに対応したクラスを定義します。
URLが指定されるとクラス内のget()が呼ばれますが、それ以外の関数もOverrideすることができます。
#
# Simple Web Server
#
import tornado.httpserver
import tornado.ioloop
import tornado.web
from tornado.options import define, options

define("port", default=8000, help="run on the given port", type=int)

class IndexHandler(tornado.web.RequestHandler):
    def initialize(self):
        print("IndexHandler:initialize")

    def prepare(self):
        print("IndexHandler:prepare")

    def get(self):
        print("IndexHandler:get")
        self.write("Hello World")

    def on_finish(self):
        print("IndexHandler:on_finish")

if __name__ == "__main__":
    tornado.options.parse_command_line()
    app = tornado.web.Application(handlers=[(r"/", IndexHandler)],debug=True)
    http_server = tornado.httpserver.HTTPServer(app)
    http_server.listen(options.port)
    tornado.ioloop.IOLoop.current().start()

上のスクリプトを実行すると以下の順にクラス内の関数が呼ばれることが分かります。
これらの関数の内容を自由に差し替えることができます。
initialize
prepare
get → ここで表示
on_finish


続く...