sanic
sanic copied to clipboard
event headers supplied as a generator, complicating asgi middleware
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)
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.
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.