]> git.ipfire.org Git - thirdparty/httpx.git/commitdiff
Handle h11.Connection.next_event() RemoteProtocolError (#524)
authortoppk <toppk@bllue.org>
Fri, 22 Nov 2019 08:33:40 +0000 (03:33 -0500)
committerFlorimond Manca <florimond.manca@gmail.com>
Fri, 22 Nov 2019 08:33:40 +0000 (09:33 +0100)
raise (new) ConnectionClosed exception if this occurs when
socket is closed, otherwise reuse ProtocolError exception.

Add test case for ConnectionClosed behavior.

Resolves #96

httpx/dispatch/http11.py
httpx/exceptions.py
tests/conftest.py
tests/dispatch/test_connections.py

index b1781bffa46379de839fc3e45ef361d0904fb7bc..188d56dc99c242c6f5d73491a33c6b10420330f6 100644 (file)
@@ -4,6 +4,7 @@ import h11
 
 from ..concurrency.base import BaseSocketStream, ConcurrencyBackend, TimeoutFlag
 from ..config import TimeoutConfig, TimeoutTypes
+from ..exceptions import ConnectionClosed, ProtocolError
 from ..models import AsyncRequest, AsyncResponse
 from ..utils import get_logger
 
@@ -161,7 +162,17 @@ class HTTP11Connection:
         Read a single `h11` event, reading more data from the network if needed.
         """
         while True:
-            event = self.h11_state.next_event()
+            try:
+                event = self.h11_state.next_event()
+            except h11.RemoteProtocolError as e:
+                logger.debug(
+                    "h11.RemoteProtocolError exception "
+                    + f"their_state={self.h11_state.their_state} "
+                    + f"error_status_hint={e.error_status_hint}"
+                )
+                if self.stream.is_connection_dropped():
+                    raise ConnectionClosed(e)
+                raise ProtocolError(e)
 
             if isinstance(event, h11.Data):
                 logger.trace(f"receive_event event=Data(<{len(event.data)} bytes>)")
index 81df38b79d7d1990b45fbcc5991667b407d2ab2f..419a21a1a5d2a59eb61ed2055dd9efdd7bfd6095 100644 (file)
@@ -150,6 +150,12 @@ class InvalidURL(HTTPError):
     """
 
 
+class ConnectionClosed(HTTPError):
+    """
+    Expected more data from peer, but connection was closed.
+    """
+
+
 class CookieConflict(HTTPError):
     """
     Attempted to lookup a cookie by name, but multiple cookies existed.
index de67ff7f921d95816bf552360591d92d038f2de9..540b013fefc2c36df28582dc96c5ffe6efe83e9f 100644 (file)
@@ -63,6 +63,8 @@ async def app(scope, receive, send):
     assert scope["type"] == "http"
     if scope["path"].startswith("/slow_response"):
         await slow_response(scope, receive, send)
+    elif scope["path"].startswith("/premature_close"):
+        await premature_close(scope, receive, send)
     elif scope["path"].startswith("/status"):
         await status_code(scope, receive, send)
     elif scope["path"].startswith("/echo_body"):
@@ -103,6 +105,16 @@ async def slow_response(scope, receive, send):
     await send({"type": "http.response.body", "body": b"Hello, world!"})
 
 
+async def premature_close(scope, receive, send):
+    await send(
+        {
+            "type": "http.response.start",
+            "status": 200,
+            "headers": [[b"content-type", b"text/plain"]],
+        }
+    )
+
+
 async def status_code(scope, receive, send):
     status_code = int(scope["path"].replace("/status/", ""))
     await send(
index b4aea17b08183043440ea2894810aca7444bf772..f39ac4fb6d7328c8166884fb12a6d5aa43a8e898 100644 (file)
@@ -1,4 +1,6 @@
-from httpx import HTTPConnection
+import pytest
+
+from httpx import HTTPConnection, exceptions
 
 
 async def test_get(server, backend):
@@ -15,6 +17,15 @@ async def test_post(server, backend):
         assert response.status_code == 200
 
 
+async def test_premature_close(server, backend):
+    with pytest.raises(exceptions.ConnectionClosed):
+        async with HTTPConnection(origin=server.url, backend=backend) as conn:
+            response = await conn.request(
+                "GET", server.url.copy_with(path="/premature_close")
+            )
+            await response.read()
+
+
 async def test_https_get_with_ssl_defaults(https_server, ca_cert_pem_file, backend):
     """
     An HTTPS request, with default SSL configuration set on the client.