Skip to content

Commit

Permalink
Handle 1xx interim response codes (#605)
Browse files Browse the repository at this point in the history
* Tests for 100 and 103 status codes

* Fix behaviour for 100 and 103 status codes
  • Loading branch information
tomchristie committed Nov 17, 2022
1 parent 18ba03a commit c03eea0
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 4 deletions.
7 changes: 6 additions & 1 deletion httpcore/_async/http11.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,12 @@ async def _receive_response_headers(

while True:
event = await self._receive_event(timeout=timeout)
if isinstance(event, (h11.Response, h11.InformationalResponse)):
if isinstance(event, h11.Response):
break
if (
isinstance(event, h11.InformationalResponse)
and event.status_code == 101
):
break

http_version = b"HTTP/" + event.http_version
Expand Down
7 changes: 6 additions & 1 deletion httpcore/_sync/http11.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,12 @@ def _receive_response_headers(

while True:
event = self._receive_event(timeout=timeout)
if isinstance(event, (h11.Response, h11.InformationalResponse)):
if isinstance(event, h11.Response):
break
if (
isinstance(event, h11.InformationalResponse)
and event.status_code == 101
):
break

http_version = b"HTTP/" + event.http_version
Expand Down
80 changes: 79 additions & 1 deletion tests/_async/test_http11.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,12 +208,54 @@ async def test_http11_request_to_incorrect_origin():
await conn.request("GET", "https://other.com/")


@pytest.mark.anyio
async def test_http11_expect_continue():
"""
HTTP "100 Continue" is an interim response.
We simply ignore it and return the final response.
https://httpwg.org/specs/rfc9110.html#status.100
https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/100
"""
origin = Origin(b"https", b"example.com", 443)
stream = AsyncMockStream(
[
b"HTTP/1.1 100 Continue\r\n",
b"\r\n",
b"HTTP/1.1 200 OK\r\n",
b"Content-Type: plain/text\r\n",
b"Content-Length: 13\r\n",
b"\r\n",
b"Hello, world!",
]
)
async with AsyncHTTP11Connection(
origin=origin, stream=stream, keepalive_expiry=5.0
) as conn:
response = await conn.request(
"GET",
"https://example.com/",
headers={"Expect": "continue"},
)
assert response.status == 200
assert response.content == b"Hello, world!"


@pytest.mark.anyio
async def test_http11_upgrade_connection():
"""
HTTP "101 Switching Protocols" indicates an upgraded connection.
We should return the response, so that the network stream
may be used for the upgraded connection.
https://httpwg.org/specs/rfc9110.html#status.101
https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/101
"""
origin = Origin(b"https", b"example.com", 443)
stream = AsyncMockStream(
[
b"HTTP/1.1 101 OK\r\n",
b"HTTP/1.1 101 Switching Protocols\r\n",
b"Connection: upgrade\r\n",
b"Upgrade: custom\r\n",
b"\r\n",
Expand All @@ -232,3 +274,39 @@ async def test_http11_upgrade_connection():
network_stream = response.extensions["network_stream"]
content = await network_stream.read(max_bytes=1024)
assert content == b"..."


@pytest.mark.anyio
async def test_http11_early_hints():
"""
HTTP "103 Early Hints" is an interim response.
We simply ignore it and return the final response.
https://datatracker.ietf.org/doc/rfc8297/
"""
origin = Origin(b"https", b"example.com", 443)
stream = AsyncMockStream(
[
b"HTTP/1.1 103 Early Hints\r\n",
b"Link: </style.css>; rel=preload; as=style\r\n",
b"Link: </script.js.css>; rel=preload; as=style\r\n",
b"\r\n",
b"HTTP/1.1 200 OK\r\n",
b"Content-Type: text/html; charset=utf-8\r\n",
b"Content-Length: 30\r\n",
b"Link: </style.css>; rel=preload; as=style\r\n",
b"Link: </script.js>; rel=preload; as=script\r\n",
b"\r\n",
b"<html>Hello, world! ...</html>",
]
)
async with AsyncHTTP11Connection(
origin=origin, stream=stream, keepalive_expiry=5.0
) as conn:
response = await conn.request(
"GET",
"https://example.com/",
headers={"Expect": "continue"},
)
assert response.status == 200
assert response.content == b"<html>Hello, world! ...</html>"
80 changes: 79 additions & 1 deletion tests/_sync/test_http11.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,11 +209,53 @@ def test_http11_request_to_incorrect_origin():



def test_http11_expect_continue():
"""
HTTP "100 Continue" is an interim response.
We simply ignore it and return the final response.
https://httpwg.org/specs/rfc9110.html#status.100
https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/100
"""
origin = Origin(b"https", b"example.com", 443)
stream = MockStream(
[
b"HTTP/1.1 100 Continue\r\n",
b"\r\n",
b"HTTP/1.1 200 OK\r\n",
b"Content-Type: plain/text\r\n",
b"Content-Length: 13\r\n",
b"\r\n",
b"Hello, world!",
]
)
with HTTP11Connection(
origin=origin, stream=stream, keepalive_expiry=5.0
) as conn:
response = conn.request(
"GET",
"https://example.com/",
headers={"Expect": "continue"},
)
assert response.status == 200
assert response.content == b"Hello, world!"



def test_http11_upgrade_connection():
"""
HTTP "101 Switching Protocols" indicates an upgraded connection.
We should return the response, so that the network stream
may be used for the upgraded connection.
https://httpwg.org/specs/rfc9110.html#status.101
https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/101
"""
origin = Origin(b"https", b"example.com", 443)
stream = MockStream(
[
b"HTTP/1.1 101 OK\r\n",
b"HTTP/1.1 101 Switching Protocols\r\n",
b"Connection: upgrade\r\n",
b"Upgrade: custom\r\n",
b"\r\n",
Expand All @@ -232,3 +274,39 @@ def test_http11_upgrade_connection():
network_stream = response.extensions["network_stream"]
content = network_stream.read(max_bytes=1024)
assert content == b"..."



def test_http11_early_hints():
"""
HTTP "103 Early Hints" is an interim response.
We simply ignore it and return the final response.
https://datatracker.ietf.org/doc/rfc8297/
"""
origin = Origin(b"https", b"example.com", 443)
stream = MockStream(
[
b"HTTP/1.1 103 Early Hints\r\n",
b"Link: </style.css>; rel=preload; as=style\r\n",
b"Link: </script.js.css>; rel=preload; as=style\r\n",
b"\r\n",
b"HTTP/1.1 200 OK\r\n",
b"Content-Type: text/html; charset=utf-8\r\n",
b"Content-Length: 30\r\n",
b"Link: </style.css>; rel=preload; as=style\r\n",
b"Link: </script.js>; rel=preload; as=script\r\n",
b"\r\n",
b"<html>Hello, world! ...</html>",
]
)
with HTTP11Connection(
origin=origin, stream=stream, keepalive_expiry=5.0
) as conn:
response = conn.request(
"GET",
"https://example.com/",
headers={"Expect": "continue"},
)
assert response.status == 200
assert response.content == b"<html>Hello, world! ...</html>"

0 comments on commit c03eea0

Please sign in to comment.