Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Problems with Sanic 23.3.0 and Mangum 0.17.0 #300

Open
digitalfiz opened this issue Jul 23, 2023 · 6 comments
Open

Problems with Sanic 23.3.0 and Mangum 0.17.0 #300

digitalfiz opened this issue Jul 23, 2023 · 6 comments

Comments

@digitalfiz
Copy link

digitalfiz commented Jul 23, 2023

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
Copy link
Author

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

@ahopkins
Copy link

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

@ahopkins
Copy link

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

@digitalfiz
Copy link
Author

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
Copy link

decarv commented Jan 16, 2024

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)

@Kludex
Copy link
Owner

Kludex commented Sep 27, 2024

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)

This doesn't seem correct... You aren't supposed to get the raw_path from the path. It's the other way around.

Also, by the ASGI specification, it's possibl for the raw_path to be None: https://asgi.readthedocs.io/en/latest/specs/www.html#http-connection-scope

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants