sanic icon indicating copy to clipboard operation
sanic copied to clipboard

event headers supplied as a generator, complicating asgi middleware

Open aronatkins opened this issue 2 years ago • 1 comments

Describe the bug

The headers exposed externally (perhaps to middleware) are a generator, which makes it challenging to intercept, inspect, or modify those headers without fully replacing the object.

I believe that this comes from processed_headers: https://github.com/sanic-org/sanic/blob/00218aa9f2cf98d291b7c0f3a71e145e5ff71376/sanic/response.py#L102-L121

Code snippet

from sanic import Sanic
from sanic.response import json

api = Sanic(__name__)

@api.route("/hello")
async def hello(request):
    name = request.args.get("name", "Sanic")
    data = { "msg": "Hello, %s!" % (name) }
    return json(data)

class HeaderPrintingMiddleware:
    def __init__(self, app):
        self.app = app

    async def __call__(self, scope, receive, send) -> None:
        async def send_with_header_printing(event):
            if event["type"] == "http.response.start":
                response_headers = event["headers"]
                for i, (header, value) in enumerate(response_headers):
                    print(i,header,value)
            await send(event)

        await self.app(scope, receive, send_with_header_printing)

if __name__ == "__main__":
    # api.run()
    import uvicorn
    wrapped = HeaderPrintingMiddleware(api)
    uvicorn.run(wrapped)

When the sample application is executed, responses do not include a "content-type" header.

curl -sv http://127.0.0.1:8000/hello\?name\=bob 2>&1 | grep -i content-type

Expected behavior

curl -sv http://127.0.0.1:8000/hello\?name\=bob 2>&1 | grep -i content-type
# => < content-type: application/json

It was expected that the event["headers"] object could be modified in-place by middleware, as is possible with the headers returned by a number of other frameworks.

Environment (please complete the following information):

  • OS: macOS
  • Version: v22.3.0
  • Python: 3.8.9
  • uvicorn: 0.17.6

Additional context

As a workaround, the middleware can overwrite the headers after inspecting them. Any modifications to the headers by the middleware need to occur on the "outbound" object.

class HeaderPrintingMiddleware:
    def __init__(self, app):
        self.app = app

    async def __call__(self, scope, receive, send) -> None:
        async def send_with_header_printing(event):
            if event["type"] == "http.response.start":
                response_headers = event["headers"]
                headers_out = []
                for i, (header, value) in enumerate(response_headers):
                    headers_out.append( (header, value) )
                    print(i,header,value)
                event["headers"] = headers_out
            await send(event)

        await self.app(scope, receive, send_with_header_printing)

aronatkins avatar Apr 06 '22 13:04 aronatkins

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. If this is incorrect, please respond with an update. Thank you for your contributions.

stale[bot] avatar Sep 21 '22 05:09 stale[bot]

This is not an issue. The ASGI spec defines this type as:

Iterable[[byte string, byte string]]

Returning a generator to the ASGI server appears to be acceptable and spec-compliant.

ahopkins avatar Mar 19 '23 13:03 ahopkins