Avoid http route linear search fallback when there are multiple paths (#95776)

This commit is contained in:
J. Nick Koston 2023-07-07 07:36:38 -10:00 committed by GitHub
parent 17440c9608
commit f1db497efe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -582,32 +582,37 @@ class FastUrlDispatcher(UrlDispatcher):
def __init__(self) -> None:
"""Initialize the dispatcher."""
super().__init__()
self._resource_index: dict[str, AbstractResource] = {}
self._resource_index: dict[str, list[AbstractResource]] = {}
def register_resource(self, resource: AbstractResource) -> None:
"""Register a resource."""
super().register_resource(resource)
canonical = resource.canonical
if "{" in canonical: # strip at the first { to allow for variables
canonical = canonical.split("{")[0]
canonical = canonical.rstrip("/")
self._resource_index[canonical] = resource
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)
async def resolve(self, request: web.Request) -> UrlMappingMatchInfo:
"""Resolve a request."""
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), 1, -1):
url_part = "/" + "/".join(url_parts[1:i])
if (resource_candidate := resource_index.get(url_part)) is not None and (
match_dict := (await resource_candidate.resolve(request))[0]
) is not None:
return match_dict
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
# Next try the index view if we don't have a match
if (index_view_candidate := resource_index.get("/")) is not None and (
match_dict := (await index_view_candidate.resolve(request))[0]
) is not None:
return match_dict
if (index_view_candidates := resource_index.get("/")) is not None:
for candidate in index_view_candidates:
if (match_dict := (await candidate.resolve(request))[0]) is not None:
return match_dict
# Finally, fallback to the linear search
return await super().resolve(request)