Skip to content

Commit bc15db6

Browse files
authored
[PR #9852/249855a backport][3.10] Fix system routes polluting the middleware cache (#9855)
1 parent 158bf30 commit bc15db6

File tree

3 files changed

+39
-5
lines changed

3 files changed

+39
-5
lines changed

CHANGES/9852.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixed system routes polluting the middleware cache -- by :user:`bdraco`.

aiohttp/web_app.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
MaskDomain,
5555
MatchedSubAppResource,
5656
PrefixedSubAppResource,
57+
SystemRoute,
5758
UrlDispatcher,
5859
)
5960

@@ -79,7 +80,6 @@
7980
_Resource = TypeVar("_Resource", bound=AbstractResource)
8081

8182

82-
@lru_cache(None)
8383
def _build_middlewares(
8484
handler: Handler, apps: Tuple["Application", ...]
8585
) -> Callable[[Request], Awaitable[StreamResponse]]:
@@ -90,6 +90,9 @@ def _build_middlewares(
9090
return handler
9191

9292

93+
_cached_build_middleware = lru_cache(maxsize=1024)(_build_middlewares)
94+
95+
9396
class Application(MutableMapping[Union[str, AppKey[Any]], Any]):
9497
ATTRS = frozenset(
9598
[
@@ -544,8 +547,13 @@ async def _handle(self, request: Request) -> StreamResponse:
544547
handler = match_info.handler
545548

546549
if self._run_middlewares:
547-
if not self._has_legacy_middlewares:
548-
handler = _build_middlewares(handler, match_info.apps)
550+
# If its a SystemRoute, don't cache building the middlewares since
551+
# they are constructed for every MatchInfoError as a new handler
552+
# is made each time.
553+
if not self._has_legacy_middlewares and not isinstance(
554+
match_info.route, SystemRoute
555+
):
556+
handler = _cached_build_middleware(handler, match_info.apps)
549557
else:
550558
for app in match_info.apps[::-1]:
551559
for m, new_style in app._middlewares_handlers: # type: ignore[union-attr]

tests/test_web_middleware.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import re
2-
from typing import Any
2+
from typing import Any, NoReturn
33

44
import pytest
55
from yarl import URL
66

7-
from aiohttp import web
7+
from aiohttp import web, web_app
8+
from aiohttp.pytest_plugin import AiohttpClient
89
from aiohttp.typedefs import Handler
910

1011

@@ -520,3 +521,27 @@ async def call(self, request, handler: Handler):
520521
assert 201 == resp.status
521522
txt = await resp.text()
522523
assert "OK[new style middleware]" == txt
524+
525+
526+
async def test_middleware_does_not_leak(aiohttp_client: AiohttpClient) -> None:
527+
async def any_handler(request: web.Request) -> NoReturn:
528+
assert False
529+
530+
class Middleware:
531+
@web.middleware
532+
async def call(
533+
self, request: web.Request, handler: Handler
534+
) -> web.StreamResponse:
535+
return await handler(request)
536+
537+
app = web.Application()
538+
app.router.add_route("POST", "/any", any_handler)
539+
app.middlewares.append(Middleware().call)
540+
541+
client = await aiohttp_client(app)
542+
543+
web_app._cached_build_middleware.cache_clear()
544+
for _ in range(10):
545+
resp = await client.get("/any")
546+
assert resp.status == 405
547+
assert web_app._cached_build_middleware.cache_info().currsize < 10

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy