Skip to content

Commit

Permalink
aio-libs#2304 add max_line_site, max_headers and max_field_size argum…
Browse files Browse the repository at this point in the history
…ents to ClientSession
  • Loading branch information
Stefan committed Apr 27, 2022
1 parent 0215cdd commit 188852b
Show file tree
Hide file tree
Showing 5 changed files with 227 additions and 2 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, max_headers and max_field_size.
30 changes: 30 additions & 0 deletions aiohttp/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,9 @@ class ClientSession:
"_ws_response_class",
"_trace_configs",
"_read_bufsize",
"_max_line_size",
"_max_headers",
"_max_field_size",
)

def __init__(
Expand Down Expand Up @@ -218,6 +221,9 @@ def __init__(
requote_redirect_url: bool = True,
trace_configs: Optional[List[TraceConfig]] = None,
read_bufsize: int = 2**16,
max_line_size: int = 8190,
max_headers: int = 32768,
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 @@ -266,6 +272,9 @@ 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_headers = max_headers
self._max_field_size = max_field_size

# Convert to list of tuples
if headers:
Expand Down Expand Up @@ -351,6 +360,9 @@ async def _request(
proxy_headers: Optional[LooseHeaders] = None,
trace_request_ctx: Optional[SimpleNamespace] = None,
read_bufsize: Optional[int] = None,
max_line_size: Optional[int] = None,
max_headers: Optional[int] = None,
max_field_size: Optional[int] = None,
) -> ClientResponse:

# NOTE: timeout clamps existing connect and read timeouts. We cannot
Expand Down Expand Up @@ -411,6 +423,15 @@ async def _request(
if read_bufsize is None:
read_bufsize = self._read_bufsize

if max_line_size is None:
max_line_size = self._max_line_size

if max_headers is None:
max_headers = self._max_headers

if max_field_size is None:
max_field_size = self._max_field_size

traces = [
Trace(
self,
Expand Down Expand Up @@ -516,6 +537,9 @@ 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_headers=max_headers,
max_field_size=max_field_size,
)

try:
Expand Down Expand Up @@ -1193,6 +1217,9 @@ def request(
version: HttpVersion = http.HttpVersion11,
connector: Optional[BaseConnector] = None,
read_bufsize: Optional[int] = None,
max_line_size: int = 8190,
max_headers: int = 32768,
max_field_size: int = 8190,
) -> _SessionRequestContextManager:
"""Constructs and sends a request.
Expand Down Expand Up @@ -1263,6 +1290,9 @@ def request(
proxy=proxy,
proxy_auth=proxy_auth,
read_bufsize=read_bufsize,
max_line_size=max_line_size,
max_headers=max_headers,
max_field_size=max_field_size,
),
session,
)
6 changes: 6 additions & 0 deletions aiohttp/client_proto.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,9 @@ 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_headers: int = 32768,
max_field_size: int = 8190,
) -> None:
self._skip_payload = skip_payload

Expand All @@ -170,6 +173,9 @@ 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_headers=max_headers,
max_field_size=max_field_size,
)

if self._tail:
Expand Down
22 changes: 20 additions & 2 deletions docs/client_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,10 @@ The client session supports the context manager protocol for self closing.
read_bufsize=2**16, \
requote_redirect_url=False, \
trust_env=False, \
trace_configs=None)
trace_configs=None, \
max_line_size=8190, \
max_headers=32768, \
max_field_size=8190)

The class for creating client sessions and making requests.

Expand Down Expand Up @@ -201,6 +204,12 @@ The client session supports the context manager protocol for self closing.
disabling. See :ref:`aiohttp-client-tracing-reference` for
more information.

:param max_line_size: The maximum length allowed for the HTTP response reason field.

:param max_headers: The maximum number of response headers allowed.

:param max_field_size: The maximum length allowed for response header values.

.. attribute:: closed

``True`` if the session has been closed, ``False`` otherwise.
Expand Down Expand Up @@ -338,7 +347,10 @@ The client session supports the context manager protocol for self closing.
proxy=None, proxy_auth=None,\
timeout=sentinel, ssl=None, \
verify_ssl=None, fingerprint=None, \
ssl_context=None, proxy_headers=None)
ssl_context=None, proxy_headers=None, \
max_line_size=8190, \
max_headers=32768, \
max_field_size=8190)
:async-with:
:coroutine:
:noindex:
Expand Down Expand Up @@ -510,6 +522,12 @@ The client session supports the context manager protocol for self closing.

.. versionadded:: 3.0

:param max_line_size: The maximum length allowed for the HTTP response reason field.

:param max_headers: The maximum number of response headers allowed.

:param max_field_size: The maximum length allowed for response header values.

:return ClientResponse: a :class:`client response <ClientResponse>`
object.

Expand Down
170 changes: 170 additions & 0 deletions tests/test_client_functional.py
Original file line number Diff line number Diff line change
Expand Up @@ -3028,3 +3028,173 @@ 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


async def test_max_headers_session_default(aiohttp_client: Any) -> None:
async def handler(request):
# generate 32764 headers:
# 32768 (max_headers default) minus 4 headers which are set implicitly
# 'Content-Length', 'Content-Type', 'Date' and 'Server'
headers = MultiDict()
for x in range(32764):
headers.add(f"x-header-{x}", str(x))
return web.Response(headers=headers)

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

client = await aiohttp_client(app)

async with await client.get("/") as resp:
assert len(resp.headers) == 32768


@pytest.mark.xfail
async def test_max_headers_session_default_fail(aiohttp_client: Any) -> None:
async def handler(request):
headers = MultiDict()
for x in range(32769):
headers.add(f"x-header-{x}", str(x))
return web.Response(headers=headers)

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_headers_session_explicit(aiohttp_client: Any) -> None:
async def handler(request):
headers = MultiDict()
for x in range(32765):
headers.add(f"x-header-{x}", str(x))
return web.Response(headers=headers)

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

client = await aiohttp_client(app, max_headers=32769)

async with await client.get("/") as resp:
assert len(resp.headers) == 32769


async def test_max_headers_request_explicit(aiohttp_client: Any) -> None:
async def handler(request):
headers = MultiDict()
for x in range(32765):
headers.add(f"x-header-{x}", str(x))
return web.Response(headers=headers)

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

client = await aiohttp_client(app)

async with await client.get("/", max_headers=32769) as resp:
assert len(resp.headers) == 32769

0 comments on commit 188852b

Please sign in to comment.