Chenglu
Posted on February 21, 2020
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.
Posted on February 21, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.