Skip to content

Commit fd09b48

Browse files
authored
[PR #9722/fbf555c backport][3.11] Simplify header and method lookups (#9724)
1 parent 740112b commit fd09b48

File tree

9 files changed

+38
-39
lines changed

9 files changed

+38
-39
lines changed

CHANGES/9722.misc.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Replace internal helper methods ``method_must_be_empty_body`` and ``status_code_must_be_empty_body`` with simple `set` lookups -- by :user:`bdraco`.

aiohttp/client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,10 @@
9292
from .helpers import (
9393
_SENTINEL,
9494
DEBUG,
95+
EMPTY_BODY_METHODS,
9596
BasicAuth,
9697
TimeoutHandle,
9798
get_env_proxy_for_url,
98-
method_must_be_empty_body,
9999
sentinel,
100100
strip_auth_from_url,
101101
)
@@ -706,7 +706,7 @@ async def _request(
706706
assert conn.protocol is not None
707707
conn.protocol.set_response_params(
708708
timer=timer,
709-
skip_payload=method_must_be_empty_body(method),
709+
skip_payload=method in EMPTY_BODY_METHODS,
710710
read_until_eof=read_until_eof,
711711
auto_decompress=auto_decompress,
712712
read_timeout=real_timeout.sock_read,

aiohttp/client_proto.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
)
1212
from .helpers import (
1313
_EXC_SENTINEL,
14+
EMPTY_BODY_STATUS_CODES,
1415
BaseTimerContext,
1516
set_exception,
16-
status_code_must_be_empty_body,
1717
)
1818
from .http import HttpResponseParser, RawResponseMessage
1919
from .http_exceptions import HttpProcessingError
@@ -284,9 +284,7 @@ def data_received(self, data: bytes) -> None:
284284

285285
self._payload = payload
286286

287-
if self._skip_payload or status_code_must_be_empty_body(
288-
message.code
289-
):
287+
if self._skip_payload or message.code in EMPTY_BODY_STATUS_CODES:
290288
self.feed_data((message, EMPTY_PAYLOAD), 0)
291289
else:
292290
self.feed_data((message, payload), 0)

aiohttp/client_reqrep.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import contextlib
44
import functools
55
import io
6-
import itertools
76
import re
87
import sys
98
import traceback
@@ -239,9 +238,6 @@ class ClientRequest:
239238
}
240239
POST_METHODS = {hdrs.METH_PATCH, hdrs.METH_POST, hdrs.METH_PUT}
241240
ALL_METHODS = GET_METHODS.union(POST_METHODS).union({hdrs.METH_DELETE})
242-
_HOST_STRINGS = frozenset(
243-
map("".join, itertools.product(*zip("host".upper(), "host".lower())))
244-
)
245241

246242
DEFAULT_HEADERS = {
247243
hdrs.ACCEPT: "*/*",
@@ -445,7 +441,7 @@ def update_headers(self, headers: Optional[LooseHeaders]) -> None:
445441

446442
for key, value in headers: # type: ignore[misc]
447443
# A special case for Host header
448-
if key in self._HOST_STRINGS:
444+
if key in hdrs.HOST_ALL:
449445
self.headers[key] = value
450446
else:
451447
self.headers.add(key, value)

aiohttp/hdrs.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
# After changing the file content call ./tools/gen.py
44
# to regenerate the headers parser
5+
import itertools
56
from typing import Final, Set
67

78
from multidict import istr
@@ -106,3 +107,15 @@
106107
X_FORWARDED_FOR: Final[istr] = istr("X-Forwarded-For")
107108
X_FORWARDED_HOST: Final[istr] = istr("X-Forwarded-Host")
108109
X_FORWARDED_PROTO: Final[istr] = istr("X-Forwarded-Proto")
110+
111+
# These are the upper/lower case variants of the headers/methods
112+
# Example: {'hOst', 'host', 'HoST', 'HOSt', 'hOsT', 'HosT', 'hoSt', ...}
113+
METH_HEAD_ALL: Final = frozenset(
114+
map("".join, itertools.product(*zip(METH_HEAD.upper(), METH_HEAD.lower())))
115+
)
116+
METH_CONNECT_ALL: Final = frozenset(
117+
map("".join, itertools.product(*zip(METH_CONNECT.upper(), METH_CONNECT.lower())))
118+
)
119+
HOST_ALL: Final = frozenset(
120+
map("".join, itertools.product(*zip(HOST.upper(), HOST.lower())))
121+
)

aiohttp/helpers.py

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,12 @@
7575

7676
NO_EXTENSIONS = bool(os.environ.get("AIOHTTP_NO_EXTENSIONS"))
7777

78+
# https://datatracker.ietf.org/doc/html/rfc9112#section-6.3-2.1
79+
EMPTY_BODY_STATUS_CODES = frozenset((204, 304, *range(100, 200)))
80+
# https://datatracker.ietf.org/doc/html/rfc9112#section-6.3-2.1
81+
# https://datatracker.ietf.org/doc/html/rfc9112#section-6.3-2.2
82+
EMPTY_BODY_METHODS = hdrs.METH_HEAD_ALL
83+
7884
DEBUG = sys.flags.dev_mode or (
7985
not sys.flags.ignore_environment and bool(os.environ.get("PYTHONASYNCIODEBUG"))
8086
)
@@ -919,34 +925,19 @@ def parse_http_date(date_str: Optional[str]) -> Optional[datetime.datetime]:
919925
def must_be_empty_body(method: str, code: int) -> bool:
920926
"""Check if a request must return an empty body."""
921927
return (
922-
status_code_must_be_empty_body(code)
923-
or method_must_be_empty_body(method)
924-
or (200 <= code < 300 and method.upper() == hdrs.METH_CONNECT)
928+
code in EMPTY_BODY_STATUS_CODES
929+
or method in EMPTY_BODY_METHODS
930+
or (200 <= code < 300 and method in hdrs.METH_CONNECT_ALL)
925931
)
926932

927933

928-
def method_must_be_empty_body(method: str) -> bool:
929-
"""Check if a method must return an empty body."""
930-
# https://datatracker.ietf.org/doc/html/rfc9112#section-6.3-2.1
931-
# https://datatracker.ietf.org/doc/html/rfc9112#section-6.3-2.2
932-
return method.upper() == hdrs.METH_HEAD
933-
934-
935-
def status_code_must_be_empty_body(code: int) -> bool:
936-
"""Check if a status code must return an empty body."""
937-
# https://datatracker.ietf.org/doc/html/rfc9112#section-6.3-2.1
938-
return code in {204, 304} or 100 <= code < 200
939-
940-
941934
def should_remove_content_length(method: str, code: int) -> bool:
942935
"""Check if a Content-Length header should be removed.
943936
944937
This should always be a subset of must_be_empty_body
945938
"""
946939
# https://www.rfc-editor.org/rfc/rfc9110.html#section-8.6-8
947940
# https://www.rfc-editor.org/rfc/rfc9110.html#section-15.4.5-4
948-
return (
949-
code in {204, 304}
950-
or 100 <= code < 200
951-
or (200 <= code < 300 and method.upper() == hdrs.METH_CONNECT)
941+
return code in EMPTY_BODY_STATUS_CODES or (
942+
200 <= code < 300 and method in hdrs.METH_CONNECT_ALL
952943
)

aiohttp/http_parser.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,11 @@
3030
from .helpers import (
3131
_EXC_SENTINEL,
3232
DEBUG,
33+
EMPTY_BODY_METHODS,
34+
EMPTY_BODY_STATUS_CODES,
3335
NO_EXTENSIONS,
3436
BaseTimerContext,
35-
method_must_be_empty_body,
3637
set_exception,
37-
status_code_must_be_empty_body,
3838
)
3939
from .http_exceptions import (
4040
BadHttpMessage,
@@ -376,8 +376,8 @@ def get_content_length() -> Optional[int]:
376376

377377
assert self.protocol is not None
378378
# calculate payload
379-
empty_body = status_code_must_be_empty_body(code) or bool(
380-
method and method_must_be_empty_body(method)
379+
empty_body = code in EMPTY_BODY_STATUS_CODES or bool(
380+
method and method in EMPTY_BODY_METHODS
381381
)
382382
if not empty_body and (
383383
((length is not None and length > 0) or msg.chunked)

aiohttp/web_response.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -776,7 +776,7 @@ async def _start(self, request: "BaseRequest") -> AbstractStreamWriter:
776776
body_len = len(self._body) if self._body else "0"
777777
# https://www.rfc-editor.org/rfc/rfc9110.html#section-8.6-7
778778
if body_len != "0" or (
779-
self.status != 304 and request.method.upper() != hdrs.METH_HEAD
779+
self.status != 304 and request.method not in hdrs.METH_HEAD_ALL
780780
):
781781
self._headers[hdrs.CONTENT_LENGTH] = str(body_len)
782782

tests/test_helpers.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
from aiohttp import helpers
1717
from aiohttp.helpers import (
18-
method_must_be_empty_body,
18+
EMPTY_BODY_METHODS,
1919
must_be_empty_body,
2020
parse_http_date,
2121
should_remove_content_length,
@@ -897,9 +897,9 @@ def test_read_basicauth_from_empty_netrc():
897897

898898
def test_method_must_be_empty_body():
899899
"""Test that HEAD is the only method that unequivocally must have an empty body."""
900-
assert method_must_be_empty_body("HEAD") is True
900+
assert "HEAD" in EMPTY_BODY_METHODS
901901
# CONNECT is only empty on a successful response
902-
assert method_must_be_empty_body("CONNECT") is False
902+
assert "CONNECT" not in EMPTY_BODY_METHODS
903903

904904

905905
def test_should_remove_content_length_is_subset_of_must_be_empty_body():

0 commit comments

Comments
 (0)