Responderの使い方まとめ【Responder v1.3.1】
Responder
ResponderとはPythonで利用可能なWebAPIフレームワークです.
公式の最新情報やチュートリアルは A familiar HTTP Service Framework です.
現在注目されているResponderですが,あまりにも情報が少なすぎるのが現状です.
日本語で紹介しているページもありますが,公式チュートリアルを和訳したものばかりです.
しかし,とある事情で私自身がResponderを使わなければいけなくなり,色々と調査をし,結果として手に入れた知識をこの記事で紹介します.
ネットの情報をかき集めた部分と,ソースを見て「なるほどこういうことか!」と判明した部分が入り混じっています.
参考までに...
ちなみにResponderはPython3.6以降でないと動作しません.
また,本記事を書いている段階ではResponderのバージョンは1.3.1です.
ローカルサーバの立ち上げとルーテイング
ローカルサーバの立ち上げは,チュートリアルに載っている通り.
以下のコードが最低限あればOKです.
1 2 3 4 5 | import responder api = responder.API() if __name__ == '__main__': api.run() |
もし上記のコードが書いてあるファイルをrun.pyとするならば,
1 | $ python run.py |
でOKです.
そして,ルーテイングは
1 2 3 4 5 6 7 8 9 10 | import responder api = responder.API() @api.route('/') def index(req, resp): resp.content = api.template('index.html') if __name__ == '__main__': api.run() |
のようにします.
が,
こういう風に書いていくと,run.pyひとつのファイルに書かなければいけなくなってしまいます.
ファイルを分けよう
スマートにコードを書くために,また管理しやすいようにファイルは用途によって分けるのが普通です.
なので分けます.
例えば,run.pyはサーバ立ち上げ用にします.
そして新たに,ルーティング専用のurls.py,各ルーテイングの処理専用のcontrollers.pyと分けます.
そうすると,以下のように書けます.
run.py
1 2 3 4 | from urls import api if __name__ == '__main__': api.run() |
urls.py
1 2 3 4 5 | from controllers import api from controllers import IndexController, CreateController api.add_route('/', IndexController) api.add_route('/create/{hogehoge}', CreateController) |
controllers.py
1 2 3 4 5 6 7 8 9 10 11 12 13 | import responder api = responder.API() class IndexController: async def on_get(self, req, resp): resp.html = api.template('index.html') class CreateController: async def on_get(self, req, resp, hogehoge): # 何かしらの処理 pass |
この方がスマートです.
Jinja2のフィルタを追加する
ResponderではHTMLの生成をJinja2というテンプレートエンジンを使用しています.
Responderでは,Jinja2の環境はAPIクラスの jinja_env というメンバ変数が持っています.
なので,もしフィルタを追加したければ,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | # 追加したい関数 def static_filter(path): return '/static/' + path def image_filter(path): return '/static/images/' + path # フィルタは辞書型なので,以下のように追加できる api.jinja_env.filters.update( static=jinja_env.static_filter, image=jinja_env.image_filter, ) |
このように書きます.
そうするとテンプレート側では,
1 | {{ 'sample.png' | image }} |
のようにパイプを使って書けます.
Jinja2のグローバル変数を追加する
これも先ほどのフィルタとやることは似ています.
辞書型の環境変数 globals に追加するだけです.
1 2 | # グローバル変数に登録する場合 api.jinja_env.globals['static'] = '/static/' |
テンプレート側では,
1 | {{ static + '/style.css' }} |
のように書きます.
ログイン,ログアウト処理
Responderでのログインおよびログアウト処理はResponderのResponseクラスが持つSessionとCookieを用います.
(該当ファイル: Responder.models.py)
Sessionにログインユーザーを登録する
セッションは辞書型変数ですので,
1 | resp.session['user_id'] = user_id |
のような形で良いです.
Cookieにログインユーザを登録する
ResponderではPythonのSimpleCookieクラスを使っており,ResponderのResponseクラスには set_cookie() という関数が用意されているのでそれを使います.
先の関数には引数として,
1 2 3 4 5 6 7 | def set_cookie( self, key, # キー value="", # 登録する値 expires=None, # いつ破棄されるか max_age=None, # 何秒後に破棄されるか ) |
などを取ります.(上記は引数の一部で,他にもあります)
ログイン時には,
1 2 | resp.set_cookie(key='user_id', value=user_id, expires=None, max_age=None) resp.set_cookie(key='token', value=token, expires=None, max_age=None) |
のようにします.
ログアウト処理
ログアウトは先のSessionとCookieを破棄すれば良いだけです.
1 2 3 | resp.set_cookie(key='user_id', value='', expires=0, max_age=0) resp.set_cookie(key='token', value='', expires=0, max_age=0) resp.session.pop('user_id') |
404エラーハンドリング
Responderの問題はこれです.
エラーハンドリングがデフォルトではできないようです.(v1.3.1)
他の方が書いた記事も参照しましたが,スマートに解決する方法はなさそうです.
参考:
Responderにはエラーハンドリング機能がない?
responderで理解するWebサーバー #2 ルーティング
そこで僕が思いついた一番簡単な方法は,Responderのソースをいじる方法です.
ソースをいじるのはナンセンスかもしれませんが,これが一番簡単です(多分).
まずいじるのは,Responderのapi.pyのAPIクラス.
その494行目あたりにある, default_response() です.
以下のように修正します.
494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 | def default_response( self, req=None, resp=None, websocket=False, notfound=False, error=False ): if websocket: return if resp.status_code is None: resp.status_code = 200 if self.default_endpoint and notfound: self.default_endpoint(req=req, resp=resp) else: if notfound: resp.status_code = status_codes.HTTP_404 # resp.text = "Not found." ここを以下に変更 resp.content = self.template('404.html') if error: resp.status_code = status_codes.HTTP_500 resp.text = "Application error." |
これで,テンプレートの404.htmlに全てリダイレクトされ,素っ気ない「Not Found.」とはおさらばです.
まとめ
Responderを少し触ってみた感想としては,シンプルで無駄がなく好きです.
Responderの強みは非同期処理にあると思いますが,僕はまだうまい使い方を模索中です.
PythonのWebフレームワークはDjangoが有名ですが,まったく系統が異なり,どちらかというとFlaskに近いです.
DjangoのようなアプリをResponderでも作れますが,少し大変でしょう.
また,Responderを学べば,勝手にSQLAlchemyなどのPythonのデータベース管理フレームワークやJinja2の知識が同時についてきます.
なのでオススメです.
ブラックボックス化したフレームワークは便利ですが,プログラミングの勉強には不向きなので...
と,まあ色々とつらつら書きましたが,まだResponderは発展途上で,今後もアップデートが期待できます.
エラーハンドリングなんかは,いずれもっと扱いやすくなるのではないでは,と思います.
みなさんもこれを機にResponder触ってみてください.