[Triage] uWSGI protocol corrupts responses without Content-Length #3697
methane
started this conversation in
Issue Triage
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Type
Bug Report
Description
Note
Regarding AI usage:
After I found this bug, I instructed Codex to reproduce it and create the following issue report.
When Gunicorn is run with --protocol uwsgi behind nginx's uwsgi_pass, a WSGI response without a Content-Length header is corrupted.
Gunicorn applies HTTP/1.1 chunked transfer encoding to the response sent over the uWSGI connection. nginx treats those chunk markers as opaque upstream response-body bytes and then applies its own chunked encoding for the downstream HTTP response.
Consequently, after curl decodes nginx's outer chunked encoding, Gunicorn's inner chunk markers remain in the response body.
I reproduced this with:
Gunicorn 26.0.0
Gunicorn master at 3110e8c (version 26.1.0)
nginx 1.28.0
Python 3.12.13
curl 8.5.0
Ubuntu 24.04
Steps to Reproduce (for bugs)
Create
app.py:Create
nginx.conf:Create a directory for nginx's runtime files:
$ mkdir nginx-runStart Gunicorn:
Start nginx in another terminal:
Then request the application:
The response body returned by curl is effectively:
b"E\r\nHello, world!\n\r\n0\r\n\r\n"The
Eand0are Gunicorn's inner HTTP chunk framing and should not be part of the application response.Using
--rawshows both framing layers:Here,
18and the final0are nginx's outer chunk framing. The payload of that outer chunk contains Gunicorn'sE ... 0chunk stream.Configuration
No response
Logs / Error Output
No response
Gunicorn Version
Gunicorn 26.0.0
Python Version
Python 3.12.13
Worker Class
sync (default)
Operating System
Ubuntu 24.04
Additional Context
Analysis
UWSGIRequest.versionis currently hard-coded to(1, 1):gunicorn/gunicorn/uwsgi/message.py
Lines 38 to 45 in 3110e8c
The generic WSGI
Response.is_chunked()therefore enables chunking whenever the application does not provideContent-Length:gunicorn/gunicorn/http/wsgi.py
Lines 401 to 412 in 3110e8c
Response.default_headers()emitsTransfer-Encoding: chunked, andResponse.write()passesself.chunkedtoutil.write(), which adds the chunk markers:gunicorn/gunicorn/http/wsgi.py
Lines 414 to 474 in 3110e8c
The terminal chunk is also written by
Response.close():gunicorn/gunicorn/http/wsgi.py
Lines 530 to 534 in 3110e8c
HTTP chunk framing should not be used for the response body sent over the uWSGI upstream connection. Gunicorn should send the unframed response body and let nginx choose the downstream HTTP framing. When no
Content-Lengthis available, the upstream connection may also need to be closed to delimit the uWSGI response.Existing integration-test gap
The existing nginx/uWSGI integration-test application already has a response without
Content-Length:gunicorn/tests/docker/uwsgi/app.py
Lines 41 to 46 in 3110e8c
However, its test only checks that the expected text occurs somewhere in the response:
gunicorn/tests/docker/uwsgi/test_uwsgi_integration.py
Lines 25 to 29 in 3110e8c
The corrupted body still contains that substring, so the test passes. An exact assertion would expose the problem:
Checklist
All reactions