From 18f51528729e0829ca0912d6c5d5832a51e5e9b1 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 13 Dec 2022 19:50:09 +0000 Subject: [PATCH 1/9] Handle cases like HTTP 413 where the request writes fail, but a response is still sent correctly. --- httpcore/_async/http11.py | 26 ++++++++++++++----- httpcore/_sync/http11.py | 26 ++++++++++++++----- tests/_async/test_connection.py | 46 ++++++++++++++++++++++++++++++++- tests/_sync/test_connection.py | 46 ++++++++++++++++++++++++++++++++- 4 files changed, 128 insertions(+), 16 deletions(-) diff --git a/httpcore/_async/http11.py b/httpcore/_async/http11.py index 9bcb5214..2ebc4cba 100644 --- a/httpcore/_async/http11.py +++ b/httpcore/_async/http11.py @@ -18,6 +18,7 @@ ConnectionNotAvailable, LocalProtocolError, RemoteProtocolError, + WriteError, map_exceptions, ) from .._models import Origin, Request, Response @@ -76,13 +77,24 @@ async def handle_async_request(self, request: Request) -> Response: try: kwargs = {"request": request} - async with Trace("http11.send_request_headers", request, kwargs) as trace: - await self._send_request_headers(**kwargs) - async with Trace("http11.send_request_body", request, kwargs) as trace: - await self._send_request_body(**kwargs) - async with Trace( - "http11.receive_response_headers", request, kwargs - ) as trace: + try: + trace_message = "http11.send_request_headers" + async with Trace(trace_message, request, kwargs) as trace: + await self._send_request_headers(**kwargs) + + trace_message = "http11.send_request_body" + async with Trace(trace_message, request, kwargs) as trace: + await self._send_request_body(**kwargs) + except WriteError: + # If we get a write error while we're writing the request, + # then we supress this error and move on to attempting to + # read the response. Servers can sometimes close the request + # pre-emptively and then respond with a well formed HTTP + # error response. + pass + + trace_message = "http11.receive_response_headers" + async with Trace(trace_message, request, kwargs) as trace: ( http_version, status, diff --git a/httpcore/_sync/http11.py b/httpcore/_sync/http11.py index dc25d1e9..6212e9e8 100644 --- a/httpcore/_sync/http11.py +++ b/httpcore/_sync/http11.py @@ -18,6 +18,7 @@ ConnectionNotAvailable, LocalProtocolError, RemoteProtocolError, + WriteError, map_exceptions, ) from .._models import Origin, Request, Response @@ -76,13 +77,24 @@ def handle_request(self, request: Request) -> Response: try: kwargs = {"request": request} - with Trace("http11.send_request_headers", request, kwargs) as trace: - self._send_request_headers(**kwargs) - with Trace("http11.send_request_body", request, kwargs) as trace: - self._send_request_body(**kwargs) - with Trace( - "http11.receive_response_headers", request, kwargs - ) as trace: + try: + trace_message = "http11.send_request_headers" + with Trace(trace_message, request, kwargs) as trace: + self._send_request_headers(**kwargs) + + trace_message = "http11.send_request_body" + with Trace(trace_message, request, kwargs) as trace: + self._send_request_body(**kwargs) + except WriteError: + # If we get a write error while we're writing the request, + # then we supress this error and move on to attempting to + # read the response. Servers can sometimes close the request + # pre-emptively and then respond with a well formed HTTP + # error response. + pass + + trace_message = "http11.receive_response_headers" + with Trace(trace_message, request, kwargs) as trace: ( http_version, status, diff --git a/tests/_async/test_connection.py b/tests/_async/test_connection.py index 8f1f19e5..44f56c09 100644 --- a/tests/_async/test_connection.py +++ b/tests/_async/test_connection.py @@ -4,7 +4,13 @@ import hyperframe.frame import pytest -from httpcore import AsyncHTTPConnection, ConnectError, ConnectionNotAvailable, Origin +from httpcore import ( + AsyncHTTPConnection, + ConnectError, + ConnectionNotAvailable, + Origin, + WriteError, +) from httpcore.backends.base import AsyncNetworkStream from httpcore.backends.mock import AsyncMockBackend @@ -76,6 +82,44 @@ async def test_concurrent_requests_not_available_on_http11_connections(): await conn.request("GET", "https://example.com/") +@pytest.mark.anyio +async def test_write_error_but_response_sent(): + """ + Attempting to issue a request against an already active HTTP/1.1 connection + will raise a `ConnectionNotAvailable` exception. + """ + + class ErrorOnRequestTooLarge(AsyncMockBackend): + def __init__(self, buffer: List[bytes], http2: bool = False) -> None: + super().__init__(buffer, http2) + self.count = 0 + + async def write(self, buffer: bytes, timeout: Optional[float] = None) -> None: + self.count += len(buffer) + + if self.count > 1_000_000: + raise WriteError() + + origin = Origin(b"https", b"example.com", 443) + network_backend = ErrorOnRequestTooLarge( + [ + b"HTTP/1.1 413 Payload Too Large\r\n", + b"Content-Type: plain/text\r\n", + b"Content-Length: 37\r\n", + b"\r\n", + b"Request body exceeded 1,000,000 bytes", + ] + ) + + async with AsyncHTTPConnection( + origin=origin, network_backend=network_backend, keepalive_expiry=5.0 + ) as conn: + content = b"x" * 10_000_000 + response = await conn.request("POST", "https://example.com/", content=content) + assert response.status == 413 + assert response.content == b"Request body exceeded 1,000,000 bytes" + + @pytest.mark.anyio async def test_http2_connection(): origin = Origin(b"https", b"example.com", 443) diff --git a/tests/_sync/test_connection.py b/tests/_sync/test_connection.py index d5c97856..62aa7acf 100644 --- a/tests/_sync/test_connection.py +++ b/tests/_sync/test_connection.py @@ -4,7 +4,13 @@ import hyperframe.frame import pytest -from httpcore import HTTPConnection, ConnectError, ConnectionNotAvailable, Origin +from httpcore import ( + HTTPConnection, + ConnectError, + ConnectionNotAvailable, + Origin, + WriteError, +) from httpcore.backends.base import NetworkStream from httpcore.backends.mock import MockBackend @@ -77,6 +83,44 @@ def test_concurrent_requests_not_available_on_http11_connections(): +def test_write_error_but_response_sent(): + """ + Attempting to issue a request against an already active HTTP/1.1 connection + will raise a `ConnectionNotAvailable` exception. + """ + + class ErrorOnRequestTooLarge(MockBackend): + def __init__(self, buffer: List[bytes], http2: bool = False) -> None: + super().__init__(buffer, http2) + self.count = 0 + + def write(self, buffer: bytes, timeout: Optional[float] = None) -> None: + self.count += len(buffer) + + if self.count > 1_000_000: + raise WriteError() + + origin = Origin(b"https", b"example.com", 443) + network_backend = ErrorOnRequestTooLarge( + [ + b"HTTP/1.1 413 Payload Too Large\r\n", + b"Content-Type: plain/text\r\n", + b"Content-Length: 37\r\n", + b"\r\n", + b"Request body exceeded 1,000,000 bytes", + ] + ) + + with HTTPConnection( + origin=origin, network_backend=network_backend, keepalive_expiry=5.0 + ) as conn: + content = b"x" * 10_000_000 + response = conn.request("POST", "https://example.com/", content=content) + assert response.status == 413 + assert response.content == b"Request body exceeded 1,000,000 bytes" + + + def test_http2_connection(): origin = Origin(b"https", b"example.com", 443) network_backend = MockBackend( From 4185ebbf83641a4d500877466354b9cbbe44d1ac Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 13 Dec 2022 20:01:12 +0000 Subject: [PATCH 2/9] Fix 'test_write_error_but_response_sent' test case --- tests/_async/test_connection.py | 14 ++++++++++++-- tests/_sync/test_connection.py | 14 ++++++++++++-- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/tests/_async/test_connection.py b/tests/_async/test_connection.py index 44f56c09..cb857654 100644 --- a/tests/_async/test_connection.py +++ b/tests/_async/test_connection.py @@ -12,7 +12,7 @@ WriteError, ) from httpcore.backends.base import AsyncNetworkStream -from httpcore.backends.mock import AsyncMockBackend +from httpcore.backends.mock import AsyncMockBackend, AsyncMockStream @pytest.mark.anyio @@ -89,7 +89,7 @@ async def test_write_error_but_response_sent(): will raise a `ConnectionNotAvailable` exception. """ - class ErrorOnRequestTooLarge(AsyncMockBackend): + class ErrorOnRequestTooLargeStream(AsyncMockStream): def __init__(self, buffer: List[bytes], http2: bool = False) -> None: super().__init__(buffer, http2) self.count = 0 @@ -100,6 +100,16 @@ async def write(self, buffer: bytes, timeout: Optional[float] = None) -> None: if self.count > 1_000_000: raise WriteError() + class ErrorOnRequestTooLarge(AsyncMockBackend): + async def connect_tcp( + self, + host: str, + port: int, + timeout: Optional[float] = None, + local_address: Optional[str] = None, + ) -> AsyncMockStream: + return ErrorOnRequestTooLargeStream(list(self._buffer), http2=self._http2) + origin = Origin(b"https", b"example.com", 443) network_backend = ErrorOnRequestTooLarge( [ diff --git a/tests/_sync/test_connection.py b/tests/_sync/test_connection.py index 62aa7acf..9ac55c4c 100644 --- a/tests/_sync/test_connection.py +++ b/tests/_sync/test_connection.py @@ -12,7 +12,7 @@ WriteError, ) from httpcore.backends.base import NetworkStream -from httpcore.backends.mock import MockBackend +from httpcore.backends.mock import MockBackend, MockStream @@ -89,7 +89,7 @@ def test_write_error_but_response_sent(): will raise a `ConnectionNotAvailable` exception. """ - class ErrorOnRequestTooLarge(MockBackend): + class ErrorOnRequestTooLargeStream(MockStream): def __init__(self, buffer: List[bytes], http2: bool = False) -> None: super().__init__(buffer, http2) self.count = 0 @@ -100,6 +100,16 @@ def write(self, buffer: bytes, timeout: Optional[float] = None) -> None: if self.count > 1_000_000: raise WriteError() + class ErrorOnRequestTooLarge(MockBackend): + def connect_tcp( + self, + host: str, + port: int, + timeout: Optional[float] = None, + local_address: Optional[str] = None, + ) -> MockStream: + return ErrorOnRequestTooLargeStream(list(self._buffer), http2=self._http2) + origin = Origin(b"https", b"example.com", 443) network_backend = ErrorOnRequestTooLarge( [ From 8d2165e0f40dc2543a871be0e66fc121c2fb66c8 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 4 Jul 2023 13:30:58 +0100 Subject: [PATCH 3/9] Linting --- httpcore/_async/http11.py | 4 +++- httpcore/_sync/http11.py | 8 +++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/httpcore/_async/http11.py b/httpcore/_async/http11.py index a43f859e..32fa3a6f 100644 --- a/httpcore/_async/http11.py +++ b/httpcore/_async/http11.py @@ -86,7 +86,9 @@ async def handle_async_request(self, request: Request) -> Response: try: kwargs = {"request": request} try: - async with Trace("send_request_headers", logger, request, kwargs) as trace: + async with Trace( + "send_request_headers", logger, request, kwargs + ) as trace: await self._send_request_headers(**kwargs) async with Trace("send_request_body", logger, request, kwargs) as trace: await self._send_request_body(**kwargs) diff --git a/httpcore/_sync/http11.py b/httpcore/_sync/http11.py index f6a4cc81..0cc100e3 100644 --- a/httpcore/_sync/http11.py +++ b/httpcore/_sync/http11.py @@ -86,18 +86,20 @@ def handle_request(self, request: Request) -> Response: try: kwargs = {"request": request} try: - with Trace("send_request_headers", logger, request, kwargs) as trace: + with Trace( + "send_request_headers", logger, request, kwargs + ) as trace: self._send_request_headers(**kwargs) with Trace("send_request_body", logger, request, kwargs) as trace: self._send_request_body(**kwargs) - except WriteError: + except WriteError: # If we get a write error while we're writing the request, # then we supress this error and move on to attempting to # read the response. Servers can sometimes close the request # pre-emptively and then respond with a well formed HTTP # error response. pass - + with Trace( "receive_response_headers", logger, request, kwargs ) as trace: From 85e6b846bcf88560ffbe3b8ca91f3e40756071d1 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 4 Jul 2023 13:34:32 +0100 Subject: [PATCH 4/9] Fix imports --- tests/_async/test_connection.py | 8 ++++---- tests/_sync/test_connection.py | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/_async/test_connection.py b/tests/_async/test_connection.py index 1492e3cf..02b1bf92 100644 --- a/tests/_async/test_connection.py +++ b/tests/_async/test_connection.py @@ -93,11 +93,11 @@ async def test_write_error_but_response_sent(): """ class ErrorOnRequestTooLargeStream(AsyncMockStream): - def __init__(self, buffer: List[bytes], http2: bool = False) -> None: + def __init__(self, buffer: typing.List[bytes], http2: bool = False) -> None: super().__init__(buffer, http2) self.count = 0 - async def write(self, buffer: bytes, timeout: Optional[float] = None) -> None: + async def write(self, buffer: bytes, timeout: typing.Optional[float] = None) -> None: self.count += len(buffer) if self.count > 1_000_000: @@ -108,8 +108,8 @@ async def connect_tcp( self, host: str, port: int, - timeout: Optional[float] = None, - local_address: Optional[str] = None, + timeout: typing.Optional[float] = None, + local_address: typing.Optional[str] = None, ) -> AsyncMockStream: return ErrorOnRequestTooLargeStream(list(self._buffer), http2=self._http2) diff --git a/tests/_sync/test_connection.py b/tests/_sync/test_connection.py index 98c8d249..16f604ae 100644 --- a/tests/_sync/test_connection.py +++ b/tests/_sync/test_connection.py @@ -93,11 +93,11 @@ def test_write_error_but_response_sent(): """ class ErrorOnRequestTooLargeStream(MockStream): - def __init__(self, buffer: List[bytes], http2: bool = False) -> None: + def __init__(self, buffer: typing.List[bytes], http2: bool = False) -> None: super().__init__(buffer, http2) self.count = 0 - def write(self, buffer: bytes, timeout: Optional[float] = None) -> None: + def write(self, buffer: bytes, timeout: typing.Optional[float] = None) -> None: self.count += len(buffer) if self.count > 1_000_000: @@ -108,8 +108,8 @@ def connect_tcp( self, host: str, port: int, - timeout: Optional[float] = None, - local_address: Optional[str] = None, + timeout: typing.Optional[float] = None, + local_address: typing.Optional[str] = None, ) -> MockStream: return ErrorOnRequestTooLargeStream(list(self._buffer), http2=self._http2) From 9ad415ad8eee38d873917e04d45ac28d197e80cb Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 4 Jul 2023 13:34:52 +0100 Subject: [PATCH 5/9] Fix imports --- tests/_async/test_connection.py | 4 +++- tests/_sync/test_connection.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/_async/test_connection.py b/tests/_async/test_connection.py index 02b1bf92..56005157 100644 --- a/tests/_async/test_connection.py +++ b/tests/_async/test_connection.py @@ -97,7 +97,9 @@ def __init__(self, buffer: typing.List[bytes], http2: bool = False) -> None: super().__init__(buffer, http2) self.count = 0 - async def write(self, buffer: bytes, timeout: typing.Optional[float] = None) -> None: + async def write( + self, buffer: bytes, timeout: typing.Optional[float] = None + ) -> None: self.count += len(buffer) if self.count > 1_000_000: diff --git a/tests/_sync/test_connection.py b/tests/_sync/test_connection.py index 16f604ae..59494fdc 100644 --- a/tests/_sync/test_connection.py +++ b/tests/_sync/test_connection.py @@ -97,7 +97,9 @@ def __init__(self, buffer: typing.List[bytes], http2: bool = False) -> None: super().__init__(buffer, http2) self.count = 0 - def write(self, buffer: bytes, timeout: typing.Optional[float] = None) -> None: + def write( + self, buffer: bytes, timeout: typing.Optional[float] = None + ) -> None: self.count += len(buffer) if self.count > 1_000_000: From f8318ba1096ce5099c44f733fd155343071d6315 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 4 Jul 2023 13:37:34 +0100 Subject: [PATCH 6/9] Fix imports --- tests/_async/test_connection.py | 1 + tests/_sync/test_connection.py | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/_async/test_connection.py b/tests/_async/test_connection.py index 56005157..09dba83a 100644 --- a/tests/_async/test_connection.py +++ b/tests/_async/test_connection.py @@ -112,6 +112,7 @@ async def connect_tcp( port: int, timeout: typing.Optional[float] = None, local_address: typing.Optional[str] = None, + socket_options: typing.Optional[typing.Iterable[SOCKET_OPTION]] = None, ) -> AsyncMockStream: return ErrorOnRequestTooLargeStream(list(self._buffer), http2=self._http2) diff --git a/tests/_sync/test_connection.py b/tests/_sync/test_connection.py index 59494fdc..c784076d 100644 --- a/tests/_sync/test_connection.py +++ b/tests/_sync/test_connection.py @@ -112,6 +112,7 @@ def connect_tcp( port: int, timeout: typing.Optional[float] = None, local_address: typing.Optional[str] = None, + socket_options: typing.Optional[typing.Iterable[SOCKET_OPTION]] = None, ) -> MockStream: return ErrorOnRequestTooLargeStream(list(self._buffer), http2=self._http2) From 1e3dfda8796c8477319a4e711054e22a2105cc54 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 4 Jul 2023 14:00:04 +0100 Subject: [PATCH 7/9] Filter out 'PytestUnraisableExceptionWarning' --- tests/_async/test_connection.py | 1 + tests/_sync/test_connection.py | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/_async/test_connection.py b/tests/_async/test_connection.py index 09dba83a..34402f82 100644 --- a/tests/_async/test_connection.py +++ b/tests/_async/test_connection.py @@ -85,6 +85,7 @@ async def test_concurrent_requests_not_available_on_http11_connections(): await conn.request("GET", "https://example.com/") +@pytest.mark.filterwarnings("ignore::pytest.PytestUnraisableExceptionWarning") @pytest.mark.anyio async def test_write_error_but_response_sent(): """ diff --git a/tests/_sync/test_connection.py b/tests/_sync/test_connection.py index c784076d..247bd450 100644 --- a/tests/_sync/test_connection.py +++ b/tests/_sync/test_connection.py @@ -85,6 +85,7 @@ def test_concurrent_requests_not_available_on_http11_connections(): conn.request("GET", "https://example.com/") +@pytest.mark.filterwarnings("ignore::pytest.PytestUnraisableExceptionWarning") def test_write_error_but_response_sent(): """ From a331e1a13e8f2209afc1e872899b16cc0e19a51c Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 5 Jul 2023 16:18:39 +0100 Subject: [PATCH 8/9] Add tests for when the server closes the connection during request writing and does not send a response --- tests/_async/test_connection.py | 56 +++++++++++++++++++++++++++++++-- tests/_sync/test_connection.py | 55 ++++++++++++++++++++++++++++++-- 2 files changed, 105 insertions(+), 6 deletions(-) diff --git a/tests/_async/test_connection.py b/tests/_async/test_connection.py index 34402f82..38b2dbb8 100644 --- a/tests/_async/test_connection.py +++ b/tests/_async/test_connection.py @@ -14,6 +14,7 @@ ConnectError, ConnectionNotAvailable, Origin, + RemoteProtocolError, WriteError, ) @@ -87,10 +88,13 @@ async def test_concurrent_requests_not_available_on_http11_connections(): @pytest.mark.filterwarnings("ignore::pytest.PytestUnraisableExceptionWarning") @pytest.mark.anyio -async def test_write_error_but_response_sent(): +async def test_write_error_with_response_sent(): """ - Attempting to issue a request against an already active HTTP/1.1 connection - will raise a `ConnectionNotAvailable` exception. + If a server half-closes the connection while the client is sending + the request, it may still send a response. In this case the client + should successfully read and return the response. + + See also the `test_write_error_without_response_sent` test above. """ class ErrorOnRequestTooLargeStream(AsyncMockStream): @@ -137,6 +141,52 @@ async def connect_tcp( assert response.content == b"Request body exceeded 1,000,000 bytes" +@pytest.mark.filterwarnings("ignore::pytest.PytestUnraisableExceptionWarning") +@pytest.mark.anyio +async def test_write_error_without_response_sent(): + """ + If a server fully closes the connection while the client is sending + the request, then client should raise an error. + + See also the `test_write_error_with_response_sent` test above. + """ + + class ErrorOnRequestTooLargeStream(AsyncMockStream): + def __init__(self, buffer: typing.List[bytes], http2: bool = False) -> None: + super().__init__(buffer, http2) + self.count = 0 + + async def write( + self, buffer: bytes, timeout: typing.Optional[float] = None + ) -> None: + self.count += len(buffer) + + if self.count > 1_000_000: + raise WriteError() + + class ErrorOnRequestTooLarge(AsyncMockBackend): + async def connect_tcp( + self, + host: str, + port: int, + timeout: typing.Optional[float] = None, + local_address: typing.Optional[str] = None, + socket_options: typing.Optional[typing.Iterable[SOCKET_OPTION]] = None, + ) -> AsyncMockStream: + return ErrorOnRequestTooLargeStream(list(self._buffer), http2=self._http2) + + origin = Origin(b"https", b"example.com", 443) + network_backend = ErrorOnRequestTooLarge([]) + + async with AsyncHTTPConnection( + origin=origin, network_backend=network_backend, keepalive_expiry=5.0 + ) as conn: + content = b"x" * 10_000_000 + with pytest.raises(RemoteProtocolError) as exc_info: + await conn.request("POST", "https://example.com/", content=content) + assert str(exc_info.value) == "Server disconnected without sending a response." + + @pytest.mark.anyio async def test_http2_connection(): origin = Origin(b"https", b"example.com", 443) diff --git a/tests/_sync/test_connection.py b/tests/_sync/test_connection.py index 247bd450..bb8e4b2f 100644 --- a/tests/_sync/test_connection.py +++ b/tests/_sync/test_connection.py @@ -14,6 +14,7 @@ ConnectError, ConnectionNotAvailable, Origin, + RemoteProtocolError, WriteError, ) @@ -87,10 +88,13 @@ def test_concurrent_requests_not_available_on_http11_connections(): @pytest.mark.filterwarnings("ignore::pytest.PytestUnraisableExceptionWarning") -def test_write_error_but_response_sent(): +def test_write_error_with_response_sent(): """ - Attempting to issue a request against an already active HTTP/1.1 connection - will raise a `ConnectionNotAvailable` exception. + If a server half-closes the connection while the client is sending + the request, it may still send a response. In this case the client + should successfully read and return the response. + + See also the `test_write_error_without_response_sent` test above. """ class ErrorOnRequestTooLargeStream(MockStream): @@ -138,6 +142,51 @@ def connect_tcp( +def test_write_error_without_response_sent(): + """ + If a server fully closes the connection while the client is sending + the request, then client should raise an error. + + See also the `test_write_error_with_response_sent` test above. + """ + + class ErrorOnRequestTooLargeStream(MockStream): + def __init__(self, buffer: typing.List[bytes], http2: bool = False) -> None: + super().__init__(buffer, http2) + self.count = 0 + + def write( + self, buffer: bytes, timeout: typing.Optional[float] = None + ) -> None: + self.count += len(buffer) + + if self.count > 1_000_000: + raise WriteError() + + class ErrorOnRequestTooLarge(MockBackend): + def connect_tcp( + self, + host: str, + port: int, + timeout: typing.Optional[float] = None, + local_address: typing.Optional[str] = None, + socket_options: typing.Optional[typing.Iterable[SOCKET_OPTION]] = None, + ) -> MockStream: + return ErrorOnRequestTooLargeStream(list(self._buffer), http2=self._http2) + + origin = Origin(b"https", b"example.com", 443) + network_backend = ErrorOnRequestTooLarge([]) + + with HTTPConnection( + origin=origin, network_backend=network_backend, keepalive_expiry=5.0 + ) as conn: + content = b"x" * 10_000_000 + with pytest.raises(RemoteProtocolError) as exc_info: + conn.request("POST", "https://example.com/", content=content) + assert str(exc_info.value) == "Server disconnected without sending a response." + + + def test_http2_connection(): origin = Origin(b"https", b"example.com", 443) network_backend = MockBackend( From d5621c19e2b769241afc128f91988e57f2a7d8c0 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 5 Jul 2023 16:33:02 +0100 Subject: [PATCH 9/9] Linting --- tests/_async/test_connection.py | 3 ++- tests/_sync/test_connection.py | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/_async/test_connection.py b/tests/_async/test_connection.py index 38b2dbb8..b6ee0c7e 100644 --- a/tests/_async/test_connection.py +++ b/tests/_async/test_connection.py @@ -141,8 +141,8 @@ async def connect_tcp( assert response.content == b"Request body exceeded 1,000,000 bytes" -@pytest.mark.filterwarnings("ignore::pytest.PytestUnraisableExceptionWarning") @pytest.mark.anyio +@pytest.mark.filterwarnings("ignore::pytest.PytestUnraisableExceptionWarning") async def test_write_error_without_response_sent(): """ If a server fully closes the connection while the client is sending @@ -188,6 +188,7 @@ async def connect_tcp( @pytest.mark.anyio +@pytest.mark.filterwarnings("ignore::pytest.PytestUnraisableExceptionWarning") async def test_http2_connection(): origin = Origin(b"https", b"example.com", 443) network_backend = AsyncMockBackend( diff --git a/tests/_sync/test_connection.py b/tests/_sync/test_connection.py index bb8e4b2f..37c82e02 100644 --- a/tests/_sync/test_connection.py +++ b/tests/_sync/test_connection.py @@ -142,6 +142,7 @@ def connect_tcp( +@pytest.mark.filterwarnings("ignore::pytest.PytestUnraisableExceptionWarning") def test_write_error_without_response_sent(): """ If a server fully closes the connection while the client is sending @@ -187,6 +188,7 @@ def connect_tcp( +@pytest.mark.filterwarnings("ignore::pytest.PytestUnraisableExceptionWarning") def test_http2_connection(): origin = Origin(b"https", b"example.com", 443) network_backend = MockBackend(