A common routing use-case is to share a route URL pattern for multiple purposes. For instance GitHub is using github.com/<something> for both users and organizations. Indeed the user and organization pages are different. A way to implement this is using a fallback mechanism called by some of us cascade routing. Meaning: fallback to next request handler if current one is not suitable. You’ll find this kind of behavior in popular front/backend framework such as Angular. Actually it’s not yet available in Angular… 😒 and as far as I know not implemented in AIOHTTP. 😭

AN: I don’t compare AIOHTTP and Angular… obviously ! I just challenge the popularity of this feature.

In this short post I implement this mechanism with AIOHTTP !

class NotHandledException(Exception):
    pass

class CascadeRouter:
    def __init__(self, handlers):
        self._handlers = handlers

    async def __call__(self, *args, **kwargs):
        for handler in self._handlers:
            try:
                return (await handler(*args, **kwargs))
            except NotHandledException:
                # Fallback on next handler
                pass
        else:
            raise HTTPNotFound()

DONE ! Yes, that’s it ! Let’s analyze the code above…

First we define a new kind of Exception named NotHandledException. Users (aka developers) will have to raise this exception if they can’t deal with the given request.

The second class CascadeRouter is the router itself. Ah bon ?! It is instantiated with the ordered list of handlers to call. Thanks to __call__ method, we can give this router to AIOHTTP which can call it like any handler.

Here is an example of usage:

async def handler_a(request):
    raise NotHandledException()
    return Response(text='Hello from A')

async def handler_b(request):
    return Response(text='Hello from B')

def main():
    app = Application()
    app.add_routes([
        web.get('/', CascadeRouter([
            handler_a,
            handler_b,
        ]))
    ])

In closing, we can discuss about the for ... else in CascadeRouter#__call__. Indeed it’s not mandatory however it’s IMHO more readable. Later we will be able to replace the return statement by a break and do some extra processing on handler result.

As usual, please leave me a message in my blog issues if you have any comment or advice.

R.