Skip to content

Commit

Permalink
Allow sending of the response before body data arrives
Browse files Browse the repository at this point in the history
This was an ASGI specification condition that has now been relaxed.
  • Loading branch information
pgjones committed May 27, 2024
1 parent d1c1a23 commit a40aa2c
Show file tree
Hide file tree
Showing 2 changed files with 11 additions and 27 deletions.
25 changes: 10 additions & 15 deletions src/hypercorn/protocol/http_stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,15 @@ async def app_send(self, message: Optional[ASGISendEvent]) -> None:
else:
if message["type"] == "http.response.start" and self.state == ASGIHTTPState.REQUEST:
self.response = message
headers = build_and_validate_headers(self.response.get("headers", []))
await self.send(
Response(
stream_id=self.stream_id,
headers=headers,
status_code=int(self.response["status"]),
)
)
self.state = ASGIHTTPState.RESPONSE
elif (
message["type"] == "http.response.push"
and self.scope["http_version"] in PUSH_VERSIONS
Expand Down Expand Up @@ -175,21 +184,7 @@ async def app_send(self, message: Optional[ASGISendEvent]) -> None:
status_code=103,
)
)
elif message["type"] == "http.response.body" and self.state in {
ASGIHTTPState.REQUEST,
ASGIHTTPState.RESPONSE,
}:
if self.state == ASGIHTTPState.REQUEST:
headers = build_and_validate_headers(self.response.get("headers", []))
await self.send(
Response(
stream_id=self.stream_id,
headers=headers,
status_code=int(self.response["status"]),
)
)
self.state = ASGIHTTPState.RESPONSE

elif message["type"] == "http.response.body" and self.state == ASGIHTTPState.RESPONSE:
if (
not suppress_body(self.scope["method"], int(self.response["status"]))
and message.get("body", b"") != b""
Expand Down
13 changes: 1 addition & 12 deletions tests/protocol/test_http_stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,7 @@ async def test_send_response(stream: HTTPStream) -> None:
await stream.app_send(
cast(HTTPResponseStartEvent, {"type": "http.response.start", "status": 200, "headers": []})
)
assert stream.state == ASGIHTTPState.REQUEST
# Must wait for response before sending anything
stream.send.assert_not_called() # type: ignore
assert stream.state == ASGIHTTPState.RESPONSE
await stream.app_send(
cast(HTTPResponseBodyEvent, {"type": "http.response.body", "body": b"Body"})
)
Expand Down Expand Up @@ -413,15 +411,6 @@ async def test_closure(stream: HTTPStream) -> None:
assert stream.app_put.call_args_list == [call({"type": "http.disconnect"})]


@pytest.mark.asyncio
async def test_closed_app_send_noop(stream: HTTPStream) -> None:
stream.closed = True
await stream.app_send(
cast(HTTPResponseStartEvent, {"type": "http.response.start", "status": 200, "headers": []})
)
stream.send.assert_not_called() # type: ignore


@pytest.mark.asyncio
async def test_abnormal_close_logging() -> None:
config = Config()
Expand Down

0 comments on commit a40aa2c

Please sign in to comment.