diff --git a/homeassistant/components/http/__init__.py b/homeassistant/components/http/__init__.py index c8fa05b2730..f559b09a1ff 100644 --- a/homeassistant/components/http/__init__.py +++ b/homeassistant/components/http/__init__.py @@ -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)