#28440 closed Bug (fixed)
runserver doesn't close the connection for responses without a Content-Length
| Reported by: | Tom Forbes | Owned by: | Tom Forbes |
|---|---|---|---|
| Component: | HTTP handling | Version: | dev |
| Severity: | Release blocker | Keywords: | |
| Cc: | Triage Stage: | Ready for checkin | |
| Has patch: | yes | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| Easy pickings: | no | UI/UX: | no |
Description (last modified by )
Using MacOS Python 3.5, runserver does not terminate a connection once a HTTP response is sent. This seems to be caused by #25619 (but could be platform specific?).
This results in tools like curl hanging forever, and browsers continually displaying the loading bar.
This code appears to be the culprit, it seems to be copied from the http.server stdlib module. It handles a response and sends the contents correctly in the first iteration of the loop, but then self.close_connection is still true, so it continues to try and read from the socket whilst the client is also reading from the socket.
Replacing the current handle function with handle_one_request fixes this problem, and still seems to use HTTP 1.1.
Change History (15)
comment:1 by , 8 years ago
| Description: | modified (diff) |
|---|
comment:2 by , 8 years ago
comment:3 by , 8 years ago
Interesting, at first I could not reproduce this with a fresh project on either MacOS or Ubuntu.
However, if you do a plain startproject/startapp and remove all MIDDLEWARE, INSTALLED_APPS (bar your app) and default template CONTEXT_PROCESSORS it is reproducible.
I've made a demo repository that is reproducible on my Ubuntu VM: https://github.com/orf/28440-django-issue
comment:4 by , 8 years ago
| Severity: | Normal → Release blocker |
|---|---|
| Triage Stage: | Unreviewed → Accepted |
Thanks. I'm not sure what the issue could be, offhand.
comment:5 by , 8 years ago
The issue appears to be that the CommonMiddleware sets the Content-Length header, which causes the server to close the connection. When this header is not present (or the middleware not installed) the server continues to wait.
comment:6 by , 8 years ago
| Owner: | changed from to |
|---|---|
| Status: | new → assigned |
comment:7 by , 8 years ago
So this actually appears to be a bug in the http.server module. If you use HTTP/1.1 with Connection: keep-alive and *dont* send a Content-Length header the connection will hang forever. You can test this with a little tinkering in the http.server module itself, and running python3 -mhttp.server on your machine.
I've made a PR (https://github.com/django/django/pull/8820) to simply disable keep-alive for now.
comment:8 by , 8 years ago
| Has patch: | set |
|---|
comment:9 by , 8 years ago
| Summary: | Runserver does not correctly close connections once a response is sent → runserver doesn't close the connection for responses without a Content-Length |
|---|---|
| Triage Stage: | Accepted → Ready for checkin |
comment:11 by , 8 years ago
A PR to fix the test on macOS as reported on reported on django-developers,
I don't see the behavior you describe on Linux. Not sure if I interpreted your suggested patch correctly but:
django/core/servers/basehttp.py
def handle(self):"""Handle multiple requests if necessary."""self.close_connection = 1self.handle_one_request()while not self.close_connection:self.handle_one_request()gives this test failure:
====================================================================== ERROR: test_protocol (servers.tests.LiveServerViews) Launched server serves with HTTP 1.1. ---------------------------------------------------------------------- Traceback (most recent call last): File "/opt/python3.6.2/lib/python3.6/unittest/case.py", line 59, in testPartExecutor yield File "/opt/python3.6.2/lib/python3.6/unittest/case.py", line 605, in run testMethod() File "/home/tim/code/django/tests/servers/tests.py", line 64, in test_protocol conn.getresponse() File "/opt/python3.6.2/lib/python3.6/http/client.py", line 1331, in getresponse response.begin() File "/opt/python3.6.2/lib/python3.6/http/client.py", line 297, in begin version, status, reason = self._read_status() File "/opt/python3.6.2/lib/python3.6/http/client.py", line 266, in _read_status raise RemoteDisconnected("Remote end closed connection without" http.client.RemoteDisconnected: Remote end closed connection without response