From 9c0335de5677ad4dd8ef76717cf76791cd73a06c Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 28 Apr 2021 16:23:09 +0100 Subject: [PATCH 1/2] Raise ConnectError when server disconnects without sending a response --- httpcore/_async/http11.py | 14 +++++++++++++- httpcore/_sync/http11.py | 14 +++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/httpcore/_async/http11.py b/httpcore/_async/http11.py index 32bc5c41e..152b65d18 100644 --- a/httpcore/_async/http11.py +++ b/httpcore/_async/http11.py @@ -5,7 +5,7 @@ from .._backends.auto import AsyncSocketStream from .._bytestreams import AsyncIteratorByteStream -from .._exceptions import LocalProtocolError, RemoteProtocolError, map_exceptions +from .._exceptions import ConnectError, LocalProtocolError, RemoteProtocolError, map_exceptions from .._types import URL, Headers, TimeoutDict from .._utils import get_logger from .base import AsyncByteStream, ConnectionState @@ -167,6 +167,18 @@ async def _receive_event(self, timeout: TimeoutDict) -> H11Event: if event is h11.NEED_DATA: data = await self.socket.read(self.READ_NUM_BYTES, timeout) + + # If we feed this case through h11 we'll raise an exception like: + # + # httpcore.RemoteProtocolError: can't handle event type + # ConnectionClosed when role=SERVER and state=SEND_RESPONSE + # + # Which is accurate, but not very informative from an end-user + # perspective. Instead we handle this case distinctly and treat + # it as a ConnectError. + if data == b"" and self.h11_state.their_state == h11.SEND_RESPONSE: + raise ConnectError("Server disconnected without sending response.") + self.h11_state.receive_data(data) else: assert event is not h11.NEED_DATA diff --git a/httpcore/_sync/http11.py b/httpcore/_sync/http11.py index a805b8bad..1a90fc961 100644 --- a/httpcore/_sync/http11.py +++ b/httpcore/_sync/http11.py @@ -5,7 +5,7 @@ from .._backends.sync import SyncSocketStream from .._bytestreams import IteratorByteStream -from .._exceptions import LocalProtocolError, RemoteProtocolError, map_exceptions +from .._exceptions import ConnectError, LocalProtocolError, RemoteProtocolError, map_exceptions from .._types import URL, Headers, TimeoutDict from .._utils import get_logger from .base import SyncByteStream, ConnectionState @@ -167,6 +167,18 @@ def _receive_event(self, timeout: TimeoutDict) -> H11Event: if event is h11.NEED_DATA: data = self.socket.read(self.READ_NUM_BYTES, timeout) + + # If we feed this case through h11 we'll raise an exception like: + # + # httpcore.RemoteProtocolError: can't handle event type + # ConnectionClosed when role=SERVER and state=SEND_RESPONSE + # + # Which is accurate, but not very informative from an end-user + # perspective. Instead we handle this case distinctly and treat + # it as a ConnectError. + if data == b"" and self.h11_state.their_state == h11.SEND_RESPONSE: + raise ConnectError("Server disconnected without sending response") + self.h11_state.receive_data(data) else: assert event is not h11.NEED_DATA From 9289ea26d883ccffccc989dd45f00cab014fef2d Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 28 Apr 2021 16:47:14 +0100 Subject: [PATCH 2/2] Tweak exception in 'disconnected without sending a response' case --- httpcore/_async/http11.py | 5 +++-- httpcore/_sync/http11.py | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/httpcore/_async/http11.py b/httpcore/_async/http11.py index 152b65d18..3b55ee2a3 100644 --- a/httpcore/_async/http11.py +++ b/httpcore/_async/http11.py @@ -5,7 +5,7 @@ from .._backends.auto import AsyncSocketStream from .._bytestreams import AsyncIteratorByteStream -from .._exceptions import ConnectError, LocalProtocolError, RemoteProtocolError, map_exceptions +from .._exceptions import LocalProtocolError, RemoteProtocolError, map_exceptions from .._types import URL, Headers, TimeoutDict from .._utils import get_logger from .base import AsyncByteStream, ConnectionState @@ -177,7 +177,8 @@ async def _receive_event(self, timeout: TimeoutDict) -> H11Event: # perspective. Instead we handle this case distinctly and treat # it as a ConnectError. if data == b"" and self.h11_state.their_state == h11.SEND_RESPONSE: - raise ConnectError("Server disconnected without sending response.") + msg = "Server disconnected without sending a response." + raise RemoteProtocolError(msg) self.h11_state.receive_data(data) else: diff --git a/httpcore/_sync/http11.py b/httpcore/_sync/http11.py index 1a90fc961..3cd1c3995 100644 --- a/httpcore/_sync/http11.py +++ b/httpcore/_sync/http11.py @@ -5,7 +5,7 @@ from .._backends.sync import SyncSocketStream from .._bytestreams import IteratorByteStream -from .._exceptions import ConnectError, LocalProtocolError, RemoteProtocolError, map_exceptions +from .._exceptions import LocalProtocolError, RemoteProtocolError, map_exceptions from .._types import URL, Headers, TimeoutDict from .._utils import get_logger from .base import SyncByteStream, ConnectionState @@ -177,7 +177,8 @@ def _receive_event(self, timeout: TimeoutDict) -> H11Event: # perspective. Instead we handle this case distinctly and treat # it as a ConnectError. if data == b"" and self.h11_state.their_state == h11.SEND_RESPONSE: - raise ConnectError("Server disconnected without sending response") + msg = "Server disconnected without sending a response." + raise RemoteProtocolError(msg) self.h11_state.receive_data(data) else: