Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES/10552.misc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Improved performance of parsing content types by adding a cache in the same manner currently done with mime types -- by :user:`bdraco`.
24 changes: 19 additions & 5 deletions aiohttp/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from http.cookies import SimpleCookie
from math import ceil
from pathlib import Path
from types import TracebackType
from types import MappingProxyType, TracebackType
from typing import (
TYPE_CHECKING,
Any,
Expand Down Expand Up @@ -367,6 +367,20 @@ def parse_mimetype(mimetype: str) -> MimeType:
)


@functools.lru_cache(maxsize=56)
def parse_content_type(raw: str) -> Tuple[str, MappingProxyType[str, str]]:
"""Parse Content-Type header.

Returns a tuple of the parsed content type and a
MappingProxyType of parameters.
"""
msg = HeaderParser().parsestr(f"Content-Type: {raw}")
content_type = msg.get_content_type()
params = msg.get_params(())
content_dict = dict(params[1:]) # First element is content type again
return content_type, MappingProxyType(content_dict)


def guess_filename(obj: Any, default: Optional[str] = None) -> Optional[str]:
name = getattr(obj, "name", None)
if name and isinstance(name, str) and name[0] != "<" and name[-1] != ">":
Expand Down Expand Up @@ -733,10 +747,10 @@ def _parse_content_type(self, raw: Optional[str]) -> None:
self._content_type = "application/octet-stream"
self._content_dict = {}
else:
msg = HeaderParser().parsestr("Content-Type: " + raw)
self._content_type = msg.get_content_type()
params = msg.get_params(())
self._content_dict = dict(params[1:]) # First element is content type again
content_type, content_mapping_proxy = parse_content_type(raw)
self._content_type = content_type
# _content_dict needs to be mutable so we can update it
self._content_dict = content_mapping_proxy.copy()

@property
def content_type(self) -> str:
Expand Down
27 changes: 27 additions & 0 deletions tests/test_benchmarks_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -319,3 +319,30 @@ async def run_client_benchmark() -> None:
@benchmark
def _run() -> None:
loop.run_until_complete(run_client_benchmark())


def test_one_hundred_json_post_requests(
loop: asyncio.AbstractEventLoop,
aiohttp_client: AiohttpClient,
benchmark: BenchmarkFixture,
) -> None:
"""Benchmark 100 JSON POST requests that check the content-type."""
message_count = 100

async def handler(request: web.Request) -> web.Response:
_ = request.content_type
_ = request.charset
return web.Response()

app = web.Application()
app.router.add_route("POST", "/", handler)

async def run_client_benchmark() -> None:
client = await aiohttp_client(app)
for _ in range(message_count):
await client.post("/", json={"key": "value"})
await client.close()

@benchmark
def _run() -> None:
loop.run_until_complete(run_client_benchmark())
Loading