Skip to content

Commit

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

## What do these changes do?

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.

## Are there changes in behavior for the user?

No

## Related issue number

#2304

## Checklist

- [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>
  • Loading branch information
3 people committed Apr 25, 2023
1 parent 315ae90 commit ecd9c72
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGES/2304.feature
@@ -0,0 +1 @@
Support setting response header parameters max_line_size and max_field_size.
20 changes: 20 additions & 0 deletions aiohttp/client.py
Expand Up @@ -186,6 +186,8 @@ class ClientSession:
"_ws_response_class",
"_trace_configs",
"_read_bufsize",
"_max_line_size",
"_max_field_size",
)

def __init__(
Expand Down Expand Up @@ -213,6 +215,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 base_url is None or isinstance(base_url, URL):
self._base_url: Optional[URL] = base_url
Expand Down Expand Up @@ -261,6 +265,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 @@ -342,6 +348,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
# set the default to None because we need to detect if the user wants
Expand Down Expand Up @@ -404,6 +412,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 @@ -510,6 +524,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 @@ -1186,6 +1202,8 @@ def request(
version: HttpVersion = http.HttpVersion11,
connector: Optional[BaseConnector] = None,
read_bufsize: Optional[int] = None,
max_line_size: int = 8190,
max_field_size: int = 8190,
) -> _SessionRequestContextManager:
"""Constructs and sends a request.
Expand Down Expand Up @@ -1256,6 +1274,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
Expand Up @@ -153,6 +153,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 @@ -169,6 +171,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
Expand Up @@ -3065,3 +3065,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 ecd9c72

Please sign in to comment.