Sergey Belash
Sergey Belash

Reputation: 1471

Tornado routing to a "base" handler

I use tornado 4.5.2 with routing implementation. My server have two versions of API, let them call base and fancy. So a client is able to use both of them:

GET /base/foo
GET /base/baz

GET /fancy/foo
GET /fancy/baz

However, some fancy handlers may not be implemented; In this case a base one should be used.

In example:

application = web.Application([
    (r"/base/foo",  handlers.BaseFooHandler,  {"some": "settings"}),
    (r"/base/baz",  handlers.BaseBazHandler,  {"some": "settings"}),
    (r"/fancy/foo", handlers.FancyFooHandler, {"some": "settings"}),
])

when cilent requests GET /fancy/baz the BaseBazHandler should do the job.

How can I achieve that with tornado routing?

Upvotes: 2

Views: 3422

Answers (1)

xyres
xyres

Reputation: 21744

Since you're registering your routes using a decorator, you can create a custom router that will respond to all the unmatched/unregistered /fancy/.* routes. For this to work correctly, you'll have to register your router at the end.

That way your custom router will be matched only if there isn't already a /fancy/... route registered. So, that means the custom router class will need to do these things:

  1. Check if a fallback BaseBazHandler exists or not.
  2. If exists, forward the request to it.
  3. Else, return a 404 error.

Before proceeding any further, you'll have to create a custom class to handle 404 requests. This is necessary because if not handler is found, then this is the easiest way to return a 404 error.

class Handle404(RequestHandler):
    def get(self):
        self.set_status(404)
        self.write('404 Not Found')

Okay, now let's write the custom router:

from tornado.routing import Router

class MyRouter(Router):
    def __init__(self, app):
        self.app = app

    def find_handler(self, request, **kwargs):
        endpoint = request.path.split('/')[2] # last part of the path

        fallback_handler = 'Base%sHandler' % endpoint.title()
        # fallback_handler will look like this - 'BaseBazHandler'

        # now check if the handler exists in the current file
        try:
            handler = globals()[fallback_handler]
        except KeyError:
            handler = Handle404

        return self.app.get_handler_delegate(request, handler)

Finally, after you've added all other routes, you can register your custom router:

from tornado.routing import PathMatches

application.add_handlers(r'.*', # listen for all hosts
    [
        (PathMatches(r"/fancy/.*"), MyRouter(application)),
    ]
)

I should point out that MyRouter.find_handler, only check checks for handlers in the current module (file). Modify the code to search for handlers in different modules, if you want.

Upvotes: 2

Related Questions