From a9c6ab03560424ed7dff24849c8ddaa3e1eae62e Mon Sep 17 00:00:00 2001 From: atsuo ishimoto Date: Sun, 7 Jul 2019 00:43:59 +0900 Subject: [PATCH] Fixed #30619 -- Made runserver --nothreading use single threaded WSGIServer. Browsers often use multiple connections with Connection: keep-alive. If --nothreading is specified, the WSGI server cannot accept new connections until the old connection is closed, causing hangs. Force Connection: close when --nothreading option is used. --- django/core/servers/basehttp.py | 3 +++ tests/servers/tests.py | 37 +++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/django/core/servers/basehttp.py b/django/core/servers/basehttp.py index ef93e28f26..02957c51a2 100644 --- a/django/core/servers/basehttp.py +++ b/django/core/servers/basehttp.py @@ -101,6 +101,9 @@ class ServerHandler(simple_server.ServerHandler): # connection. if 'Content-Length' not in self.headers: self.headers['Connection'] = 'close' + # Persistent connections require threading server. + elif not isinstance(self.request_handler.server, socketserver.ThreadingMixIn): + self.headers['Connection'] = 'close' # 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': diff --git a/tests/servers/tests.py b/tests/servers/tests.py index 1f111fb5ae..243b5a4200 100644 --- a/tests/servers/tests.py +++ b/tests/servers/tests.py @@ -9,7 +9,9 @@ from urllib.error import HTTPError from urllib.parse import urlencode from urllib.request import urlopen +from django.core.servers.basehttp import WSGIServer from django.test import LiveServerTestCase, override_settings +from django.test.testcases import LiveServerThread, QuietWSGIRequestHandler from .models import Person @@ -50,6 +52,15 @@ class LiveServerAddress(LiveServerBase): self.assertEqual(self.live_server_url_test[0], self.live_server_url) +class LiveServerSingleThread(LiveServerThread): + def _create_server(self): + return WSGIServer((self.host, self.port), QuietWSGIRequestHandler, allow_reuse_address=False) + + +class SingleThreadLiveServerTestCase(LiveServerTestCase): + server_thread_class = LiveServerSingleThread + + class LiveServerViews(LiveServerBase): def test_protocol(self): """Launched server serves with HTTP 1.1.""" @@ -162,6 +173,32 @@ class LiveServerViews(LiveServerBase): self.assertIn(b"QUERY_STRING: 'q=%D1%82%D0%B5%D1%81%D1%82'", f.read()) +@override_settings(ROOT_URLCONF='servers.urls') +class SingleTreadLiveServerViews(SingleThreadLiveServerTestCase): + available_apps = ['servers'] + + def test_closes_connection_with_content_length(self): + """ + Contrast to + LiveServerViews.test_keep_alive_on_connection_with_content_length(). + Persistent connections require threading server. + """ + conn = HTTPConnection( + SingleTreadLiveServerViews.server_thread.host, + SingleTreadLiveServerViews.server_thread.port, + timeout=1, + ) + try: + conn.request('GET', '/example_view/', headers={'Connection': 'keep-alive'}) + response = conn.getresponse() + self.assertTrue(response.will_close) + self.assertEqual(response.read(), b'example view') + self.assertEqual(response.status, 200) + self.assertEqual(response.getheader('Connection'), 'close') + finally: + conn.close() + + class LiveServerDatabase(LiveServerBase): def test_fixtures_loaded(self):