From b514dc14f4e1c364341f5931b354e83ef15ee12d Mon Sep 17 00:00:00 2001
From: Konstantin Alekseev <mail@kalekseev.com>
Date: Thu, 6 Dec 2018 18:08:01 +0300
Subject: [PATCH] Fixed #30015 -- Ensured request body is properly consumed for
 keep-alive connections.

---
 django/core/servers/basehttp.py | 19 +++++++++++++++++++
 tests/servers/tests.py          | 17 +++++++++++++++++
 tests/servers/urls.py           |  1 +
 tests/servers/views.py          |  6 ++++++
 4 files changed, 43 insertions(+)

diff --git a/django/core/servers/basehttp.py b/django/core/servers/basehttp.py
index 8a36b67eef..01487527f8 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,6 +81,20 @@ class ThreadedWSGIServer(socketserver.ThreadingMixIn, WSGIServer):
 class ServerHandler(simple_server.ServerHandler):
     http_version = '1.1'
 
+    def __init__(self, stdin, stdout, stderr, environ, **kwargs):
+        """
+        Setup a limited stream, so we can discard unread request data
+        at the end of the request. Django already uses `LimitedStream`
+        in `WSGIRequest` but it shouldn't discard the data since the
+        upstream servers usually do this. Hence we fix this only for
+        our 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
@@ -92,6 +107,10 @@ class ServerHandler(simple_server.ServerHandler):
         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/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)