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
2 changes: 2 additions & 0 deletions CHANGES/11673.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fixed routing to a sub-application added via ``.add_domain()`` not working
if the same path exists on the parent app. -- by :user:`Dreamsorcerer`.
30 changes: 15 additions & 15 deletions aiohttp/web_urldispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -977,6 +977,21 @@ async def resolve(self, request: Request) -> UrlMappingMatchInfo:
resource_index = self._resource_index
allowed_methods: set[str] = set()

# MatchedSubAppResource is primarily used to match on domain names
# (though custom rules could match on other things). This means that
# the traversal algorithm below can't be applied, and that we likely
# need to check these first so a sub app that defines the same path
# as a parent app will get priority if there's a domain match.
#
# For most cases we do not expect there to be many of these since
# currently they are only added by `.add_domain()`.
for resource in self._matched_sub_app_resources:
match_dict, allowed = await resource.resolve(request)
if match_dict is not None:
return match_dict
else:
allowed_methods |= allowed

# Walk the url parts looking for candidates. We walk the url backwards
# to ensure the most explicit match is found first. If there are multiple
# candidates for a given url part because there are multiple resources
Expand All @@ -994,21 +1009,6 @@ async def resolve(self, request: Request) -> UrlMappingMatchInfo:
break
url_part = url_part.rpartition("/")[0] or "/"

#
# We didn't find any candidates, so we'll try the matched sub-app
# resources which we have to walk in a linear fashion because they
# have regex/wildcard match rules and we cannot index them.
#
# For most cases we do not expect there to be many of these since
# currently they are only added by `add_domain`
#
for resource in self._matched_sub_app_resources:
match_dict, allowed = await resource.resolve(request)
if match_dict is not None:
return match_dict
else:
allowed_methods |= allowed

if allowed_methods:
return MatchInfoError(HTTPMethodNotAllowed(request.method, allowed_methods))

Expand Down
8 changes: 8 additions & 0 deletions docs/web_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1539,6 +1539,14 @@ Application and Router
matches the pattern *domain* then
further resolving is passed to *subapp*.

.. warning::

Registering many domains using this method may cause performance
issues with handler routing. If you have a substantial number of
applications for different domains, you may want to consider
using a reverse proxy (such as Nginx) to handle routing to
different apps, rather that registering them as sub-applications.

:param str domain: domain or mask of domain for the resource.

:param Application subapp: nested application.
Expand Down
22 changes: 22 additions & 0 deletions tests/test_web_urldispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -951,6 +951,28 @@ async def get(self) -> web.Response:
assert r.status == 200


async def test_subapp_domain_routing_same_path(aiohttp_client: AiohttpClient) -> None:
"""Regression test for #11665."""
app = web.Application()
sub_app = web.Application()

async def mainapp_handler(request: web.Request) -> web.Response:
assert False

async def subapp_handler(request: web.Request) -> web.Response:
return web.Response(text="SUBAPP")

app.router.add_get("/", mainapp_handler)
sub_app.router.add_get("/", subapp_handler)
app.add_domain("different.example.com", sub_app)

client = await aiohttp_client(app)
async with client.get("/", headers={"Host": "different.example.com"}) as r:
assert r.status == 200
result = await r.text()
assert result == "SUBAPP"


async def test_route_with_regex(aiohttp_client: AiohttpClient) -> None:
"""Test a route with a regex preceded by a fixed string."""
app = web.Application()
Expand Down
Loading