Skip to content

Commit 7e628f4

Browse files
authored
[PR #8699/11f0e7f backport][3.11] Reduce code indent in ResponseHandler.data_received (#10056)
1 parent 1a6fafe commit 7e628f4

File tree

2 files changed

+135
-54
lines changed

2 files changed

+135
-54
lines changed

aiohttp/client_proto.py

Lines changed: 52 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ def data_received(self, data: bytes) -> None:
242242
if not data:
243243
return
244244

245-
# custom payload parser
245+
# custom payload parser - currently always WebSocketReader
246246
if self._payload_parser is not None:
247247
eof, tail = self._payload_parser.feed_data(data)
248248
if eof:
@@ -252,57 +252,56 @@ def data_received(self, data: bytes) -> None:
252252
if tail:
253253
self.data_received(tail)
254254
return
255-
else:
256-
if self._upgraded or self._parser is None:
257-
# i.e. websocket connection, websocket parser is not set yet
258-
self._tail += data
255+
256+
if self._upgraded or self._parser is None:
257+
# i.e. websocket connection, websocket parser is not set yet
258+
self._tail += data
259+
return
260+
261+
# parse http messages
262+
try:
263+
messages, upgraded, tail = self._parser.feed_data(data)
264+
except BaseException as underlying_exc:
265+
if self.transport is not None:
266+
# connection.release() could be called BEFORE
267+
# data_received(), the transport is already
268+
# closed in this case
269+
self.transport.close()
270+
# should_close is True after the call
271+
if isinstance(underlying_exc, HttpProcessingError):
272+
exc = HttpProcessingError(
273+
code=underlying_exc.code,
274+
message=underlying_exc.message,
275+
headers=underlying_exc.headers,
276+
)
259277
else:
260-
# parse http messages
261-
try:
262-
messages, upgraded, tail = self._parser.feed_data(data)
263-
except BaseException as underlying_exc:
264-
if self.transport is not None:
265-
# connection.release() could be called BEFORE
266-
# data_received(), the transport is already
267-
# closed in this case
268-
self.transport.close()
269-
# should_close is True after the call
270-
if isinstance(underlying_exc, HttpProcessingError):
271-
exc = HttpProcessingError(
272-
code=underlying_exc.code,
273-
message=underlying_exc.message,
274-
headers=underlying_exc.headers,
275-
)
276-
else:
277-
exc = HttpProcessingError()
278-
self.set_exception(exc, underlying_exc)
279-
return
280-
281-
self._upgraded = upgraded
282-
283-
payload: Optional[StreamReader] = None
284-
for message, payload in messages:
285-
if message.should_close:
286-
self._should_close = True
287-
288-
self._payload = payload
289-
290-
if self._skip_payload or message.code in EMPTY_BODY_STATUS_CODES:
291-
self.feed_data((message, EMPTY_PAYLOAD), 0)
292-
else:
293-
self.feed_data((message, payload), 0)
294-
if payload is not None:
295-
# new message(s) was processed
296-
# register timeout handler unsubscribing
297-
# either on end-of-stream or immediately for
298-
# EMPTY_PAYLOAD
299-
if payload is not EMPTY_PAYLOAD:
300-
payload.on_eof(self._drop_timeout)
301-
else:
302-
self._drop_timeout()
278+
exc = HttpProcessingError()
279+
self.set_exception(exc, underlying_exc)
280+
return
303281

304-
if tail:
305-
if upgraded:
306-
self.data_received(tail)
307-
else:
308-
self._tail = tail
282+
self._upgraded = upgraded
283+
284+
payload: Optional[StreamReader] = None
285+
for message, payload in messages:
286+
if message.should_close:
287+
self._should_close = True
288+
289+
self._payload = payload
290+
291+
if self._skip_payload or message.code in EMPTY_BODY_STATUS_CODES:
292+
self.feed_data((message, EMPTY_PAYLOAD), 0)
293+
else:
294+
self.feed_data((message, payload), 0)
295+
296+
if payload is not None:
297+
# new message(s) was processed
298+
# register timeout handler unsubscribing
299+
# either on end-of-stream or immediately for
300+
# EMPTY_PAYLOAD
301+
if payload is not EMPTY_PAYLOAD:
302+
payload.on_eof(self._drop_timeout)
303+
else:
304+
self._drop_timeout()
305+
306+
if upgraded and tail:
307+
self.data_received(tail)

tests/test_client_proto.py

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,89 @@ async def test_uncompleted_message(loop) -> None:
7272
assert dict(exc.message.headers) == {"Location": "http://python.org/"}
7373

7474

75-
async def test_client_protocol_readuntil_eof(loop) -> None:
75+
async def test_data_received_after_close(loop: asyncio.AbstractEventLoop) -> None:
76+
proto = ResponseHandler(loop=loop)
77+
transport = mock.Mock()
78+
proto.connection_made(transport)
79+
proto.set_response_params(read_until_eof=True)
80+
proto.close()
81+
assert transport.close.called
82+
transport.close.reset_mock()
83+
proto.data_received(b"HTTP\r\n\r\n")
84+
assert proto.should_close
85+
assert not transport.close.called
86+
assert isinstance(proto.exception(), http.HttpProcessingError)
87+
88+
89+
async def test_multiple_responses_one_byte_at_a_time(
90+
loop: asyncio.AbstractEventLoop,
91+
) -> None:
92+
proto = ResponseHandler(loop=loop)
93+
proto.connection_made(mock.Mock())
94+
conn = mock.Mock(protocol=proto)
95+
proto.set_response_params(read_until_eof=True)
96+
97+
for _ in range(2):
98+
messages = (
99+
b"HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nab"
100+
b"HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\ncd"
101+
b"HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nef"
102+
)
103+
for i in range(len(messages)):
104+
proto.data_received(messages[i : i + 1])
105+
106+
expected = [b"ab", b"cd", b"ef"]
107+
for payload in expected:
108+
response = ClientResponse(
109+
"get",
110+
URL("http://def-cl-resp.org"),
111+
writer=mock.Mock(),
112+
continue100=None,
113+
timer=TimerNoop(),
114+
request_info=mock.Mock(),
115+
traces=[],
116+
loop=loop,
117+
session=mock.Mock(),
118+
)
119+
await response.start(conn)
120+
await response.read() == payload
121+
122+
123+
async def test_unexpected_exception_during_data_received(
124+
loop: asyncio.AbstractEventLoop,
125+
) -> None:
126+
proto = ResponseHandler(loop=loop)
127+
128+
class PatchableHttpResponseParser(http.HttpResponseParser):
129+
"""Subclass of HttpResponseParser to make it patchable."""
130+
131+
with mock.patch(
132+
"aiohttp.client_proto.HttpResponseParser", PatchableHttpResponseParser
133+
):
134+
proto.connection_made(mock.Mock())
135+
conn = mock.Mock(protocol=proto)
136+
proto.set_response_params(read_until_eof=True)
137+
proto.data_received(b"HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nab")
138+
response = ClientResponse(
139+
"get",
140+
URL("http://def-cl-resp.org"),
141+
writer=mock.Mock(),
142+
continue100=None,
143+
timer=TimerNoop(),
144+
request_info=mock.Mock(),
145+
traces=[],
146+
loop=loop,
147+
session=mock.Mock(),
148+
)
149+
await response.start(conn)
150+
await response.read() == b"ab"
151+
with mock.patch.object(proto._parser, "feed_data", side_effect=ValueError):
152+
proto.data_received(b"HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\ncd")
153+
154+
assert isinstance(proto.exception(), http.HttpProcessingError)
155+
156+
157+
async def test_client_protocol_readuntil_eof(loop: asyncio.AbstractEventLoop) -> None:
76158
proto = ResponseHandler(loop=loop)
77159
transport = mock.Mock()
78160
proto.connection_made(transport)

0 commit comments

Comments
 (0)