From 669b12c5a2658e738ceee16d8b62d50ae0dfaf02 Mon Sep 17 00:00:00 2001 From: Gaurav Ojha Date: Fri, 10 Jun 2022 14:01:17 -0700 Subject: [PATCH 1/7] Solves #1234 --- docs/deployment.md | 3 ++ docs/index.md | 3 ++ docs/settings.md | 1 + tests/protocols/test_http.py | 67 ++++++++++++++++++++++++++++++ tests/test_cli.py | 15 +++++++ uvicorn/config.py | 4 ++ uvicorn/main.py | 12 ++++++ uvicorn/protocols/http/h11_impl.py | 2 +- 8 files changed, 106 insertions(+), 1 deletion(-) diff --git a/docs/deployment.md b/docs/deployment.md index 94a0d130b..af16d4b23 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -120,6 +120,9 @@ Options: --app-dir TEXT Look for APP in the specified directory, by adding this to the PYTHONPATH. Defaults to the current working directory. [default: .] + --h11-max-incomplete-event-size INTEGER + For h11, the maximum number of bytes to + buffer of an incomplete event --factory Treat APP as an application factory, i.e. a () -> callable. --help Show this message and exit. diff --git a/docs/index.md b/docs/index.md index 472c92ca1..b4beaebe6 100644 --- a/docs/index.md +++ b/docs/index.md @@ -187,6 +187,9 @@ Options: --app-dir TEXT Look for APP in the specified directory, by adding this to the PYTHONPATH. Defaults to the current working directory. [default: .] + --h11-max-incomplete-event-size INTEGER + For h11, the maximum number of bytes to + buffer of an incomplete event --factory Treat APP as an application factory, i.e. a () -> callable. --help Show this message and exit. diff --git a/docs/settings.md b/docs/settings.md index 25be52353..1f185e9e0 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -65,6 +65,7 @@ For more nuanced control over which file modifications trigger reloads, install * `--ws-ping-interval ` - Set the WebSockets ping interval, in seconds. Please note that this can be used only with the default `websockets` protocol. * `--ws-ping-timeout ` - Set the WebSockets ping timeout, in seconds. Please note that this can be used only with the default `websockets` protocol. * `--lifespan ` - Set the Lifespan protocol implementation. **Options:** *'auto', 'on', 'off'.* **Default:** *'auto'*. +* `--h11-max-incomplete-event-size ` - Set the maximum number of bytes to buffer of an incomplete event. Only available for `h11` HTTP protocol implementation. **Default:** *'16384'* ## Application Interface diff --git a/tests/protocols/test_http.py b/tests/protocols/test_http.py index 876fcf076..5d363fb80 100644 --- a/tests/protocols/test_http.py +++ b/tests/protocols/test_http.py @@ -87,6 +87,17 @@ ] ) +GET_REQUEST_HUGE_HEADERS = [ + b"".join( + [ + b"GET / HTTP/1.1\r\n", + b"Host: example.org\r\n", + b"Cookie: " + b"x" * 32 * 1024, + ] + ), + b"".join([b"x" * 32 * 1024 + b"\r\n", b"\r\n", b"\r\n"]), +] + class MockTransport: def __init__(self, sockname=None, peername=None, sslcontext=False): @@ -796,3 +807,59 @@ def send_fragmented_req(path): assert bad_response != response[: len(bad_response)] server.should_exit = True t.join() + + +@pytest.mark.parametrize("protocol_cls", HTTP_PROTOCOLS) +def test_huge_headers_h11_fail(protocol_cls, event_loop): + app = Response("Hello, world", media_type="text/plain") + + with get_connected_protocol(app, protocol_cls, event_loop) as protocol: + # Huge headers make h11 fail in it's default config + # h11 sends back a 400 in this case + if protocol_cls == H11Protocol: + protocol.data_received(GET_REQUEST_HUGE_HEADERS[0]) + assert b"HTTP/1.1 400 Bad Request" in protocol.transport.buffer + assert b"Connection: close" in protocol.transport.buffer + assert b"Invalid HTTP request received." in protocol.transport.buffer + else: + protocol.data_received(GET_REQUEST_HUGE_HEADERS[0]) + protocol.data_received(GET_REQUEST_HUGE_HEADERS[1]) + protocol.loop.run_one() + assert b"HTTP/1.1 200 OK" in protocol.transport.buffer + assert b"Hello, world" in protocol.transport.buffer + + +@pytest.mark.parametrize("protocol_cls", HTTP_PROTOCOLS) +def test_huge_headers_h11_fail_with_setting(protocol_cls, event_loop): + app = Response("Hello, world", media_type="text/plain") + + # a test to make sure that even when a value is set + # a larger header will fail + with get_connected_protocol( + app, protocol_cls, event_loop, h11_max_incomplete_event_size=20 * 1024 + ) as protocol: + if protocol_cls == H11Protocol: + protocol.data_received(GET_REQUEST_HUGE_HEADERS[0]) + assert b"HTTP/1.1 400 Bad Request" in protocol.transport.buffer + assert b"Connection: close" in protocol.transport.buffer + assert b"Invalid HTTP request received." in protocol.transport.buffer + else: + protocol.data_received(GET_REQUEST_HUGE_HEADERS[0]) + protocol.data_received(GET_REQUEST_HUGE_HEADERS[1]) + protocol.loop.run_one() + assert b"HTTP/1.1 200 OK" in protocol.transport.buffer + assert b"Hello, world" in protocol.transport.buffer + + +@pytest.mark.parametrize("protocol_cls", HTTP_PROTOCOLS) +def test_huge_headers_h11_max_incomplete(protocol_cls, event_loop): + app = Response("Hello, world", media_type="text/plain") + + with get_connected_protocol( + app, protocol_cls, event_loop, h11_max_incomplete_event_size=64 * 1024 + ) as protocol: + protocol.data_received(GET_REQUEST_HUGE_HEADERS[0]) + protocol.data_received(GET_REQUEST_HUGE_HEADERS[1]) + protocol.loop.run_one() + assert b"HTTP/1.1 200 OK" in protocol.transport.buffer + assert b"Hello, world" in protocol.transport.buffer diff --git a/tests/test_cli.py b/tests/test_cli.py index 5935d3825..eb950ff88 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -122,6 +122,21 @@ def test_cli_incomplete_app_parameter() -> None: assert result.exit_code == 1 +def test_cli_event_size() -> None: + runner = CliRunner() + + with mock.patch.object(main, "run") as mock_run: + result = runner.invoke( + cli, + ["tests.test_cli:App", "--h11-max-incomplete-event-size", str(32 * 1024)], + ) + + assert result.output == "" + assert result.exit_code == 0 + mock_run.assert_called_once() + assert mock_run.call_args[1]["h11_max_incomplete_event_size"] == 32768 + + @pytest.fixture() def load_env_h11_protocol(): old_environ = dict(os.environ) diff --git a/uvicorn/config.py b/uvicorn/config.py index c96f16691..ce2415492 100644 --- a/uvicorn/config.py +++ b/uvicorn/config.py @@ -10,6 +10,8 @@ from pathlib import Path from typing import Awaitable, Callable, Dict, List, Optional, Tuple, Type, Union +from h11._connection import DEFAULT_MAX_INCOMPLETE_EVENT_SIZE + from uvicorn._logging import TRACE_LOG_LEVEL if sys.version_info < (3, 8): # pragma: py-gte-38 @@ -240,6 +242,7 @@ def __init__( ssl_ciphers: str = "TLSv1", headers: Optional[List[Tuple[str, str]]] = None, factory: bool = False, + h11_max_incomplete_event_size: int = DEFAULT_MAX_INCOMPLETE_EVENT_SIZE, ): self.app = app self.host = host @@ -283,6 +286,7 @@ def __init__( self.headers: List[Tuple[str, str]] = headers or [] self.encoded_headers: List[Tuple[bytes, bytes]] = [] self.factory = factory + self.h11_max_incomplete_event_size = h11_max_incomplete_event_size self.loaded = False self.configure_logging() diff --git a/uvicorn/main.py b/uvicorn/main.py index bfeeeb584..49e86c398 100644 --- a/uvicorn/main.py +++ b/uvicorn/main.py @@ -7,6 +7,7 @@ import click from asgiref.typing import ASGIApplication +from h11._connection import DEFAULT_MAX_INCOMPLETE_EVENT_SIZE import uvicorn from uvicorn.config import ( @@ -339,6 +340,13 @@ def print_version(ctx: click.Context, param: click.Parameter, value: bool) -> No help="Look for APP in the specified directory, by adding this to the PYTHONPATH." " Defaults to the current working directory.", ) +@click.option( + "--h11-max-incomplete-event-size", + "h11_max_incomplete_event_size", + type=int, + default=None, + help="For h11, the maximum number of bytes to buffer of an incomplete event", +) @click.option( "--factory", is_flag=True, @@ -391,6 +399,7 @@ def main( headers: typing.List[str], use_colors: bool, app_dir: str, + h11_max_incomplete_event_size: int, factory: bool, ) -> None: run( @@ -439,6 +448,7 @@ def main( use_colors=use_colors, factory=factory, app_dir=app_dir, + h11_max_incomplete_event_size=h11_max_incomplete_event_size, ) @@ -489,6 +499,7 @@ def run( use_colors: typing.Optional[bool] = None, app_dir: typing.Optional[str] = None, factory: bool = False, + h11_max_incomplete_event_size: int = DEFAULT_MAX_INCOMPLETE_EVENT_SIZE, ) -> None: if app_dir is not None: sys.path.insert(0, app_dir) @@ -538,6 +549,7 @@ def run( headers=headers, use_colors=use_colors, factory=factory, + h11_max_incomplete_event_size=h11_max_incomplete_event_size, ) server = Server(config=config) diff --git a/uvicorn/protocols/http/h11_impl.py b/uvicorn/protocols/http/h11_impl.py index 1767f5597..036c20af6 100644 --- a/uvicorn/protocols/http/h11_impl.py +++ b/uvicorn/protocols/http/h11_impl.py @@ -77,7 +77,7 @@ def __init__( self.logger = logging.getLogger("uvicorn.error") self.access_logger = logging.getLogger("uvicorn.access") self.access_log = self.access_logger.hasHandlers() - self.conn = h11.Connection(h11.SERVER) + self.conn = h11.Connection(h11.SERVER, config.h11_max_incomplete_event_size) self.ws_protocol_class = config.ws_protocol_class self.root_path = config.root_path self.limit_concurrency = config.limit_concurrency From 8f9b7d55dcc5d836d9952f90abd0902d90d24506 Mon Sep 17 00:00:00 2001 From: Gaurav Ojha Date: Tue, 14 Jun 2022 09:55:16 -0700 Subject: [PATCH 2/7] Update docs/settings.md Co-authored-by: Marcelo Trylesinski --- docs/settings.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/settings.md b/docs/settings.md index 1f185e9e0..6cf0baeb5 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -65,7 +65,7 @@ For more nuanced control over which file modifications trigger reloads, install * `--ws-ping-interval ` - Set the WebSockets ping interval, in seconds. Please note that this can be used only with the default `websockets` protocol. * `--ws-ping-timeout ` - Set the WebSockets ping timeout, in seconds. Please note that this can be used only with the default `websockets` protocol. * `--lifespan ` - Set the Lifespan protocol implementation. **Options:** *'auto', 'on', 'off'.* **Default:** *'auto'*. -* `--h11-max-incomplete-event-size ` - Set the maximum number of bytes to buffer of an incomplete event. Only available for `h11` HTTP protocol implementation. **Default:** *'16384'* +* `--h11-max-incomplete-event-size ` - Set the maximum number of bytes to buffer of an incomplete event. Only available for `h11` HTTP protocol implementation. **Default:** *'16384'* (16 KB). ## Application Interface From 64856927162cef885762406654d282f0d3793b71 Mon Sep 17 00:00:00 2001 From: Gaurav Ojha Date: Tue, 14 Jun 2022 09:55:26 -0700 Subject: [PATCH 3/7] Update uvicorn/main.py Co-authored-by: Marcelo Trylesinski --- uvicorn/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uvicorn/main.py b/uvicorn/main.py index 49e86c398..5b8603244 100644 --- a/uvicorn/main.py +++ b/uvicorn/main.py @@ -345,7 +345,7 @@ def print_version(ctx: click.Context, param: click.Parameter, value: bool) -> No "h11_max_incomplete_event_size", type=int, default=None, - help="For h11, the maximum number of bytes to buffer of an incomplete event", + help="For h11, the maximum number of bytes to buffer of an incomplete event.", ) @click.option( "--factory", From bf74201a9887f2b2de5d5948f8f4a0a4aaf46fd3 Mon Sep 17 00:00:00 2001 From: Gaurav Ojha Date: Tue, 14 Jun 2022 10:03:28 -0700 Subject: [PATCH 4/7] Fix lint --- docs/deployment.md | 2 +- docs/index.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/deployment.md b/docs/deployment.md index af16d4b23..f1cefc8b8 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -122,7 +122,7 @@ Options: the current working directory. [default: .] --h11-max-incomplete-event-size INTEGER For h11, the maximum number of bytes to - buffer of an incomplete event + buffer of an incomplete event. --factory Treat APP as an application factory, i.e. a () -> callable. --help Show this message and exit. diff --git a/docs/index.md b/docs/index.md index b4beaebe6..af8e9f7e3 100644 --- a/docs/index.md +++ b/docs/index.md @@ -189,7 +189,7 @@ Options: the current working directory. [default: .] --h11-max-incomplete-event-size INTEGER For h11, the maximum number of bytes to - buffer of an incomplete event + buffer of an incomplete event. --factory Treat APP as an application factory, i.e. a () -> callable. --help Show this message and exit. From b393a2bb0e15039e73205fb9ea87fae74ce456ef Mon Sep 17 00:00:00 2001 From: Gaurav Ojha Date: Tue, 14 Jun 2022 11:25:00 -0700 Subject: [PATCH 5/7] Fixing tests --- tests/protocols/test_http.py | 70 +++++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 28 deletions(-) diff --git a/tests/protocols/test_http.py b/tests/protocols/test_http.py index 5d363fb80..9b1faa653 100644 --- a/tests/protocols/test_http.py +++ b/tests/protocols/test_http.py @@ -809,46 +809,60 @@ def send_fragmented_req(path): t.join() -@pytest.mark.parametrize("protocol_cls", HTTP_PROTOCOLS) -def test_huge_headers_h11_fail(protocol_cls, event_loop): +@pytest.mark.parametrize("protocol_cls", [H11Protocol]) +def test_huge_headers_h11protocol_will_fail(protocol_cls, event_loop): app = Response("Hello, world", media_type="text/plain") with get_connected_protocol(app, protocol_cls, event_loop) as protocol: # Huge headers make h11 fail in it's default config # h11 sends back a 400 in this case - if protocol_cls == H11Protocol: - protocol.data_received(GET_REQUEST_HUGE_HEADERS[0]) - assert b"HTTP/1.1 400 Bad Request" in protocol.transport.buffer - assert b"Connection: close" in protocol.transport.buffer - assert b"Invalid HTTP request received." in protocol.transport.buffer - else: - protocol.data_received(GET_REQUEST_HUGE_HEADERS[0]) - protocol.data_received(GET_REQUEST_HUGE_HEADERS[1]) - protocol.loop.run_one() - assert b"HTTP/1.1 200 OK" in protocol.transport.buffer - assert b"Hello, world" in protocol.transport.buffer + protocol.data_received(GET_REQUEST_HUGE_HEADERS[0]) + assert b"HTTP/1.1 400 Bad Request" in protocol.transport.buffer + assert b"Connection: close" in protocol.transport.buffer + assert b"Invalid HTTP request received." in protocol.transport.buffer -@pytest.mark.parametrize("protocol_cls", HTTP_PROTOCOLS) -def test_huge_headers_h11_fail_with_setting(protocol_cls, event_loop): +def test_huge_headers_httptools_will_pass(event_loop, protocol_cls=HttpToolsProtocol): + app = Response("Hello, world", media_type="text/plain") + + with get_connected_protocol(app, protocol_cls, event_loop) as protocol: + # Huge headers make h11 fail in it's default config + # httptools protocol will always pass + protocol.data_received(GET_REQUEST_HUGE_HEADERS[0]) + protocol.data_received(GET_REQUEST_HUGE_HEADERS[1]) + protocol.loop.run_one() + assert b"HTTP/1.1 200 OK" in protocol.transport.buffer + assert b"Hello, world" in protocol.transport.buffer + + +@pytest.mark.parametrize("protocol_cls", [H11Protocol]) +def test_huge_headers_h11protocol_will_fail_with_setting(protocol_cls, event_loop): app = Response("Hello, world", media_type="text/plain") - # a test to make sure that even when a value is set - # a larger header will fail with get_connected_protocol( app, protocol_cls, event_loop, h11_max_incomplete_event_size=20 * 1024 ) as protocol: - if protocol_cls == H11Protocol: - protocol.data_received(GET_REQUEST_HUGE_HEADERS[0]) - assert b"HTTP/1.1 400 Bad Request" in protocol.transport.buffer - assert b"Connection: close" in protocol.transport.buffer - assert b"Invalid HTTP request received." in protocol.transport.buffer - else: - protocol.data_received(GET_REQUEST_HUGE_HEADERS[0]) - protocol.data_received(GET_REQUEST_HUGE_HEADERS[1]) - protocol.loop.run_one() - assert b"HTTP/1.1 200 OK" in protocol.transport.buffer - assert b"Hello, world" in protocol.transport.buffer + # Huge headers make h11 fail in it's default config + # h11 sends back a 400 in this case + protocol.data_received(GET_REQUEST_HUGE_HEADERS[0]) + assert b"HTTP/1.1 400 Bad Request" in protocol.transport.buffer + assert b"Connection: close" in protocol.transport.buffer + assert b"Invalid HTTP request received." in protocol.transport.buffer + + +def test_huge_headers_httptools_will_pass_with_setting( + event_loop, protocol_cls=HttpToolsProtocol +): + app = Response("Hello, world", media_type="text/plain") + + with get_connected_protocol(app, protocol_cls, event_loop) as protocol: + # Huge headers make h11 fail in it's default config + # httptools protocol will always pass + protocol.data_received(GET_REQUEST_HUGE_HEADERS[0]) + protocol.data_received(GET_REQUEST_HUGE_HEADERS[1]) + protocol.loop.run_one() + assert b"HTTP/1.1 200 OK" in protocol.transport.buffer + assert b"Hello, world" in protocol.transport.buffer @pytest.mark.parametrize("protocol_cls", HTTP_PROTOCOLS) From 6f5ff1c0adde35ec07a5d695a795eb7d197eb2b6 Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Mon, 20 Jun 2022 21:29:20 +0200 Subject: [PATCH 6/7] Tweak tests a bit --- tests/protocols/test_http.py | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/tests/protocols/test_http.py b/tests/protocols/test_http.py index 9b1faa653..7614ff621 100644 --- a/tests/protocols/test_http.py +++ b/tests/protocols/test_http.py @@ -809,11 +809,10 @@ def send_fragmented_req(path): t.join() -@pytest.mark.parametrize("protocol_cls", [H11Protocol]) -def test_huge_headers_h11protocol_will_fail(protocol_cls, event_loop): +def test_huge_headers_h11protocol_failure(event_loop): app = Response("Hello, world", media_type="text/plain") - with get_connected_protocol(app, protocol_cls, event_loop) as protocol: + with get_connected_protocol(app, H11Protocol, event_loop) as protocol: # Huge headers make h11 fail in it's default config # h11 sends back a 400 in this case protocol.data_received(GET_REQUEST_HUGE_HEADERS[0]) @@ -822,10 +821,11 @@ def test_huge_headers_h11protocol_will_fail(protocol_cls, event_loop): assert b"Invalid HTTP request received." in protocol.transport.buffer -def test_huge_headers_httptools_will_pass(event_loop, protocol_cls=HttpToolsProtocol): +@pytest.mark.skipif(HttpToolsProtocol is None, reason="httptools is not installed") +def test_huge_headers_httptools_will_pass(event_loop): app = Response("Hello, world", media_type="text/plain") - with get_connected_protocol(app, protocol_cls, event_loop) as protocol: + with get_connected_protocol(app, HttpToolsProtocol, event_loop) as protocol: # Huge headers make h11 fail in it's default config # httptools protocol will always pass protocol.data_received(GET_REQUEST_HUGE_HEADERS[0]) @@ -835,12 +835,11 @@ def test_huge_headers_httptools_will_pass(event_loop, protocol_cls=HttpToolsProt assert b"Hello, world" in protocol.transport.buffer -@pytest.mark.parametrize("protocol_cls", [H11Protocol]) -def test_huge_headers_h11protocol_will_fail_with_setting(protocol_cls, event_loop): +def test_huge_headers_h11protocol_failure_with_setting(event_loop): app = Response("Hello, world", media_type="text/plain") with get_connected_protocol( - app, protocol_cls, event_loop, h11_max_incomplete_event_size=20 * 1024 + app, H11Protocol, event_loop, h11_max_incomplete_event_size=20 * 1024 ) as protocol: # Huge headers make h11 fail in it's default config # h11 sends back a 400 in this case @@ -850,12 +849,11 @@ def test_huge_headers_h11protocol_will_fail_with_setting(protocol_cls, event_loo assert b"Invalid HTTP request received." in protocol.transport.buffer -def test_huge_headers_httptools_will_pass_with_setting( - event_loop, protocol_cls=HttpToolsProtocol -): +@pytest.mark.skipif(HttpToolsProtocol is None, reason="httptools is not installed") +def test_huge_headers_httptools(event_loop): app = Response("Hello, world", media_type="text/plain") - with get_connected_protocol(app, protocol_cls, event_loop) as protocol: + with get_connected_protocol(app, HttpToolsProtocol, event_loop) as protocol: # Huge headers make h11 fail in it's default config # httptools protocol will always pass protocol.data_received(GET_REQUEST_HUGE_HEADERS[0]) @@ -865,12 +863,11 @@ def test_huge_headers_httptools_will_pass_with_setting( assert b"Hello, world" in protocol.transport.buffer -@pytest.mark.parametrize("protocol_cls", HTTP_PROTOCOLS) -def test_huge_headers_h11_max_incomplete(protocol_cls, event_loop): +def test_huge_headers_h11_max_incomplete(event_loop): app = Response("Hello, world", media_type="text/plain") with get_connected_protocol( - app, protocol_cls, event_loop, h11_max_incomplete_event_size=64 * 1024 + app, H11Protocol, event_loop, h11_max_incomplete_event_size=64 * 1024 ) as protocol: protocol.data_received(GET_REQUEST_HUGE_HEADERS[0]) protocol.data_received(GET_REQUEST_HUGE_HEADERS[1]) From e523d591a548a47635adf7d3da99f87c8fe1b8cd Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Mon, 20 Jun 2022 22:48:03 +0200 Subject: [PATCH 7/7] Remove event_loop fixture --- tests/protocols/test_http.py | 95 +++++++++++++++++++----------------- 1 file changed, 50 insertions(+), 45 deletions(-) diff --git a/tests/protocols/test_http.py b/tests/protocols/test_http.py index 629339f3d..b8e3189bb 100644 --- a/tests/protocols/test_http.py +++ b/tests/protocols/test_http.py @@ -814,68 +814,73 @@ def send_fragmented_req(path): t.join() -def test_huge_headers_h11protocol_failure(event_loop): +@pytest.mark.anyio +async def test_huge_headers_h11protocol_failure(): app = Response("Hello, world", media_type="text/plain") - with get_connected_protocol(app, H11Protocol, event_loop) as protocol: - # Huge headers make h11 fail in it's default config - # h11 sends back a 400 in this case - protocol.data_received(GET_REQUEST_HUGE_HEADERS[0]) - assert b"HTTP/1.1 400 Bad Request" in protocol.transport.buffer - assert b"Connection: close" in protocol.transport.buffer - assert b"Invalid HTTP request received." in protocol.transport.buffer + protocol = get_connected_protocol(app, H11Protocol) + # Huge headers make h11 fail in it's default config + # h11 sends back a 400 in this case + protocol.data_received(GET_REQUEST_HUGE_HEADERS[0]) + assert b"HTTP/1.1 400 Bad Request" in protocol.transport.buffer + assert b"Connection: close" in protocol.transport.buffer + assert b"Invalid HTTP request received." in protocol.transport.buffer +@pytest.mark.anyio @pytest.mark.skipif(HttpToolsProtocol is None, reason="httptools is not installed") -def test_huge_headers_httptools_will_pass(event_loop): +async def test_huge_headers_httptools_will_pass(): app = Response("Hello, world", media_type="text/plain") - with get_connected_protocol(app, HttpToolsProtocol, event_loop) as protocol: - # Huge headers make h11 fail in it's default config - # httptools protocol will always pass - protocol.data_received(GET_REQUEST_HUGE_HEADERS[0]) - protocol.data_received(GET_REQUEST_HUGE_HEADERS[1]) - protocol.loop.run_one() - assert b"HTTP/1.1 200 OK" in protocol.transport.buffer - assert b"Hello, world" in protocol.transport.buffer + protocol = get_connected_protocol(app, HttpToolsProtocol) + # Huge headers make h11 fail in it's default config + # httptools protocol will always pass + protocol.data_received(GET_REQUEST_HUGE_HEADERS[0]) + protocol.data_received(GET_REQUEST_HUGE_HEADERS[1]) + await protocol.loop.run_one() + assert b"HTTP/1.1 200 OK" in protocol.transport.buffer + assert b"Hello, world" in protocol.transport.buffer -def test_huge_headers_h11protocol_failure_with_setting(event_loop): +@pytest.mark.anyio +async def test_huge_headers_h11protocol_failure_with_setting(): app = Response("Hello, world", media_type="text/plain") - with get_connected_protocol( - app, H11Protocol, event_loop, h11_max_incomplete_event_size=20 * 1024 - ) as protocol: - # Huge headers make h11 fail in it's default config - # h11 sends back a 400 in this case - protocol.data_received(GET_REQUEST_HUGE_HEADERS[0]) - assert b"HTTP/1.1 400 Bad Request" in protocol.transport.buffer - assert b"Connection: close" in protocol.transport.buffer - assert b"Invalid HTTP request received." in protocol.transport.buffer + protocol = get_connected_protocol( + app, H11Protocol, h11_max_incomplete_event_size=20 * 1024 + ) + # Huge headers make h11 fail in it's default config + # h11 sends back a 400 in this case + protocol.data_received(GET_REQUEST_HUGE_HEADERS[0]) + assert b"HTTP/1.1 400 Bad Request" in protocol.transport.buffer + assert b"Connection: close" in protocol.transport.buffer + assert b"Invalid HTTP request received." in protocol.transport.buffer +@pytest.mark.anyio @pytest.mark.skipif(HttpToolsProtocol is None, reason="httptools is not installed") -def test_huge_headers_httptools(event_loop): +async def test_huge_headers_httptools(): app = Response("Hello, world", media_type="text/plain") - with get_connected_protocol(app, HttpToolsProtocol, event_loop) as protocol: - # Huge headers make h11 fail in it's default config - # httptools protocol will always pass - protocol.data_received(GET_REQUEST_HUGE_HEADERS[0]) - protocol.data_received(GET_REQUEST_HUGE_HEADERS[1]) - protocol.loop.run_one() - assert b"HTTP/1.1 200 OK" in protocol.transport.buffer - assert b"Hello, world" in protocol.transport.buffer + protocol = get_connected_protocol(app, HttpToolsProtocol) + # Huge headers make h11 fail in it's default config + # httptools protocol will always pass + protocol.data_received(GET_REQUEST_HUGE_HEADERS[0]) + protocol.data_received(GET_REQUEST_HUGE_HEADERS[1]) + await protocol.loop.run_one() + assert b"HTTP/1.1 200 OK" in protocol.transport.buffer + assert b"Hello, world" in protocol.transport.buffer -def test_huge_headers_h11_max_incomplete(event_loop): +@pytest.mark.anyio +async def test_huge_headers_h11_max_incomplete(): app = Response("Hello, world", media_type="text/plain") - with get_connected_protocol( - app, H11Protocol, event_loop, h11_max_incomplete_event_size=64 * 1024 - ) as protocol: - protocol.data_received(GET_REQUEST_HUGE_HEADERS[0]) - protocol.data_received(GET_REQUEST_HUGE_HEADERS[1]) - protocol.loop.run_one() - assert b"HTTP/1.1 200 OK" in protocol.transport.buffer - assert b"Hello, world" in protocol.transport.buffer + protocol = get_connected_protocol( + app, H11Protocol, h11_max_incomplete_event_size=64 * 1024 + ) + protocol.data_received(GET_REQUEST_HUGE_HEADERS[0]) + protocol.data_received(GET_REQUEST_HUGE_HEADERS[1]) + await protocol.loop.run_one() + assert b"HTTP/1.1 200 OK" in protocol.transport.buffer + assert b"Hello, world" in protocol.transport.buffer