Skip to content

Commit

Permalink
Support setting response header size limits (aio-libs#6559)
Browse files Browse the repository at this point in the history
<!-- Thank you for your contribution! -->

Allow the user to set the response header limits. The properties
max_line_size, max_headers and max_field_size can be set on
ClientSession creation. Default values are: `max_line_size=8190`
`max_headers=32768` and `max_field_size=8190`.
Thus the user can set and - when needed - receive bigger response
headers.

No

- [x] I think the code is well written
- [x] Unit tests for the changes exist
- [ ] Documentation reflects the changes
- [ ] If you provide code modification, please add yourself to
`CONTRIBUTORS.txt`
  * The format is &lt;Name&gt; &lt;Surname&gt;.
  * Please keep alphabetical order, the file is sorted by names.
- [x] Add a new news fragment into the `CHANGES` folder
  * name it `<issue_id>.<type>` for example (588.bugfix)
* if you don't have an `issue_id` change it to the pr id after creating
the pr
  * ensure type is one of the following:
    * `.feature`: Signifying a new feature.
    * `.bugfix`: Signifying a bug fix.
    * `.doc`: Signifying a documentation improvement.
    * `.removal`: Signifying a deprecation or removal of public API.
* `.misc`: A ticket has been closed, but it is not of interest to users.
* Make sure to use full sentences with correct case and punctuation, for
example: "Fix issue with non-ascii contents in doctest text files."

---------

Co-authored-by: Josef Prenner <josef@cloudomation.com>
Co-authored-by: Sam Bull <aa6bs0@sambull.org>
(cherry picked from commit ecd9c72)
  • Loading branch information
jorop authored and rohansahai committed Apr 29, 2023
1 parent 2b3db3a commit 1a1aa48
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 25 deletions.
1 change: 1 addition & 0 deletions CHANGES/2304.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Support setting response header parameters max_line_size and max_field_size.
67 changes: 42 additions & 25 deletions aiohttp/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,31 +165,30 @@ class ClientTimeout:
class ClientSession:
"""First-class interface for making HTTP requests."""

ATTRS = frozenset(
[
"_base_url",
"_source_traceback",
"_connector",
"requote_redirect_url",
"_loop",
"_cookie_jar",
"_connector_owner",
"_default_auth",
"_version",
"_json_serialize",
"_requote_redirect_url",
"_timeout",
"_raise_for_status",
"_auto_decompress",
"_trust_env",
"_default_headers",
"_skip_auto_headers",
"_request_class",
"_response_class",
"_ws_response_class",
"_trace_configs",
"_read_bufsize",
]
__slots__ = (
"_base_url",
"_source_traceback",
"_connector",
"_loop",
"_cookie_jar",
"_connector_owner",
"_default_auth",
"_version",
"_json_serialize",
"_requote_redirect_url",
"_timeout",
"_raise_for_status",
"_auto_decompress",
"_trust_env",
"_default_headers",
"_skip_auto_headers",
"_request_class",
"_response_class",
"_ws_response_class",
"_trace_configs",
"_read_bufsize",
"_max_line_size",
"_max_field_size",
)

_source_traceback: Optional[traceback.StackSummary] = None
Expand Down Expand Up @@ -223,6 +222,8 @@ def __init__(
requote_redirect_url: bool = True,
trace_configs: Optional[List[TraceConfig]] = None,
read_bufsize: int = 2**16,
max_line_size: int = 8190,
max_field_size: int = 8190,
) -> None:
if loop is None:
if connector is not None:
Expand Down Expand Up @@ -296,6 +297,8 @@ def __init__(
self._trust_env = trust_env
self._requote_redirect_url = requote_redirect_url
self._read_bufsize = read_bufsize
self._max_line_size = max_line_size
self._max_field_size = max_field_size

# Convert to list of tuples
if headers:
Expand Down Expand Up @@ -393,6 +396,8 @@ async def _request(
trace_request_ctx: Optional[SimpleNamespace] = None,
read_bufsize: Optional[int] = None,
auto_decompress: Optional[bool] = None,
max_line_size: Optional[int] = None,
max_field_size: Optional[int] = None,
) -> ClientResponse:

# NOTE: timeout clamps existing connect and read timeouts. We cannot
Expand Down Expand Up @@ -458,6 +463,12 @@ async def _request(
if auto_decompress is None:
auto_decompress = self._auto_decompress

if max_line_size is None:
max_line_size = self._max_line_size

if max_field_size is None:
max_field_size = self._max_field_size

traces = [
Trace(
self,
Expand Down Expand Up @@ -564,6 +575,8 @@ async def _request(
read_timeout=real_timeout.sock_read,
read_bufsize=read_bufsize,
timeout_ceil_threshold=self._connector._timeout_ceil_threshold,
max_line_size=max_line_size,
max_field_size=max_field_size,
)

try:
Expand Down Expand Up @@ -1247,6 +1260,8 @@ def request(
connector: Optional[BaseConnector] = None,
read_bufsize: Optional[int] = None,
loop: Optional[asyncio.AbstractEventLoop] = None,
max_line_size: int = 8190,
max_field_size: int = 8190,
) -> _SessionRequestContextManager:
"""Constructs and sends a request.
Expand Down Expand Up @@ -1318,6 +1333,8 @@ def request(
proxy=proxy,
proxy_auth=proxy_auth,
read_bufsize=read_bufsize,
max_line_size=max_line_size,
max_field_size=max_field_size,
),
session,
)
4 changes: 4 additions & 0 deletions aiohttp/client_proto.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@ def set_response_params(
read_timeout: Optional[float] = None,
read_bufsize: int = 2**16,
timeout_ceil_threshold: float = 5,
max_line_size: int = 8190,
max_field_size: int = 8190,
) -> None:
self._skip_payload = skip_payload

Expand All @@ -162,6 +164,8 @@ def set_response_params(
response_with_body=not skip_payload,
read_until_eof=read_until_eof,
auto_decompress=auto_decompress,
max_line_size=max_line_size,
max_field_size=max_field_size,
)

if self._tail:
Expand Down
102 changes: 102 additions & 0 deletions tests/test_client_functional.py
Original file line number Diff line number Diff line change
Expand Up @@ -3077,3 +3077,105 @@ async def handler(request):
assert resp.status == 200
assert await resp.text() == "ok"
assert resp.headers["Content-Type"] == "text/plain; charset=utf-8"


async def test_max_field_size_session_default(aiohttp_client: Any) -> None:
async def handler(request):
return web.Response(headers={"Custom": "x" * 8190})

app = web.Application()
app.add_routes([web.get("/", handler)])

client = await aiohttp_client(app)

async with await client.get("/") as resp:
assert resp.headers["Custom"] == "x" * 8190


async def test_max_field_size_session_default_fail(aiohttp_client: Any) -> None:
async def handler(request):
return web.Response(headers={"Custom": "x" * 8191})

app = web.Application()
app.add_routes([web.get("/", handler)])

client = await aiohttp_client(app)
with pytest.raises(aiohttp.ClientResponseError):
await client.get("/")


async def test_max_field_size_session_explicit(aiohttp_client: Any) -> None:
async def handler(request):
return web.Response(headers={"Custom": "x" * 8191})

app = web.Application()
app.add_routes([web.get("/", handler)])

client = await aiohttp_client(app, max_field_size=8191)

async with await client.get("/") as resp:
assert resp.headers["Custom"] == "x" * 8191


async def test_max_field_size_request_explicit(aiohttp_client: Any) -> None:
async def handler(request):
return web.Response(headers={"Custom": "x" * 8191})

app = web.Application()
app.add_routes([web.get("/", handler)])

client = await aiohttp_client(app)

async with await client.get("/", max_field_size=8191) as resp:
assert resp.headers["Custom"] == "x" * 8191


async def test_max_line_size_session_default(aiohttp_client: Any) -> None:
async def handler(request):
return web.Response(status=200, reason="x" * 8190)

app = web.Application()
app.add_routes([web.get("/", handler)])

client = await aiohttp_client(app)

async with await client.get("/") as resp:
assert resp.reason == "x" * 8190


async def test_max_line_size_session_default_fail(aiohttp_client: Any) -> None:
async def handler(request):
return web.Response(status=200, reason="x" * 8192)

app = web.Application()
app.add_routes([web.get("/", handler)])

client = await aiohttp_client(app)
with pytest.raises(aiohttp.ClientResponseError):
await client.get("/")


async def test_max_line_size_session_explicit(aiohttp_client: Any) -> None:
async def handler(request):
return web.Response(status=200, reason="x" * 8191)

app = web.Application()
app.add_routes([web.get("/", handler)])

client = await aiohttp_client(app, max_line_size=8191)

async with await client.get("/") as resp:
assert resp.reason == "x" * 8191


async def test_max_line_size_request_explicit(aiohttp_client: Any) -> None:
async def handler(request):
return web.Response(status=200, reason="x" * 8191)

app = web.Application()
app.add_routes([web.get("/", handler)])

client = await aiohttp_client(app)

async with await client.get("/", max_line_size=8191) as resp:
assert resp.reason == "x" * 8191

0 comments on commit 1a1aa48

Please sign in to comment.