encode / starlette

The little ASGI framework that shines. 🌟
https://www.starlette.io/
BSD 3-Clause "New" or "Revised" License
10.28k stars 939 forks source link

Receive event for Pure ASGI Middleware never called #2670

Closed jules-ch closed 3 months ago

jules-ch commented 3 months ago

It seems receive calls cannot be wrapped when using Pure ASGI Middleware.

I followed the example stated in the docs but it just does not work, the wrapped function is never called.

Wrapped send work as expected.

See here in the docs : https://www.starlette.io/middleware/#inspecting-or-modifying-the-request

Expected Behaviour

wrapped received coroutine called

Minimal example


from starlette.applications import Starlette
from starlette.responses import PlainTextResponse
from starlette.routing import Route
from starlette.middleware import Middleware

from starlette.types import ASGIApp, Receive, Scope, Send, Message
from starlette.datastructures import MutableHeaders

async def homepage(request):
    return PlainTextResponse("Hello, World!")

class LoggingMiddleware:

    def __init__(self, app: ASGIApp) -> None:
        self.app = app

    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
        print(scope)
        if scope["type"] != "http":
            await self.app(scope, receive, send)
            return

        async def receive_logging_request():
            message = await receive()
            print(message)

            return message

        async def send_cache_headers(message: Message) -> None:
            print(message)
            if message["type"] == "http.response.start":
                headers = MutableHeaders(scope=message)

                headers.append("x-cache", "hit")

            await send(message)

        await self.app(scope, receive_logging_request, send_cache_headers)

app = Starlette(
    debug=True,
    routes=[
        Route("/", homepage),
    ],
    middleware=[Middleware(LoggingMiddleware)],
)
jules-ch commented 3 months ago

Debugging the receive_wrapper is never called in Response.__call__.

Just a guess there:

https://github.com/encode/starlette/blob/c78c9aac17a4d68e0647252310044502f1b7da71/starlette/responses.py#L150-L162

jules-ch commented 3 months ago

It may have other implication since the receive function is never called as part of the request response cycle.

Just tested and the uvicorn.protocols.http.httptools_impl:RequestResponseCycle.receive for example is never called.

Adding await receive() seems to do the trick.

adriangb commented 3 months ago

If you never call request.body() or similar indeed receive will never be called. Why would you expect it to be called?

jules-ch commented 3 months ago

Oh my bad just found out receive is only used when you need to access to request body as you said. Just got confused there. For access to request and mutating request headers how would I do that ?

jules-ch commented 3 months ago

It just feels the event should still be sent as per the ASGI spec.

Looking at example in the doc with logging the request size. Since the event is never sent. Then logging of the request size is never triggered

adriangb commented 3 months ago

That's how ASGI works. Happy to tweak wording or examples to make it more explicit.

In case you find it helpful I gave a talk last year that covers ASGI generally (although maybe not this specific case): https://youtu.be/fcfyDvK_A6Q?si=hwwenyLUr3xOujQh.