Skip to content

Commit

Permalink
Index resources in the UrlDispatcher to avoid linear search on hit
Browse files Browse the repository at this point in the history
fixes #7828
  • Loading branch information
bdraco authored and Dreamsorcerer committed Nov 27, 2023
1 parent 40a5197 commit 9a94e36
Showing 1 changed file with 22 additions and 0 deletions.
22 changes: 22 additions & 0 deletions aiohttp/web_urldispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -979,8 +979,24 @@ def __init__(self) -> None:
super().__init__()
self._resources: List[AbstractResource] = []
self._named_resources: Dict[str, AbstractResource] = {}
self._resource_index: dict[str, list[AbstractResource]] = {}

async def resolve(self, request: Request) -> UrlMappingMatchInfo:
url_parts = request.rel_url.raw_parts
resource_index = self._resource_index

# Walk the url parts looking for candidates
for i in range(len(url_parts), 0, -1):
url_part = "/" + "/".join(url_parts[1:i])
if (resource_candidates := resource_index.get(url_part)) is not None:
for candidate in resource_candidates:
if (
match_dict := (await candidate.resolve(request))[0]
) is not None:
return match_dict

# We didn't find any candidates, so will fallback to a linear search

method = request.method
allowed_methods: Set[str] = set()

Expand Down Expand Up @@ -1049,6 +1065,12 @@ def register_resource(self, resource: AbstractResource) -> None:
)
self._named_resources[name] = resource
self._resources.append(resource)
canonical = resource.canonical
if "{" in canonical: # strip at the first { to allow for variables
canonical = canonical.split("{")[0].rstrip("/")
# There may be multiple resources for a canonical path
# so we use a list to avoid falling back to a full linear search
self._resource_index.setdefault(canonical, []).append(resource)

def add_resource(self, path: str, *, name: Optional[str] = None) -> Resource:
if path and not path.startswith("/"):
Expand Down

0 comments on commit 9a94e36

Please sign in to comment.