diff options
| author | Konstantin Alekseev <mail@kalekseev.com> | 2018-12-06 18:08:01 +0300 |
|---|---|---|
| committer | Tim Graham <timograham@gmail.com> | 2018-12-20 22:17:21 -0500 |
| commit | 08f78a4fc8e7882f9fcad19aa6d497f749a9d261 (patch) | |
| tree | 2c1c9e375434aff5dca7f3c046b4d607974933de | |
| parent | 60398f0e3979e25881f80db79155953b65925ab1 (diff) | |
[2.1.x] Fixed #30015 -- Ensured request body is properly consumed for keep-alive connections.
Backport of b514dc14f4e1c364341f5931b354e83ef15ee12d and
bbe28fa07658f00786dc1d91ee281b4daac22d07 from master.
| -rw-r--r-- | django/core/servers/basehttp.py | 28 | ||||
| -rw-r--r-- | docs/releases/2.1.5.txt | 4 | ||||
| -rw-r--r-- | tests/servers/tests.py | 17 | ||||
| -rw-r--r-- | tests/servers/urls.py | 1 | ||||
| -rw-r--r-- | tests/servers/views.py | 6 |
5 files changed, 51 insertions, 5 deletions
diff --git a/django/core/servers/basehttp.py b/django/core/servers/basehttp.py index 8a36b67eef..3e8f176bc6 100644 --- a/django/core/servers/basehttp.py +++ b/django/core/servers/basehttp.py @@ -14,6 +14,7 @@ import sys from wsgiref import simple_server from django.core.exceptions import ImproperlyConfigured +from django.core.handlers.wsgi import LimitedStream from django.core.wsgi import get_wsgi_application from django.utils.module_loading import import_string @@ -80,18 +81,35 @@ class ThreadedWSGIServer(socketserver.ThreadingMixIn, WSGIServer): class ServerHandler(simple_server.ServerHandler): http_version = '1.1' + def __init__(self, stdin, stdout, stderr, environ, **kwargs): + """ + Use a LimitedStream so that unread request data will be ignored at + the end of the request. WSGIRequest uses a LimitedStream but it + shouldn't discard the data since the upstream servers usually do this. + This fix applies only for testserver/runserver. + """ + try: + content_length = int(environ.get('CONTENT_LENGTH')) + except (ValueError, TypeError): + content_length = 0 + super().__init__(LimitedStream(stdin, content_length), stdout, stderr, environ, **kwargs) + def cleanup_headers(self): super().cleanup_headers() - # HTTP/1.1 requires us to support persistent connections, so - # explicitly send close if we do not know the content length to - # prevent clients from reusing the connection. + # HTTP/1.1 requires support for persistent connections. Send 'close' if + # the content length is unknown to prevent clients from reusing the + # connection. if 'Content-Length' not in self.headers: self.headers['Connection'] = 'close' - # Mark the connection for closing if we set it as such above or - # if the application sent the header. + # Mark the connection for closing if it's set as such above or if the + # application sent the header. if self.headers.get('Connection') == 'close': self.request_handler.close_connection = True + def close(self): + self.get_stdin()._read_limited() + super().close() + def handle_error(self): # Ignore broken pipe errors, otherwise pass on if not is_broken_pipe_error(): diff --git a/docs/releases/2.1.5.txt b/docs/releases/2.1.5.txt index f5306192a4..73850e01b5 100644 --- a/docs/releases/2.1.5.txt +++ b/docs/releases/2.1.5.txt @@ -18,3 +18,7 @@ Bugfixes * Prevented SQLite schema alterations while foreign key checks are enabled to avoid the possibility of schema corruption (:ticket:`30023`). + +* Fixed a regression in Django 2.1.4 (which enabled keep-alive connections) + where request body data isn't properly consumed for such connections + (:ticket:`30015`). diff --git a/tests/servers/tests.py b/tests/servers/tests.py index e38cb5eb07..5917e30d24 100644 --- a/tests/servers/tests.py +++ b/tests/servers/tests.py @@ -111,6 +111,23 @@ class LiveServerViews(LiveServerBase): finally: conn.close() + def test_keep_alive_connection_clears_previous_request_data(self): + conn = HTTPConnection(LiveServerViews.server_thread.host, LiveServerViews.server_thread.port) + try: + conn.request('POST', '/method_view/', b'{}', headers={"Connection": "keep-alive"}) + response = conn.getresponse() + self.assertFalse(response.will_close) + self.assertEqual(response.status, 200) + self.assertEqual(response.read(), b'POST') + + conn.request('POST', '/method_view/', b'{}', headers={"Connection": "close"}) + response = conn.getresponse() + self.assertFalse(response.will_close) + self.assertEqual(response.status, 200) + self.assertEqual(response.read(), b'POST') + finally: + conn.close() + def test_404(self): with self.assertRaises(HTTPError) as err: self.urlopen('/') diff --git a/tests/servers/urls.py b/tests/servers/urls.py index 9017161808..0a8a2984aa 100644 --- a/tests/servers/urls.py +++ b/tests/servers/urls.py @@ -11,4 +11,5 @@ urlpatterns = [ url(r'^subview_calling_view/$', views.subview_calling_view), url(r'^subview/$', views.subview), url(r'^check_model_instance_from_subview/$', views.check_model_instance_from_subview), + url(r'^method_view/$', views.method_view), ] diff --git a/tests/servers/views.py b/tests/servers/views.py index 078be67f46..1db56f44a3 100644 --- a/tests/servers/views.py +++ b/tests/servers/views.py @@ -1,6 +1,7 @@ from urllib.request import urlopen from django.http import HttpResponse, StreamingHttpResponse +from django.views.decorators.csrf import csrf_exempt from .models import Person @@ -42,3 +43,8 @@ def check_model_instance_from_subview(request): pass with urlopen(request.GET['url'] + '/model_view/') as response: return HttpResponse('subview calling view: {}'.format(response.read().decode())) + + +@csrf_exempt +def method_view(request): + return HttpResponse(request.method) |
