jordaneremieff / mangum

AWS Lambda support for ASGI applications
https://mangum.io/
MIT License
1.66k stars 104 forks source link

Problems with Sanic 23.3.0 and Mangum 0.17.0 #300

Open digitalfiz opened 1 year ago

digitalfiz commented 1 year ago

On a cold start I get this:

INIT_START Runtime Version: python:3.10.v5  Runtime Version ARN: arn:aws:lambda:us-east-1::runtime:*snip*
START RequestId: 2aef27a3-e535-4a74-a02e-46e64404aeef Version: 54
[2023-07-23 17:48:42 +0000] [8] [INFO] Sanic v23.3.0
[INFO]  2023-07-23T17:48:42.351Z    2aef27a3-e535-4a74-a02e-46e64404aeef    Sanic v23.3.0
[2023-07-23 17:48:42 +0000] [8] [INFO] mode: production, ASGI
[INFO]  2023-07-23T17:48:42.352Z    2aef27a3-e535-4a74-a02e-46e64404aeef    mode: production, ASGI
[2023-07-23 17:48:42 +0000] [8] [INFO] server: ASGI
[INFO]  2023-07-23T17:48:42.352Z    2aef27a3-e535-4a74-a02e-46e64404aeef    server: ASGI
[2023-07-23 17:48:42 +0000] [8] [INFO] python: 3.10.11
[INFO]  2023-07-23T17:48:42.352Z    2aef27a3-e535-4a74-a02e-46e64404aeef    python: 3.10.11
[2023-07-23 17:48:42 +0000] [8] [INFO] platform: Linux-4.14.255-311-248.529.amzn2.aarch64-aarch64-with-glibc2.26
[INFO]  2023-07-23T17:48:42.352Z    2aef27a3-e535-4a74-a02e-46e64404aeef    platform: Linux-4.14.255-311-248.529.amzn2.aarch64-aarch64-with-glibc2.26
[2023-07-23 17:48:42 +0000] [8] [INFO] packages: sanic-routing==22.8.0
[INFO]  2023-07-23T17:48:42.352Z    2aef27a3-e535-4a74-a02e-46e64404aeef    packages: sanic-routing==22.8.0
[ERROR] 2023-07-23T17:48:42.612Z    2aef27a3-e535-4a74-a02e-46e64404aeef    An error occurred running the application.
Traceback (most recent call last):
  File "/var/task/mangum/protocols/http.py", line 58, in run
    await app(self.scope, self.receive, self.send)
  File "/var/task/sanic/app.py", line 1366, in __call__
    self._asgi_app = await ASGIApp.create(self, scope, receive, send)
  File "/var/task/sanic/asgi.py", line 170, in create
    instance.request = request_class(
  File "/var/task/sanic/request/types.py", line 134, in __init__
    self._parsed_url = parse_url(url_bytes)
  File "httptools/parser/url_parser.pyx", line 64, in httptools.parser.url_parser.parse_url
TypeError: a bytes-like object is required, not 'NoneType'
END RequestId: 2aef27a3-e535-4a74-a02e-46e64404aeef
REPORT RequestId: 2aef27a3-e535-4a74-a02e-46e64404aeef  Duration: 302.53 ms Billed Duration: 303 ms Memory Size: 256 MB Max Memory Used: 113 MB Init Duration: 2864.82 ms   

And this on a warm call:

START RequestId: 39a71a4c-5f8e-4397-82d5-391214d59c58 Version: 54
--
[2023-07-23 17:52:34 +0000] [8] [INFO] Sanic v23.3.0
[INFO]  2023-07-23T17:52:34.125Z    39a71a4c-5f8e-4397-82d5-391214d59c58    Sanic v23.3.0
[2023-07-23 17:52:34 +0000] [8] [INFO] mode: production, ASGI
[INFO]  2023-07-23T17:52:34.125Z    39a71a4c-5f8e-4397-82d5-391214d59c58    mode: production, ASGI
[2023-07-23 17:52:34 +0000] [8] [INFO] server: ASGI
[INFO]  2023-07-23T17:52:34.125Z    39a71a4c-5f8e-4397-82d5-391214d59c58    server: ASGI
[2023-07-23 17:52:34 +0000] [8] [INFO] python: 3.10.11
[INFO]  2023-07-23T17:52:34.125Z    39a71a4c-5f8e-4397-82d5-391214d59c58    python: 3.10.11
[2023-07-23 17:52:34 +0000] [8] [INFO] platform: Linux-4.14.255-311-248.529.amzn2.aarch64-aarch64-with-glibc2.26
[INFO]  2023-07-23T17:52:34.125Z    39a71a4c-5f8e-4397-82d5-391214d59c58    platform: Linux-4.14.255-311-248.529.amzn2.aarch64-aarch64-with-glibc2.26
[2023-07-23 17:52:34 +0000] [8] [INFO] packages: sanic-routing==22.8.0
[INFO]  2023-07-23T17:52:34.125Z    39a71a4c-5f8e-4397-82d5-391214d59c58    packages: sanic-routing==22.8.0
[2023-07-23 17:52:34 +0000] [8] [ERROR] Cannot finalize router more than once.
Traceback (most recent call last):
File "/var/task/sanic/asgi.py", line 86, in __call__
await self.startup()
File "/var/task/sanic/asgi.py", line 58, in startup
await self.sanic_app._startup()
File "/var/task/sanic/app.py", line 1579, in _startup
self.signalize(self.config.TOUCHUP)
File "/var/task/sanic/app.py", line 1563, in signalize
raise e
File "/var/task/sanic/app.py", line 1560, in signalize
self.signal_router.finalize()
File "/var/task/sanic/signals.py", line 243, in finalize
self.add(_blank, "sanic.__signal__.__init__")
File "/var/task/sanic/signals.py", line 228, in add
signal = super().add(
File "/var/task/sanic_routing/router.py", line 190, in add
raise FinalizationError("Cannot finalize router more than once.")
sanic_routing.exceptions.FinalizationError: Cannot finalize router more than once.
[ERROR] 2023-07-23T17:52:34.126Z    39a71a4c-5f8e-4397-82d5-391214d59c58    Cannot finalize router more than once.Traceback (most recent call last):  File "/var/task/sanic/asgi.py", line 86, in __call__    await self.startup()  File "/var/task/sanic/asgi.py", line 58, in startup    await self.sanic_app._startup()  File "/var/task/sanic/app.py", line 1579, in _startup    self.signalize(self.config.TOUCHUP)  File "/var/task/sanic/app.py", line 1563, in signalize    raise e  File "/var/task/sanic/app.py", line 1560, in signalize    self.signal_router.finalize()  File "/var/task/sanic/signals.py", line 243, in finalize    self.add(_blank, "sanic.__signal__.__init__")  File "/var/task/sanic/signals.py", line 228, in add    signal = super().add(  File "/var/task/sanic_routing/router.py", line 190, in add    raise FinalizationError("Cannot finalize router more than once.")sanic_routing.exceptions.FinalizationError: Cannot finalize router more than once.
[ERROR] LifespanFailure: Lifespan startup failure. Cannot finalize router more than once.Traceback (most recent call last):  File "/var/task/mangum/adapter.py", line 80, in __call__    stack.enter_context(lifespan_cycle)  File "/var/lang/lib/python3.10/contextlib.py", line 492, in enter_context    result = _cm_type.__enter__(cm)  File "/var/task/mangum/protocols/lifespan.py", line 69, in __enter__    self.loop.run_until_complete(self.startup())  File "/var/lang/lib/python3.10/asyncio/base_events.py", line 649, in run_until_complete    return future.result()  File "/var/task/mangum/protocols/lifespan.py", line 167, in startup    raise LifespanFailure(self.exception)
END RequestId: 39a71a4c-5f8e-4397-82d5-391214d59c58
REPORT RequestId: 39a71a4c-5f8e-4397-82d5-391214d59c58  Duration: 9.05 ms   Billed Duration: 10 ms  Memory Size: 256 MB Max Memory Used: 115 MB

I don't know what versions of which are compatible with each other but the latest of each is not. I used the example from https://mangum.io/asgi-frameworks/#sanic. I had to add a name to the Sanic() instance because they require that now but I can not figure out what else might now be required to work with Mangum or if Mangum needs interal adjustments to work with Sanic.

Here is my full example of what I am using:

import logging
from sanic import Sanic
from sanic.response import json
from mangum import Mangum

logger = logging.getLogger()
logging.basicConfig(level="WARNING")

app = Sanic(name="isoulz-api")

@app.route("/")
async def home(request):
    return json({"hello": "world"})

@app.route("/api/v1/hud/event")
async def event(request):
    return json({"foo": "bar"})

handler = Mangum(app)
digitalfiz commented 1 year ago

@ahopkins here is the issue I was talking about :)

ahopkins commented 1 year ago

My initial reaction is that it sounds like maybe "lifespan.startup" is being called multiple times.

ahopkins commented 1 year ago

Just curious, what asgi server are you running this with?

digitalfiz commented 1 year ago

Mangum basically acts like a translator between an aws lambda request structure and what an asgi server would send to Sanic or FastAPI or any of the others.

decarv commented 6 months ago

I ran into the same problem yesterday and below is my quick hack to make it work. This has only been tested locally with SAM.

The problem is that Sanic expects that scope has raw path in scope["raw_path"], which is set to None by default on the handler construction. The solution is to overwrite __call__ in Mangum handler to craft the scope and add raw path.

This issue is from July 25, it should have been fixed by now. Am I missing something?


class MangumWrapper(mangum.Mangum):
    def __init__(self, app: Sanic[Config, SimpleNamespace]) -> None:
        super().__init__(app)

    def __call__(self, event: LambdaEvent, context: LambdaContext) -> dict:
        handler = self.infer(event, context)

        crafted_scope = {**handler.scope, "raw_path": handler.scope["path"].encode()}
        with ExitStack() as stack:
            if self.lifespan in ("auto", "on"):
                lifespan_cycle = LifespanCycle(self.app, self.lifespan)
                stack.enter_context(lifespan_cycle)

            http_cycle = HTTPCycle(crafted_scope, handler.body)
            http_response = http_cycle(self.app)

            return handler(http_response)