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

お前はまだ本当のDjango ASGIを知らない(PyConJP2022 )

Denzow
October 15, 2022
5k

お前はまだ本当のDjango ASGIを知らない(PyConJP2022 )

Django は3.0よりASGIをサポートしました。WSGIの後継として登場したASGIですが、実際にこうすれば使える、async/awaitで書きます。といった情報は多く見かけるようになりました。しかし、結局運用的に何が美味しいのか、パフォーマンス的に何が美味しいのか、そもそも実際に美味しいのかといった観点の情報はまだあまり世の中に多くありません。

今回はDjangoが正式にASGI対応を始める前のDjango 2.x時代からDjango Channelsを利用したASGIウェブアプリケーションを運用してきたLAPRASでの経験を踏まえ、Django標準のASGI対応をお伝えしていきます。

Denzow

October 15, 2022
Tweet

Transcript

  1. そもそもASGI?
 29
 WSGIの最小ケース
 def application(environ, start_response): body = b'Hello world!\n'

    status = '200 OK' headers = [('Content-type', 'text/plain')] start_response(status, headers) return [body]
  2. そもそもASGI?
 30
 WSGIの最小ケース
 def application(environ, start_response): body = b'Hello world!\n'

    status = '200 OK' headers = [('Content-type', 'text/plain')] start_response(status, headers) return [body] 呼び出し可能で 2つの引数をとる
  3. そもそもASGI?
 31
 WSGIの最小ケース
 def application(environ, start_response): body = b'Hello world!\n'

    status = '200 OK' headers = [('Content-type', 'text/plain')] start_response(status, headers) return [body] 第2引数のオブジェクトを ステータスとヘッダーとともに呼 び出す
  4. そもそもASGI?
 32
 WSGIの最小ケース
 def application(environ, start_response): body = b'Hello world!\n'

    status = '200 OK' headers = [('Content-type', 'text/plain')] start_response(status, headers) return [body] レスポンスボディをiterableな バイト文字列を戻す
  5. そもそもASGI?
 33
 ASGIの最小ケース(httpの場合)
 async def application(scope, receive, send): assert scope['type']

    == 'http' await send({ 'type': 'http.response.start', 'status': 200, 'headers': [ [b'content-type', b'text/plain'], ], }) await send({ 'type': 'http.response.body', 'body': b'Hello, world!', }) 呼び出し可能な非同期オブジェクト scope: 接続の詳細を含むdict receive: クライアントからイベントを受信 send: クライアントへメッセージを送信
  6. そもそもASGI?
 34
 ASGIの最小ケース(httpの場合)
 async def application(scope, receive, send): assert scope['type']

    == 'http' await send({ 'type': 'http.response.start', 'status': 200, 'headers': [ [b'content-type', b'text/plain'], ], }) await send({ 'type': 'http.response.body', 'body': b'Hello, world!', }) scope: 接続の詳細を含むdict receive: クライアントからイベントを受信 send: クライアントへメッセージを送信
  7. そもそもASGI?
 35
 ASGIの最小ケース(httpの場合)
 async def application(scope, receive, send): assert scope['type']

    == 'http' await send({ 'type': 'http.response.start', 'status': 200, 'headers': [ [b'content-type', b'text/plain'], ], }) await send({ 'type': 'http.response.body', 'body': b'Hello, world!', }) WSGIでのstart_responseにあたる ヘッダーとステータスをクライアントへ 送信
  8. そもそもASGI?
 36
 ASGIの最小ケース(httpの場合)
 async def application(scope, receive, send): assert scope['type']

    == 'http' await send({ 'type': 'http.response.start', 'status': 200, 'headers': [ [b'content-type', b'text/plain'], ], }) await send({ 'type': 'http.response.body', 'body': b'Hello, world!', }) レスポンスbodyの送信
  9. DjangoのWSGI / ASGI
 40
 Djangoのwsgi.py/asgi.py
 asgi_sample/ ├── __init__.py ├── asgi.py

    ├── settings.py ├── urls.py └── wsgi.py createprojectした時点で 用意されている(3.x以降)
  10. DjangoのWSGI / ASGI
 44
 wsgi.py 
 import os from django.core.wsgi

    import get_wsgi_application os.environ.setdefault( 'DJANGO_SETTINGS_MODULE', 'asgi_sample.settings' ) application = get_wsgi_application()
  11. DjangoのWSGI / ASGI
 45
 django.core.handlers.wsgi.WSGIHandler
 class WSGIHandler(base.BaseHandler): : def __call__(self,

    environ, start_response): set_script_prefix(get_script_name(environ)) signals.request_started.send( sender=self.__class__, environ=environ ) request = self.request_class(environ) response = self.get_response(request) : start_response(status, response_headers) : return response
  12. DjangoのWSGI / ASGI
 46
 django.core.handlers.wsgi.WSGIHandler
 class WSGIHandler(base.BaseHandler): : def __call__(self,

    environ, start_response): set_script_prefix(get_script_name(environ)) signals.request_started.send( sender=self.__class__, environ=environ ) request = self.request_class(environ) response = self.get_response(request) : start_response(status, response_headers) : return response
  13. DjangoのWSGI / ASGI
 47
 asgi.py 
 import os from django.core.asgi

    import get_asgi_application os.environ.setdefault( 'DJANGO_SETTINGS_MODULE', 'asgi_sample.settings' ) application = get_asgi_application()
  14. DjangoのWSGI / ASGI
 48
 django.core.handlers.asgi.ASGIHandler
 class ASGIHandler(base.BaseHandler): """Handler for ASGI

    requests.""" : async def __call__(self, scope, receive, send): # Serve only HTTP connections. # FIXME: Allow to override this. if scope["type"] != "http": raise ValueError( "Django can only handle ASGI/HTTP connections, not %s." % scope["type"] ) async with ThreadSensitiveContext(): await self.handle(scope, receive, send)
  15. DjangoのWSGI / ASGI
 49
 django.core.handlers.asgi.ASGIHandler
 class ASGIHandler(base.BaseHandler): """Handler for ASGI

    requests.""" : async def __call__(self, scope, receive, send): # Serve only HTTP connections. # FIXME: Allow to override this. if scope["type"] != "http": raise ValueError( "Django can only handle ASGI/HTTP connections, not %s." % scope["type"] ) async with ThreadSensitiveContext(): await self.handle(scope, receive, send)
  16. Django Channels
 67
 django.core.handlers.asgi.ASGIHandler
 class ASGIHandler(base.BaseHandler): """Handler for ASGI requests."""

    : async def __call__(self, scope, receive, send): # Serve only HTTP connections. # FIXME: Allow to override this. if scope["type"] != "http": raise ValueError( "Django can only handle ASGI/HTTP connections, not %s." % scope["type"] ) async with ThreadSensitiveContext(): await self.handle(scope, receive, send)
  17. Django Channels
 68
 django.core.handlers.asgi.ASGIHandler
 class ASGIHandler(base.BaseHandler): """Handler for ASGI requests."""

    : async def __call__(self, scope, receive, send): # Serve only HTTP connections. # FIXME: Allow to override this. if scope["type"] != "http": raise ValueError( "Django can only handle ASGI/HTTP connections, not %s." % scope["type"] ) async with ThreadSensitiveContext(): await self.handle(scope, receive, send) DjangoのASGIはHTTPしかサポートしない
  18. Django Channels
 71
 : os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings") django_asgi_app = get_asgi_application() from

    chat import AdminChatConsumer, PublicChatConsumer application = ProtocolTypeRouter({ # Django's ASGI application to handle traditional HTTP requests "http": django_asgi_app, # WebSocket chat handler "websocket": AllowedHostsOriginValidator( AuthMiddlewareStack( URLRouter([ path("chat/", PublicChatConsumer.as_asgi()), ]) ) ), })
  19. Django Channels
 72
 : os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings") django_asgi_app = get_asgi_application() from

    chat import AdminChatConsumer, PublicChatConsumer application = ProtocolTypeRouter({ # Django's ASGI application to handle traditional HTTP requests "http": django_asgi_app, # WebSocket chat handler "websocket": AllowedHostsOriginValidator( AuthMiddlewareStack( URLRouter([ path("chat/", PublicChatConsumer.as_asgi()), ]) ) ), }) httpはDjango自体のASGI対応に流す
  20. Django Channels
 73
 : os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings") django_asgi_app = get_asgi_application() from

    chat import AdminChatConsumer, PublicChatConsumer application = ProtocolTypeRouter({ # Django's ASGI application to handle traditional HTTP requests "http": django_asgi_app, # WebSocket chat handler "websocket": AllowedHostsOriginValidator( AuthMiddlewareStack( URLRouter([ path("chat/", PublicChatConsumer.as_asgi()), ]) ) ), }) websocketプロトコルはChannelsに流す
  21. パフォーマンスの話
 80
 そもそも書き換えるには
 https://docs.djangoproject.com/en/4.1/topics/class-based-views/#asynchronous-class-based-views import asyncio from django.http import HttpResponse

    from django.views import View class AsyncView(View): async def get(self, request, *args, **kwargs): await asyncio.sleep(1) return HttpResponse("Hello async world!")
  22. パフォーマンスの話
 81
 そもそも書き換えるには
 https://docs.djangoproject.com/en/4.1/topics/class-based-views/#asynchronous-class-based-views import asyncio from django.http import HttpResponse

    from django.views import View class AsyncView(View): async def get(self, request, *args, **kwargs): await asyncio.sleep(1) return HttpResponse("Hello async world!")
  23. パフォーマンスの話
 82
 そもそも書き換えるには
 https://docs.djangoproject.com/en/4.1/topics/class-based-views/#asynchronous-class-based-views import asyncio from django.http import HttpResponse

    from django.views import View class AsyncView(View): async def get(self, request, *args, **kwargs): await asyncio.sleep(1) return HttpResponse("Hello async world!") 非同期対応のライブラリに 差し替える必要がある
  24. パフォーマンスの話
 83
 そもそも書き換えるには
 https://docs.djangoproject.com/en/4.1/topics/class-based-views/#asynchronous-class-based-views import asyncio from django.http import HttpResponse

    from django.views import View class AsyncView(View): async def get(self, request, *args, **kwargs): await asyncio.sleep(1) return HttpResponse("Hello async world!") 同期処理はsync_to_asyncデコレータを 使う必要もある