概要
DjangoアプリをMangumを利用してAPI Gateway + Lambdaで動かそうとした際、以下のエラーが出ました。
Exception in 'lifespan' protocol.
ValueError: Django can only handle ASGI/HTTP connections, not lifespan.
こちらの解決方法を紹介します。
エラー内容
MangumはASGIアプリケーションをAWS Lambda向けに変換するアダプタです。
ASGIの仕様には以下のプロトコルタイプがあります。
HTTP and WebSocket Protocol
Lifespan Protocol
Mangumはデフォルトで、Lambda起動時にASGIアプリのlifespanプロトコルを呼び出し、アプリケーションの初期化処理(DB接続のセットアップなど)を実行しようとします。
しかし、DjangoのASGIハンドラ(django.core.asgi.get_asgi_application())はlifespanプロトコルをサポートしていません。DjangoはHTTPリクエストの処理のみに対応しており、また、Djangoは独自の初期化プロセス(AppConfig.ready()など)を持っています。
以下チケットで、lifespanプロトコルをサポートしていないやり取りが記されていました。
そのため、Mangumがlifespanイベント(アプリケーションの起動/終了時の処理)をDjangoに送ろうとすると、上記のValueErrorが発生する、ということになります。
解決方法
Mangumのlifespanオプションをoffに設定することで解決します。
以下のように、Mangumのインスタンス生成時にlifespan="off"を指定してください。
import os
import django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
django.setup()
from mangum import Mangum
from django.core.asgi import get_asgi_application
application = get_asgi_application()
handler = Mangum(application, lifespan="off") # ここに追加
これにより、MangumはlifespanプロトコルをスキップしてHTTPリクエストのみを処理するようになります。
参考
備考
※そもそも、Lambda環境ではWSGIとASGIの違いがほぼ意味をなさないのでは?ASGIにする意味あるの?という問いがあると思います。
wsgiで記述すると以下のエラーになってしまいました。
TypeError: DjangoIntegration.setup_once..sentry_patched_wsgi_handler() takes 3 positional arguments but 4 were given
エラーメッセージに出ているsentryというのは、エラートラッキングサービスです。
Djangoの例外ハンドラやミドルウェアに自動でフックし、エラー発生時にSentryへ送信してくれます。
どうやらSentry統合で上手く処理できていないようでした。
あまり使っていないサービスなので、Sentry自体を無くしちゃえばいいんじゃない?とも思いましたが、そうすると今度は下記のエラーが発生しました。
TypeError: WSGIHandler.call() takes 3 positional arguments but 4 were given
WSGIアプリ(get_wsgi_application())をMangumでラップしている場合、LambdaのイベントによってはASGI的な呼び出しが行われ、引数不一致エラーが発生するようです。
ということで、ASGIで書いた方がエラーなく対応できますし、何か不都合があるわけでもないので、そのように進めました。