Fix Chunked Transport-Encoding in ASGI streaming response

In ASGI-mode, don't do sanic-side response chunk encoding, leave that to the ASGI-response-transport
Don't set content-length when using chunked-encoding in ASGI mode, this is incompatible with ASGI Chunked Transport-Encoding.

(cherry picked from commit c0839afdde)
This commit is contained in:
Ashley Sommer 2020-11-05 14:53:48 +10:00
parent c5070bd449
commit 3e4bec7f2c
4 changed files with 19 additions and 5 deletions

View File

@ -1474,3 +1474,4 @@ class Sanic:
self.asgi = True self.asgi = True
asgi_app = await ASGIApp.create(self, scope, receive, send) asgi_app = await ASGIApp.create(self, scope, receive, send)
await asgi_app() await asgi_app()
_asgi_single_callable = True # We conform to ASGI 3.0 single-callable

View File

@ -309,13 +309,17 @@ class ASGIApp:
callback = None if self.ws else self.stream_callback callback = None if self.ws else self.stream_callback
await handler(self.request, None, callback) await handler(self.request, None, callback)
_asgi_single_callable = True # We conform to ASGI 3.0 single-callable
async def stream_callback(self, response: HTTPResponse) -> None: async def stream_callback(self, response: HTTPResponse) -> None:
""" """
Write the response. Write the response.
""" """
headers: List[Tuple[bytes, bytes]] = [] headers: List[Tuple[bytes, bytes]] = []
cookies: Dict[str, str] = {} cookies: Dict[str, str] = {}
content_length: List[str] = []
try: try:
content_length = response.headers.popall("content-length", [])
cookies = { cookies = {
v.key: v v.key: v
for _, v in list( for _, v in list(
@ -348,10 +352,17 @@ class ASGIApp:
] ]
response.asgi = True response.asgi = True
is_streaming = isinstance(response, StreamingHTTPResponse)
if "content-length" not in response.headers and not isinstance( if is_streaming and getattr(response, "chunked", False):
response, StreamingHTTPResponse # disable sanic chunking, this is done at the ASGI-server level
): response.chunked = False
# content-length header is removed to signal to the ASGI-server
# to use automatic-chunking if it supports it
elif len(content_length) > 0:
headers += [
(b"content-length", str(content_length[0]).encode("latin-1"))
]
elif not is_streaming:
headers += [ headers += [
(b"content-length", str(len(response.body)).encode("latin-1")) (b"content-length", str(len(response.body)).encode("latin-1"))
] ]

View File

@ -80,6 +80,8 @@ class StreamingHTTPResponse(BaseHTTPResponse):
if type(data) != bytes: if type(data) != bytes:
data = self._encode_body(data) data = self._encode_body(data)
# `chunked` will always be False in ASGI-mode, even if the underlying
# ASGI Transport implements Chunked transport. That does it itself.
if self.chunked: if self.chunked:
await self.protocol.push_data(b"%x\r\n%b\r\n" % (len(data), data)) await self.protocol.push_data(b"%x\r\n%b\r\n" % (len(data), data))
else: else:

View File

@ -235,7 +235,7 @@ def test_chunked_streaming_returns_correct_content(streaming_app):
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_chunked_streaming_returns_correct_content_asgi(streaming_app): async def test_chunked_streaming_returns_correct_content_asgi(streaming_app):
request, response = await streaming_app.asgi_client.get("/") request, response = await streaming_app.asgi_client.get("/")
assert response.text == "4\r\nfoo,\r\n3\r\nbar\r\n0\r\n\r\n" assert response.text == "foo,bar"
def test_non_chunked_streaming_adds_correct_headers(non_chunked_streaming_app): def test_non_chunked_streaming_adds_correct_headers(non_chunked_streaming_app):