Tornado ウェブアプリケーションの構造¶
Tornado ウェブアプリケーションは通常、1つ以上の RequestHandler
サブクラス、受信リクエストをハンドラーにルーティングする Application
オブジェクト、およびサーバーを起動する main()
関数で構成されます。
最小限の「hello world」の例は次のようになります。
import asyncio
import tornado
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, world")
def make_app():
return tornado.web.Application([
(r"/", MainHandler),
])
async def main():
app = make_app()
app.listen(8888)
shutdown_event = asyncio.Event()
await shutdown_event.wait()
if __name__ == "__main__":
asyncio.run(main())
main
コルーチン¶
Tornado 6.2 および Python 3.10 以降では、Tornado アプリケーションを起動するための推奨パターンは、asyncio.run
で実行される main
コルーチンを作成することです。(以前のバージョンでは、通常の関数で初期化を行い、IOLoop.current().start()
でイベントループを開始するのが一般的でした。ただし、このパターンは Python 3.10 以降で非推奨の警告を生成し、将来の Python のバージョンでは動作しなくなります。)
main
関数が戻るとプログラムは終了するため、ウェブサーバーの場合、ほとんどの場合 main
は永続的に実行する必要があります。 asyncio.Event
を待機し、その set()
メソッドが呼び出されないようにすることは、非同期関数を永続的に実行するための便利な方法です。(また、グレースフルシャットダウン手順の一部として main
を早期に終了させたい場合は、shutdown_event.set()
を呼び出して終了させることができます)。
Application
オブジェクト¶
Application
オブジェクトは、リクエストをハンドラーにマッピングするルーティングテーブルなど、グローバルな設定を担当します。
ルーティングテーブルは URLSpec
オブジェクト (またはタプル) のリストであり、各オブジェクトには (少なくとも) 正規表現とハンドラークラスが含まれています。順序が重要で、最初に一致するルールが使用されます。正規表現にキャプチャグループが含まれている場合、これらのグループは _パス引数_ であり、ハンドラーの HTTP メソッドに渡されます。辞書が URLSpec
の3番目の要素として渡される場合、それは RequestHandler.initialize
に渡される _初期化引数_ を提供します。最後に、URLSpec
には名前を付けることができ、これにより RequestHandler.reverse_url
で使用できるようになります。
たとえば、このフラグメントでは、ルート URL /
が MainHandler
にマッピングされ、/story/
の形式の URL の後に続く数値が StoryHandler
にマッピングされます。その数値は (文字列として) StoryHandler.get
に渡されます。
class MainHandler(RequestHandler):
def get(self):
self.write('<a href="%s">link to story 1</a>' %
self.reverse_url("story", "1"))
class StoryHandler(RequestHandler):
def initialize(self, db):
self.db = db
def get(self, story_id):
self.write("this is story %s" % story_id)
app = Application([
url(r"/", MainHandler),
url(r"/story/([0-9]+)", StoryHandler, dict(db=db), name="story")
])
Application
コンストラクターは、アプリケーションの動作をカスタマイズしたり、オプション機能を有効にしたりするために使用できる多くのキーワード引数を受け取ります。完全なリストについては、Application.settings
を参照してください。
RequestHandler
のサブクラス化¶
Tornado ウェブアプリケーションの作業のほとんどは、RequestHandler
のサブクラスで行われます。ハンドラーサブクラスの主なエントリポイントは、処理される HTTP メソッドにちなんで名付けられたメソッド (get()
、post()
など) です。各ハンドラーは、異なる HTTP アクションを処理するために、これらのメソッドの1つ以上を定義できます。上記のように、これらのメソッドは、一致したルーティングルールのキャプチャグループに対応する引数を付けて呼び出されます。
ハンドラー内では、RequestHandler.render
や RequestHandler.write
などのメソッドを呼び出して、応答を生成します。render()
は、名前で Template
をロードし、指定された引数を使用してレンダリングします。write()
は、テンプレートベースではない出力に使用されます。文字列、バイト、および辞書を受け入れます (辞書は JSON としてエンコードされます)。
RequestHandler
の多くのメソッドは、サブクラスでオーバーライドしてアプリケーション全体で使用するように設計されています。write_error
や get_current_user
などのメソッドをオーバーライドする BaseHandler
クラスを定義し、特定のすべてのハンドラーに対して RequestHandler
の代わりに独自の BaseHandler
をサブクラス化するのが一般的です。
リクエスト入力の処理¶
リクエストハンドラーは、self.request
を使用して、現在のリクエストを表すオブジェクトにアクセスできます。属性の完全なリストについては、HTTPServerRequest
のクラス定義を参照してください。
HTML フォームで使用される形式のリクエストデータは解析され、get_query_argument
や get_body_argument
などのメソッドで利用できるようになります。
class MyFormHandler(tornado.web.RequestHandler):
def get(self):
self.write('<html><body><form action="/myform" method="POST">'
'<input type="text" name="message">'
'<input type="submit" value="Submit">'
'</form></body></html>')
def post(self):
self.set_header("Content-Type", "text/plain")
self.write("You wrote " + self.get_body_argument("message"))
HTML フォームエンコーディングでは、引数が単一の値なのか、1つの要素を持つリストなのかが曖昧であるため、RequestHandler
には、アプリケーションがリストを期待するかどうかを示すための個別のメソッドがあります。リストの場合は、単数形に対応するものの代わりに、get_query_arguments
および get_body_arguments
を使用します。
フォームを介してアップロードされたファイルは self.request.files
で利用できます。これは、名前 (HTML の <input type="file">
要素の名前) をファイルのリストにマッピングします。各ファイルは、{"filename":..., "content_type":..., "body":...}
の形式の辞書です。files
オブジェクトは、ファイルがフォームラッパー (つまり、multipart/form-data
Content-Type) でアップロードされた場合にのみ存在します。この形式が使用されていない場合、生のアップロードされたデータは self.request.body
で利用できます。デフォルトでは、アップロードされたファイルは完全にメモリにバッファリングされます。メモリに快適に保持するには大きすぎるファイルを処理する必要がある場合は、stream_request_body
クラスデコレータを参照してください。
demos ディレクトリでは、file_receiver.py に、ファイルのアップロードを受信する両方のメソッドが示されています。
HTMLフォームのエンコーディングの癖(例えば、単数または複数の引数に関する曖昧さなど)により、Tornadoはフォーム引数を他の種類の入力と統合しようとはしません。特に、JSONリクエストボディを解析することはありません。フォームエンコードの代わりにJSONを使用したいアプリケーションは、prepare
をオーバーライドしてリクエストを解析することができます。
def prepare(self):
if self.request.headers.get("Content-Type", "").startswith("application/json"):
self.json_args = json.loads(self.request.body)
else:
self.json_args = None
RequestHandlerメソッドのオーバーライド¶
get()
/post()
などの他に、RequestHandler
の特定のメソッドは、必要に応じてサブクラスによってオーバーライドされるように設計されています。すべてのリクエストにおいて、以下の呼び出しシーケンスが行われます。
新しい
RequestHandler
オブジェクトがリクエストごとに作成されます。initialize()
が、Application
設定からの初期化引数とともに呼び出されます。initialize
は通常、渡された引数をメンバ変数に保存するだけです。出力を行ったり、send_error
のようなメソッドを呼び出したりすることはできません。prepare()
が呼び出されます。これは、すべてのハンドラーサブクラスで共有されるベースクラスで最も役立ちます。prepare
は、どのHTTPメソッドが使用されても呼び出されるためです。prepare
は出力を行うことができます。もしfinish
(またはredirect
など)を呼び出すと、ここで処理が停止します。HTTPメソッドのうちの1つ(
get()
、post()
、put()
など)が呼び出されます。URL正規表現がキャプチャグループを含んでいる場合、それらはこのメソッドへの引数として渡されます。リクエストが完了すると、
on_finish()
が呼び出されます。これは通常、get()
または別のHTTPメソッドが戻った後です。
オーバーライドされるように設計されたすべてのメソッドは、RequestHandler
ドキュメントでそのように注記されています。最も一般的にオーバーライドされるメソッドの一部には、以下が含まれます。
write_error
- エラーページで使用するためのHTMLを出力します。on_connection_close
- クライアントが切断したときに呼び出されます。アプリケーションは、このケースを検出して、それ以上の処理を停止することを選択できます。切断された接続がすぐに検出できるとは限りません。get_current_user
- ユーザー認証を参照してください。get_user_locale
- 現在のユーザーに使用するLocale
オブジェクトを返します。set_default_headers
- レスポンスに追加のヘッダー(カスタムのServer
ヘッダーなど)を設定するために使用できます。
エラー処理¶
ハンドラーが例外を発生させると、TornadoはRequestHandler.write_error
を呼び出してエラーページを生成します。tornado.web.HTTPError
を使用して特定のステータスコードを生成できます。その他の例外はすべて500ステータスを返します。
デフォルトのエラーページには、デバッグモードではスタックトレースが含まれ、それ以外の場合はエラーの1行の説明(例:「500: Internal Server Error」)が含まれます。カスタムエラーページを生成するには、RequestHandler.write_error
(おそらく、すべてのハンドラーで共有されるベースクラスで)をオーバーライドします。このメソッドは、write
やrender
などのメソッドを介して通常通り出力できます。エラーが例外によって引き起こされた場合、exc_info
タプルがキーワード引数として渡されます(この例外はsys.exc_info
の現在の例外であるとは保証されないため、write_error
は、例えばtraceback.format_exception
を、traceback.format_exc
の代わりに使う必要があります)。
write_error
の代わりに、set_status
を呼び出し、レスポンスを書き込み、戻ることで、通常ハンドラーメソッドからエラーページを生成することも可能です。単に戻るのが不便な状況で、write_error
を呼び出さずにハンドラーを終了させるために、特別な例外tornado.web.Finish
を発生させることができます。
404エラーの場合、default_handler_class
Application 設定
を使用します。このハンドラーは、任意のHTTPメソッドで動作するように、get()
のようなより具体的なメソッドの代わりに、prepare
をオーバーライドする必要があります。上記のように、エラーページを生成する必要があります。HTTPError(404)
を発生させてwrite_error
をオーバーライドするか、self.set_status(404)
を呼び出して、prepare()
でレスポンスを直接生成します。
リダイレクト¶
Tornadoでリクエストをリダイレクトするには、主に2つの方法があります。RequestHandler.redirect
と、RedirectHandler
を使用する方法です。
RequestHandler
メソッド内でself.redirect()
を使用して、ユーザーを別の場所にリダイレクトできます。また、リダイレクトが永続的であると見なされることを示すために使用できるオプションのパラメータpermanent
もあります。permanent
のデフォルト値はFalse
であり、302 Found
HTTPレスポンスコードを生成し、POST
リクエストの成功後にユーザーをリダイレクトするような場合に適切です。permanent
がTrue
の場合、301 Moved Permanently
HTTPレスポンスコードが使用され、例えば、SEOに配慮した方法でページの正規URLにリダイレクトする場合に便利です。
RedirectHandler
を使うと、Application
のルーティングテーブルで直接リダイレクトを設定できます。たとえば、単一の静的なリダイレクトを設定するには、次のようにします。
app = tornado.web.Application([
url(r"/app", tornado.web.RedirectHandler,
dict(url="http://itunes.apple.com/my-app-id")),
])
RedirectHandler
は、正規表現による置換もサポートしています。次のルールでは、/pictures/
で始まるすべてのリクエストを、代わりに /photos/
というプレフィックスにリダイレクトします。
app = tornado.web.Application([
url(r"/photos/(.*)", MyPhotoHandler),
url(r"/pictures/(.*)", tornado.web.RedirectHandler,
dict(url=r"/photos/{0}")),
])
RequestHandler.redirect
とは異なり、RedirectHandler
はデフォルトで永続的なリダイレクトを使用します。これは、ルーティングテーブルが実行時に変更されず、永続的であると想定されるためです。一方、ハンドラー内のリダイレクトは、変更される可能性のある他のロジックの結果である可能性が高いためです。RedirectHandler
で一時的なリダイレクトを送信するには、RedirectHandler
の初期化引数に permanent=False
を追加します。
非同期ハンドラー¶
特定のハンドラーメソッド(prepare()
や HTTP 動詞メソッド get()
/post()
/ など)は、コルーチンとしてオーバーライドして、ハンドラーを非同期にすることができます。
たとえば、コルーチンを使用した簡単なハンドラーを次に示します。
class MainHandler(tornado.web.RequestHandler):
async def get(self):
http = tornado.httpclient.AsyncHTTPClient()
response = await http.fetch("http://friendfeed-api.com/v2/feed/bret")
json = tornado.escape.json_decode(response.body)
self.write("Fetched " + str(len(json["entries"])) + " entries "
"from the FriendFeed API")
より高度な非同期の例については、チャットのサンプルアプリケーションを参照してください。これは、ロングポーリング を使用して AJAX チャットルームを実装しています。ロングポーリングのユーザーは、クライアントが接続を閉じた後にクリーンアップするために on_connection_close()
をオーバーライドすることを検討できます(ただし、そのメソッドの docstring で注意事項を確認してください)。