From 4795b9a7ce49fc48c250cb89400c380731cba8b3 Mon Sep 17 00:00:00 2001 From: David Lord Date: Fri, 28 Jan 2022 15:52:15 -0800 Subject: [PATCH] enable HTTP/1.1 when server has multiple workers HTTP/1.1 with the base single worker server seems to be unreliable, sometimes simultaneous requests never complete. When the server is multithread or multiprocess, and the handler doesn't directly set a protocol version, enable HTTP/1.1. This enables keep-alive connections and chunked streaming responses. --- CHANGES.rst | 3 +++ src/werkzeug/serving.py | 8 ++++++++ tests/live_apps/run.py | 9 --------- tests/test_serving.py | 4 ++-- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index ba538645f..cb5530528 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -71,6 +71,9 @@ Unreleased - The development server uses ``Transfer-Encoding: chunked`` for streaming responses when it is configured for HTTP/1.1. :issue:`2090, 1327`, :pr:`2091` +- The development server uses HTTP/1.1, which enables keep-alive + connections and chunked streaming responses, when ``threaded`` or + ``processes`` is enabled. :pr:`2323` Version 2.0.3 diff --git a/src/werkzeug/serving.py b/src/werkzeug/serving.py index fd2164a04..21d30f9f9 100644 --- a/src/werkzeug/serving.py +++ b/src/werkzeug/serving.py @@ -654,6 +654,14 @@ def __init__( if handler is None: handler = WSGIRequestHandler + # If the handler doesn't directly set a protocol version and + # thread or process workers are used, then allow chunked + # responses and keep-alive connections by enabling HTTP/1.1. + if "protocol_version" not in vars(handler) and ( + self.multithread or self.multiprocess + ): + handler.protocol_version = "HTTP/1.1" + self.host = host self.port = port self.app = app diff --git a/tests/live_apps/run.py b/tests/live_apps/run.py index 5f8ff1711..aacdcb664 100644 --- a/tests/live_apps/run.py +++ b/tests/live_apps/run.py @@ -4,15 +4,9 @@ from werkzeug.serving import generate_adhoc_ssl_context from werkzeug.serving import run_simple -from werkzeug.serving import WSGIRequestHandler from werkzeug.wrappers import Request from werkzeug.wrappers import Response - -class WSGI11RequestHandler(WSGIRequestHandler): - protocol_version = "HTTP/1.1" - - name = sys.argv[1] mod = import_module(f"{name}_app") @@ -30,9 +24,6 @@ def app(request): kwargs.update(json.loads(sys.argv[2])) ssl_context = kwargs.get("ssl_context") -if kwargs.get("request_handler") == "HTTP/1.1": - kwargs["request_handler"] = WSGI11RequestHandler - if ssl_context == "custom": kwargs["ssl_context"] = generate_adhoc_ssl_context() elif isinstance(ssl_context, list): diff --git a/tests/test_serving.py b/tests/test_serving.py index 2b46c8a02..d670c725f 100644 --- a/tests/test_serving.py +++ b/tests/test_serving.py @@ -259,7 +259,7 @@ def test_streaming_chunked_response(dev_server): https://tools.ietf.org/html/rfc2616#section-3.6.1 """ - r = dev_server("streaming", request_handler="HTTP/1.1").request("/") + r = dev_server("streaming", threaded=True).request("/") assert r.getheader("transfer-encoding") == "chunked" assert r.data == "".join(str(x) + "\n" for x in range(5)).encode() @@ -270,4 +270,4 @@ def test_streaming_chunked_truncation(dev_server): content truncated by a prematurely closed connection. """ with pytest.raises(http.client.IncompleteRead): - dev_server("streaming", request_handler="HTTP/1.1").request("/crash") + dev_server("streaming", threaded=True).request("/crash")