From fea58e2e6fc98fa7fce65a6e0b41b17a0663a66b Mon Sep 17 00:00:00 2001 From: AryanRG Date: Sat, 17 Oct 2026 09:19:46 +0530 Subject: [PATCH 1/2] Keep Expect wait through non-100 interim responses --- docs/source/api.rst | 20 +++++++++++--------- h11/_connection.py | 6 +++++- h11/tests/test_connection.py | 11 ++++++++++- newsfragments/early-hints.bugfix.rst | 3 +++ 4 files changed, 29 insertions(+), 11 deletions(-) create mode 100644 newsfragments/early-hints.bugfix.rst diff --git a/docs/source/api.rst b/docs/source/api.rst index 4b6798e..c264c72 100644 --- a/docs/source/api.rst +++ b/docs/source/api.rst @@ -431,9 +431,10 @@ The Connection object True if the client sent a request with the ``Expect: 100-continue`` header, and is still waiting for a response - (i.e., the server has not sent a 100 Continue or any other kind - of response, and the client has not gone ahead and started - sending the body anyway). + (i.e., the server has not sent a 100 Continue, switched protocols, + or sent a final response, and the client has not gone ahead and + started sending the body anyway). Other informational responses, + such as 103 Early Hints, do not clear this flag. See `RFC 7231 section 5.1.1 `_ for details. @@ -695,12 +696,13 @@ have to be prepared to handle :class:`Request`\s that have an ``Expect: 100-continue`` header. You can `read the spec `_ for the full details, but basically what this header means is that after sending -the :class:`Request`, the client plans to pause and wait until they -see some response from the server before they send that request's -:class:`Data`. The server's response would normally be an -:class:`InformationalResponse` with status ``100 Continue``, but it -could be anything really (e.g. a full :class:`Response` with a 4xx -status code). The crucial thing as a server, though, is that you +the :class:`Request`, the client plans to pause and wait for a +``100 Continue`` or a final response before they send that request's +:class:`Data`. The server can send an :class:`InformationalResponse` +with status ``100 Continue`` to request the body, or a final +:class:`Response` (e.g. with a 4xx status code). Other informational +responses, such as ``103 Early Hints``, leave the client waiting for +``100 Continue``. The crucial thing as a server, though, is that you should never block trying to read a request body if the client is blocked waiting for you to tell them to send the request body. diff --git a/h11/_connection.py b/h11/_connection.py index e37d82a..3c54e96 100644 --- a/h11/_connection.py +++ b/h11/_connection.py @@ -310,7 +310,11 @@ def _process_event(self, role: Type[Sentinel], event: Event) -> None: # 100-continue if type(event) is Request and has_expect_100_continue(event): self.client_is_waiting_for_100_continue = True - if type(event) in (InformationalResponse, Response): + # Other informational responses, such as 103 Early Hints, do not + # tell the client to send its request body. + if type(event) is Response or ( + type(event) is InformationalResponse and event.status_code in (100, 101) + ): self.client_is_waiting_for_100_continue = False if role is CLIENT and type(event) in (Data, EndOfMessage): self.client_is_waiting_for_100_continue = False diff --git a/h11/tests/test_connection.py b/h11/tests/test_connection.py index 01260dc..c3b8a7b 100644 --- a/h11/tests/test_connection.py +++ b/h11/tests/test_connection.py @@ -365,7 +365,8 @@ def test_automagic_connection_close_handling() -> None: assert conn.states == {CLIENT: MUST_CLOSE, SERVER: MUST_CLOSE} -def test_100_continue() -> None: +@pytest.mark.parametrize("informational_status", [None, 102, 103]) +def test_100_continue(informational_status: Optional[int]) -> None: def setup() -> ConnectionPair: p = ConnectionPair() p.send( @@ -382,6 +383,14 @@ def setup() -> ConnectionPair: ) for conn in p.conns: assert conn.client_is_waiting_for_100_continue + if informational_status is not None: + for _ in range(2): + p.send( + SERVER, + InformationalResponse(status_code=informational_status, headers=[]), + ) + for conn in p.conns: + assert conn.client_is_waiting_for_100_continue assert not p.conn[CLIENT].they_are_waiting_for_100_continue assert p.conn[SERVER].they_are_waiting_for_100_continue return p diff --git a/newsfragments/early-hints.bugfix.rst b/newsfragments/early-hints.bugfix.rst new file mode 100644 index 0000000..837c369 --- /dev/null +++ b/newsfragments/early-hints.bugfix.rst @@ -0,0 +1,3 @@ +Preserve the waiting-for-100-continue state after informational responses +such as 103 Early Hints, so servers still send 100 Continue before waiting +for the request body. From 47099bca1af51642e98622d7d5951827dbec570c Mon Sep 17 00:00:00 2001 From: AryanRG Date: Sat, 17 Oct 2026 09:20:36 +0530 Subject: [PATCH 2/2] Name release note for PR 211 --- newsfragments/{early-hints.bugfix.rst => 211.bugfix.rst} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename newsfragments/{early-hints.bugfix.rst => 211.bugfix.rst} (100%) diff --git a/newsfragments/early-hints.bugfix.rst b/newsfragments/211.bugfix.rst similarity index 100% rename from newsfragments/early-hints.bugfix.rst rename to newsfragments/211.bugfix.rst