-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Open
Description
Heya! I think I found a bug in gunicorn's handling of responses which were already chunked.
The issue
If a handler upstream to gunicorn:
- Does not set
Content-Length
- Sets
Transfer-Encoding: chunked
- Streams back data which is already chunked (i.e. response data is already framed as
<length>\r\n<data>\r\n
)
gunicorn wraps the already-chunked content in chunks, corrupting the data stream with erroneous framing.
Suspected code
is_chunked
is used to turn on gunicorn's own chunking, but does not consider if the response is already chunked.
gunicorn/gunicorn/http/wsgi.py
Lines 286 to 301 in bacbf8a
def is_chunked(self): | |
# Only use chunked responses when the client is | |
# speaking HTTP/1.1 or newer and there was | |
# no Content-Length header set. | |
if self.response_length is not None: | |
return False | |
elif self.req.version <= (1, 0): | |
return False | |
elif self.req.method == 'HEAD': | |
# Responses to a HEAD request MUST NOT contain a response body. | |
return False | |
elif self.status_code in (204, 304): | |
# Do not use chunked responses when the response is guaranteed to | |
# not have a response body. | |
return False | |
return True |
Repro steps
A flask app which streams its own response like this. Works fine with other WSGI wrappers:
@some_blueprint.route('/stream', methods=['POST'])
def stream():
r = Response(chunk_generator(), content_type='application/chunked-json')
r.automatically_set_content_length = False
r.direct_passthrough = True
r.headers['Transfer-Encoding'] = 'chunked'
return r
def chunk_generator() -> Generator[bytes, None, None]:
for x in range(50):
c = wire_chunk('some bytes'.encode('utf-8'))
yield c
yield '0\r\n\r\n'.encode('utf-8') # Symbolizes end of stream
def wire_chunk(data: bytes) -> bytes:
data_len = len(data)
return f'{data_len:x}'.encode('utf-8') + '\r\n'.encode('utf-8') + data + '\r\n'.encode('utf-8')
ChadScott-Harvey, not-mike-wazowski, robertmd and ankush
Metadata
Metadata
Assignees
Labels
No labels