A way to solve the handler mess of Tornado

louisshe

Chenglu

Posted on February 21, 2020

A way to solve the handler mess of Tornado

As a web developer comming from Ruby on Rails, the "flexibility" of the handlers organization in Tornado is really painful. So I decide to organize the handlers in a Rails way.

By default, in Tornado, the actions of a handler are exactly the http verb: GET, POST, PATCH, DELETE. So when I think about how to write a handler, I have to think in the protocol layer.

For example, building a websites of books. The books list page and book detail page both use the same http verb GET, but they have totally different logic in the backend. If we organize the handler in http verb, then the handler goese like this:

class BooksHandler(RequestHandler):

    def get(self, id=None):
        if id is not None:
            # render book list page
        else:
            # render book detail of id

Wouldn't that be great if we could just seperate the 2 unrelated pieces of code into 2 methods, like the following:

class BooksHandler(RequestHandler):

    def index(self):
        # render book list page

    def show(self, id):
        # render book detail of id

And that is the main goal of the refactoring, to organize the handlers in a Resource way instead of Protocol way.

Luckly Ruby on Rails already done this before and have been proved to be practisable even in large projects. So I'm just gonna copy the resource abstraction of Rails, that is:

  • A Handler is for manumating one resource.
  • A Handler can have at most 7 actions. The following table shows what the action should do and how does a http request reach them.
HTTP Verb Path Handler#Action Used for
GET /photos photos#index display a list of all photos
GET /photos/new photos#new return an HTML form for creating a new photo
POST /photos photos#create create a new photo
GET /photos/:id photos#show display a specific photo
GET /photos/:id/edit photos#edit return an HTML form for editing a photo
PATCH/PUT /photos/:id photos#update update a specific photo
DELETE /photos/:id photos#destroy delete a specific photo

Then What about login? I don't see any resources in that action.

Everything can be resources. For login/logout, it's actually create a session and destroy a session.

Now if we organizing our handlers in this way, there is no need for us to write the router's url matcher manully, a handler should populate the routes by itself, like these:

class BooksHandler(RequestHandler):

    def index(self):
        # render book list page

    def show(self, id):
        # render book detail of id


app = tornado.web.Application([
    # no more manully set regex
    *BooksHandler.routes()
])
app.listen(8888)
tornado.ioloop.IOLoop.current().start()

Now, it should be good to visit the 2 url:

curl http://localhost:8888/books
curl http://localhost:8888/books/1

I have already implemented this at https://github.com/louis-she/tornado-resource-handler. This can be directly used in existed projects, just try to write new handler in resource way, it will not broke the existing handlers.

Have fun with abstracting Resources.

💖 💪 🙅 🚩
louisshe
Chenglu

Posted on February 21, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related