Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Django・WSGIミドルウェア入門/django-congress-jp-2018-talk

 Django・WSGIミドルウェア入門/django-congress-jp-2018-talk

Django Congress JP 2018の発表資料です

thinkAmi

May 18, 2018
Tweet

More Decks by thinkAmi

Other Decks in Programming

Transcript

  1. お前誰よ / Who are you ? thinkAmi Python & Django

    Blog :メモ的な思考的な (http://thinkami.hatenablog.com/) ( 株) 日本システム技研 PyCon JP 2016-17 Silver Sponsor ギークラボ長野 Python Boot Camp in 長野 みんなのPython 勉強会 in 長野
  2. WSGI とは Python Web Server Gateway Interface (WSGI) PEP333 とPEP3333

    PEP333 (Python2) PEP3333 (Python3 対応) https://www.python.org/dev/peps/pep-3333/ https://knzm.readthedocs.io/en/latest/pep-3333-ja.html
  3. WSGI アプリケーションとは 2 つの固定引数を持つアプリケーションオブジェクト environ CGI スタイルの環境変数を含む辞書オブジェクト start_response 2 つの固定引数(status

    ・response_headers) と、1 つの任意引数 (exc_info) を受け付ける、呼び出し可能なオブジェクト 戻り値は、HTTP レスポンスボディ(iterable なバイト文字列) 関数ベース・クラスベースの2 種類
  4. WSGI 登場後のDjango Django 1.5 で、mod_python のサポートが削除 http://django.readthedocs.io/en/1.4.X/howto/deployment/modpy thon.html Django の主要なデプロイプラットフォームは、

    Web サーバとWeb アプリケーションに関してPython の標準であるWSGI です。 https://docs.djangoproject.com/ja/2.0/howto/deployment/wsgi/ “ “
  5. Django とWSGI django.core.handlers.wsgi.py class WSGIHandler(base.BaseHandler): def __call__(self, environ, start_response): ...

    start_response(status, response_headers) ... return response Django はクラスベース
  6. WSGI サーバとは uWSGI Gunicorn mod_wsgi WSGI サーバ(WSGI アプリケーションコンテナ)は、WSGI アプリ ケーションを常駐させ、HTTP

    クライアントからリクエストを受け 取るごとに、WSGI アプリケーションのcallable オブジェクトを呼び 出す https://ja.wikipedia.org/wiki/Web_Server_Gateway_Interface “ “
  7. WSGI アプリとWSGI サーバ WSGI アプリを動かすためには、WSGI サーバが必要 標準モジュール wsgiref のWSGI サーバ

    wsgiref.simple_server.make_server Django の開発サーバも、標準モジュール wsgiref を使用 $ python manage.py runserver django.core.management.commands.runserver.py django.core.servers.basehttp.py
  8. 関数ベースのWSGI アプリを動かす WSGI サーバにWSGI アプリを乗せる from wsgiref.simple_server import make_server from

    wsgi_function import hello_wsgi if __name__ == '__main__': server = make_server('', 15000, hello_wsgi) server.serve_forever()
  9. 関数ベースのWSGI アプリを動かす WSGI アプリ def hello_wsgi(environ, start_response): return [b'Hello, WSGI

    function\n'] WSGI サーバを起動後、確認 $ curl http://localhost:15000 Hello, WSGI function
  10. クラスベースのWSGI アプリを動かす WSGI サーバにWSGI アプリを乗せる from wsgiref.simple_server import make_server from

    wsgi_class import HelloWSGI if __name__ == '__main__': server = make_server('', 15001, HelloWSGI()) server.serve_forever()
  11. クラスベースのWSGI アプリを動かす WSGI アプリ class HelloWSGI: def __call__(self, environ, start_response):

    return [b'Hello, WSGI class\n'] WSGI サーバを起動後、確認 $ curl http://localhost:15001 Hello, WSGI class
  12. WSGI ミドルウェア class WSGIMiddleware: def __init__(self, app): self.app = app

    def __call__(self, environ, start_response): # WSGI アプリの処理前に、何か処理をする場所 response = self.app(environ, start_response) # WSGI アプリの処理後に、何か処理をする場所 return response
  13. ( 例) レスポンスを追加するミドルウェア class HelloWSGIMiddleware: def __init__(self, app): self.app =

    app def __call__(self, environ, start_response): response = self.app(environ, start_response) response[0] += b'Hello, WSGI middleware\n' return response
  14. WSGI ミドルウェア + WSGI アプリを動かす WSGI アプリ + WSGI ミドルウェアを、WSGI

    サーバで動かす from wsgiref.simple_server import make_server from wsgi_class import HelloWSGI from wsgi_middleware import HelloWSGIMiddleware if __name__ == '__main__': wsgi_app = HelloWSGI() app_with_middleware = HelloWSGIMiddleware(wsgi_app) server = make_server('', 15002, app_with_middleware) server.serve_forever()
  15. WSGI ミドルウェア + WSGI アプリを動かす WSGI アプリ class HelloWSGI: def

    __call__(self, environ, start_response): return [b'Hello, WSGI class\n'] WSGI サーバを起動後、確認 $ curl http://localhost:15002 Hello, WSGI class Hello, WSGI middleware
  16. Django にWSGI ミドルウェアを組み込む Django アプリのView # `localhost:8000/myapp/hello` で呼ばれるView class HelloView(View):

    def get(self, request, *args, **kwargs): print('called: HelloView') return HttpResponse('Hello world\n')
  17. Django にWSGI ミドルウェアを組み込む WSGI ミドルウェア class HelloWSGIMiddleware: def __init__(self, app):

    self.app = app def __call__(self, environ, start_response): print('[wsgi_middleware] before view') response = self.app(environ, start_response) print('[wsgi_middleware] after view') return response
  18. 実行結果 # curl 結果 $ curl localhost:8000/myapp/hello Hello world #

    Django ログ [wsgi_middleware] before view called: HelloView [wsgi_middleware] after view
  19. Django ミドルウェア class SimpleMiddleware: def __init__(self, get_response): self.get_response = get_response

    def __call__(self, request): # Django アプリの処理前に、何か処理をする場所 response = self.get_response(request) # Django アプリの処理後に、何か処理をする場所 return response https://docs.djangoproject.com/ja/2.0/topics/http/middleware/#writin g-your-own-middleware
  20. Django ミドルウェアを使ってみる Django アプリのView ( 再掲) # `localhost:8000/myapp/hello` で呼ばれるView class

    HelloView(View): def get(self, request, *args, **kwargs): print('called: HelloView') return HttpResponse('Hello world\n')
  21. Django ミドルウェアを使ってみる ミドルウェアの実装 class HelloDjangoMiddleware: def __init__(self, get_response): self.get_response =

    get_response print('[hello] one-time configuration') def __call__(self, request): print('[hello] before view') response = self.get_response(request) print('[hello] after view') return response
  22. process_view() の戻り値 None か HttpResponse オブジェクト None の場合、他の process_view() へ遷移

    メソッドの戻り値が無い場合も同じ HttpResponse オブジェクトの場合、そのオブジェクトに差し替わる render() メソッドを持つオブジェクトを返す場合、 process_template_response() が呼ばれる
  23. process_view() を実装したミドルウェア ( 例) クエリ文字列に foo が無い場合、HttpResponse を返す class ProcessViewDjangoMiddleware:

    ... def process_view(self, request, view_func, view_args, view_kwargs): if not request.GET.get('foo'): return HttpResponse('overwrite by process_view\n')
  24. クエリ文字列にfoo があるリクエストの実行結果 curl 結果 $ curl localhost:8000/myapp/hello?foo=123 Hello world Django

    ログ [process_view] before view [process_view] hook! called: HelloView # View が呼ばれている [process_view] after view
  25. クエリ文字列にfoo が無いリクエストの実行結果 curl 結果 $ curl localhost:8000/myapp/hello?bar=456 overwrite by process_view

    # ミドルウェアで差し替わった Django ログ [process_view] before view [process_view] hook! [process_view] after view # View が呼ばれていない
  26. 使用するView とTemplate View class HelloTemplateView(TemplateView): template_name = 'hello.html' extra_context =

    {'message': 'hello'} def get(self, request, *args, **kwargs): return super().get(request, args, kwargs) Template {{ message }}
  27. TemplateResponse を返すView の場合 View class HelloTemplateView(TemplateView): extra_context = {'message': 'hello'}

    実行結果 $ curl localhost:8000/myapp/template overwrite by middleware # 値が差し替わった
  28. process_exception() の戻り値 None か HttpResponse オブジェクト None の場合、他の process_exception() へ遷移

    メソッドの戻り値が無い場合も同じ HttpResponse オブジェクトの場合、そのオブジェクトに差し替わる render() メソッドを持つオブジェクトを返す場合、 process_template_response() が呼ばれる
  29. process_exception() を実装したミドルウェア class ProcessExceptionDjangoMiddleware: def process_exception(self, request, exception): if request.GET.get('http'):

    return HttpResponse('HttpResponse by process_exception\n') elif request.GET.get('template'): return HelloTemplateView(request=request).render_to_response( {'message': 'TemplateResponse by process_exception\n'}) def process_template_response(self, request, response): response.context_data['message'] += \ 'called process_template_response' return response
  30. None を返す場合 curl の結果 $ curl localhost:8000/myapp/error ... <title>ValueError at

    /myapp/error</title> ... Django のログ Internal Server Error: /myapp/error Traceback (most recent call last): ... raise ValueError('Oops!') ValueError: Oops!
  31. WSGI ミドルウェアの設定 Django View に近いWSGI ミドルウェアから設定 wsgi.py application = ThirdWSGIMiddleware(application)

    application = SecondWSGIMiddleware(application) application = FirstWSGIMiddleware(application)
  32. 各WSGI ミドルウェア class FirstWSGIMiddleware: def __init__(self, app): self.app = app

    def __call__(self, environ, start_response): print('[wsgi1] before view') response = self.app(environ, start_response) print('[wsgi1] after view') return response
  33. 実行結果 [wsgi1] before view [wsgi2] before view [wsgi3] before view

    called: HelloTemplateView [wsgi3] after view [wsgi2] after view [wsgi1] after view
  34. 各Django ミドルウェア class FirstDjangoMiddleware: def __init__(self, get_response): print('[django1] one-time configuration')

    def __call__(self, request): print('[django1] before view') response = self.get_response(request) print('[django1] after view') def process_view(self, request, view_func, view_args, view_kwargs): print('[django1] process view') def process_template_response(self, request, response): print('[django1] process template response')
  35. 結果 [django3] one-time configuration [django2] one-time configuration [django1] one-time configuration

    [django1] before view [django2] before view [django3] before view [django1] process view [django2] process view [django3] process view called: HelloTemplateView [django3] process template response [django2] process template response [django1] process template response [django3] after view [django2] after view [django1] after view
  36. 各ミドルウェアの設定 WSGI application = SecondWSGIMiddleware(application) application = FirstWSGIMiddleware(application) Django MIDDLEWARE

    = [ 'myproject.middlewares.multi_middleware.FirstDjangoMiddleware', 'myproject.middlewares.multi_middleware.SecondDjangoMiddleware', ]
  37. 結果 [django2] one-time configuration [django1] one-time configuration [wsgi1] before view

    [wsgi2] before view [django1] before view [django2] before view [django1] process view [django2] process view called: HelloTemplateView [django2] process template response [django1] process template response [django2] after view [django1] after view [wsgi2] after view [wsgi1] after view
  38. Django ミドルウェアでのハンドリング process_exception() では、例外をハンドリングできない 上位ミドルウェアの self.get_response(request) に HttpResponse が返 る

    HTTP ステータスコード 500 など MIDDLEWARE = [ 'myproject.xxx.HandlingExceptionDjangoMiddleware', 'myproject.xxx.RaisingExceptionInProcessViewDjangoMiddleware', ]
  39. Django1.9 と1.10 の違い 1.10 から、Django ミドルウェアの実装方法が変更 1.9 までの実装のままでは、TypeError: object() takes

    no parameters 主な変更点 MIDDLEWARE_CLASSES ではなく、MIDDLEWARE を使う __init__() メソッドに引数が必要 __call__() メソッドが必要 process_request() と process_response() がDjango2.0 で削除 https://docs.djangoproject.com/en/1.10/releases/1.10/#new- style-middleware
  40. MiddlewareMixin を使ったミドルウェア class DeprecationMixinDjangoMiddleware(MiddlewareMixin): def process_request(self, request): print('[mixin] process_request') def

    process_response(self, request, response): print('[mixin] process_response') return response def process_template_response(self, request, response): print('[mixin] process_template_response') response.context_data['message'] = 'overwrite by deprecation mixin middleware' return response
  41. Django ミドルウェアの動的解除 __init__() の中で、 django.core.exceptions.MiddlewareNotUsed を送出 class UnusedDjangoMiddleware: def __init__(self,

    get_response): self.get_response = get_response raise MiddlewareNotUsed def process_template_response(self, request, response): response.context_data['message'] = 'overwrite by unused' return response