Channels ▼
RSS

JVM Languages

Building RESTful APIs with Tornado


Working with Asynchronous Code

If you've ever worked with callbacks, you know how difficult it is to read and understand code split into different methods. Tornado provides a generator-based interface (tornado.gen) to enable you to write asynchronous code in handlers in a single generator.

You simply need to use the @tornado.gen.coroutine decorator for asynchronous generators in the required method and you don't need to add the @tornado.web.asynchronous decorator. Listing Three shows a new subclass of tornado.web.RequestHandler and defines the get() method with the @tornado.gen.coroutine decorator. You need to add two imports to add the code to the previous listing: import tornado.gen and import tornado.httpclient.

Listing Three: A different subclass of tornado.web.RequestHandler.

class GetFullPageAsyncHandler(tornado.web.RequestHandler):
    @tornado.gen.coroutine
    def get(self):
        http_client = tornado.httpclient.AsyncHTTPClient()
        http_response = yield http_client.fetch("http://www.drdobbs.com/web-development")
        response = http_response.body.decode().replace(
            "Most Recent Premium Content", "Most Recent Content")
        self.write(response)
        self.set_header("Content-Type", "text/html")

Because I've added a new subclass of RequestHandler, it is necessary to map the URL pattern in tornado.Web.Application. Listing Four shows the new code that maps the /getfullpage URL to the GetFullPageAsyncHandler.

Listing Four: Mapping a URL to a handler.

application = tornado.web.Application([
    (r"/getfullpage", GetFullPageAsyncHandler),
    (r"/getgamebyid/([0-9]+)", GetGameByIdHandler),
    (r"/version", VersionHandler),
    
])

The GetFullPageAsyncHandler.get method creates a tornado.httpclient.AsyncHTTPClient instance (http_client) that represents a non-blocking HTTP client. Then, the code calls the http_client.fetch method of this instance to asynchronously execute a request. The fetch method returns a Future, whose result is an HTTPResponse, and raises an HTTPError if the request returns a response code other than 200. The code uses the yield keyword to retrieve the HTTPResponse from the Future returned by the fetch method.

The call to fetch retrieves the Dr. Dobb's Web Development page from http://www.drdobbs.com/web-development with an asynchronous execution. When fetch finishes its execution with a successful response code equal to 200, http_response will be an HTTPRequest instance with the contents of the retrieved HTML page in http_response.body. The method continues its execution with the line after the call to fetch. You have all the code that needs to be executed in the get method with the @tornado.gen.coroutine decorator, and you don't have to worry about writing a callback for on_fetch. The next line decodes the response body to a string and replaces "Most Recent Premium Content" with "Most Recent Content." Then, the code calls the self.write method to write the modified string and sets the Content-Type of the response to application/html.

Listing Five is the equivalent code, which uses the @tornado.web.asynchronous decorator instead of @tornado.gen.coroutine. In this case, it is necessary to define the on_fetch method that works as the callback for the http_client.fetch method; therefore, the code is split into two methods.

Listing Five: The equivalent functionality using a decorator.

class GetFullPageAsyncNewHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    def get(self):
        http_client = tornado.httpclient.AsyncHTTPClient()
        http_client.fetch("http://www.drdobbs.com/web-development", callback=self.on_fetch)

    def on_fetch(self, http_response):
        if http_response.error: raise tornado.web.HTTPError(500)
        response = http_response.body.decode().replace("Most Recent Premium Content", "Most Recent Content")
        self.write(response)
        self.set_header("Content-Type", "text/html")
        self.finish()

When the fetch method finishes retrieving the content, it executes the code in on_fetch. Because the get method uses @tornado.web.asynchronous, it is your responsibility to call self.finish() to finish the HTTP request. Thus, the on_fetch method calls self_finish in its last line, after calling self.write and self.set_header. As you can see, it is much easier to use the @tornado.gen.coroutine decorator.

Understanding How Tornado Works with a RequestHandler Subclass

The RequestHandler class defines a SUPPORTED_METHODS class variable with the following code. If you need support for different methods, you need to override the SUPPORTED_METHODS class variable in your RequestHandler subclass:

SUPPORTED_METHODS = ("GET", "HEAD", "POST", "DELETE", "PATCH", "PUT", "OPTIONS")

The default code for the head(), get(), post(), delete(), patch(), put(), and options() methods is a single line that raises an HTTPError. Listing Six shows the code for the get method:

Listing Six: The get() method.

  def get(self, *args, **kwargs):
      raise HTTPError(405)

Whenever the Web application receives a request and matches the URL pattern, Tornado performs the following actions:

  1. It creates a new instance of the RequestHandler subclass that has been mapped to the URL pattern.
  2. It calls the initialize method with the keyword arguments specified in the application configuration. You can override the initialize method to save the arguments into member variables.
  3. No matter what the HTTP request, Tornado calls the prepare method. If you call either finish or send_error, Tornado won't call any additional methods. You can override the prepare method to execute code that is necessary for any HTTP request, then write your specific code in the head(), get(), post(), delete(), patch(), put() or options() method.
  4. It calls the method according to the HTTP request with the arguments based on the URL regular expression that captured the different groups. As you already know, you must override the methods you want your RequestHandler subclass to be able to process. For example, if there is an HTTP GET request, Tornado will call the get method with the different arguments.
  5. If the handler is synchronous, Tornado calls on_finish after the previous method called, according to the HTTP request returns. But if the handler is asynchronous, Tornado executes on_finish after the code calls finish. The previous asynchronous example showed the usage of finish. You can override the on_finish method to perform cleanup or logging. Notice that Tornado calls on_finish after it sends the response to the client.

If the client closes the connection in asynchronous handlers, Tornado calls on_connection_close. You can override this method to clean up resources in this specific scenario. However, the cleanup after processing the request must be included in the on_finish method.

Listing Seven shows a new version of the GetGameByIdHandler class that overrides the initialize method to receive a string specified in the application configuration. The initialize method just saves common_string into a member variable, then the get method uses the string in the response:

Listing Seven: Overriding the initialize() method.

class GetGameByIdHandler(tornado.web.RequestHandler):
    def initialize(self, common_string):
        self.common_string = common_string

    def get(self, id):
        response = { 'id': int(id),
                     'name': 'Crazy Game',
                     'release_date': date.today().isoformat(),
                     'common_string': self.common_string }
        self.write(response)

The following code shows the changes in the arguments passed to the tornado.web.Application constructor to pass a value for common_string in a dictionary for the GetGameByIdHandler request handler:

  application = tornado.web.Application([
      (r"/getgamebyid/([0-9]+)", GetGameByIdHandler,
       dict(common_string='Value defined in Application')),

In this case, I've used a simple string as the value passed from the application. However, the most common usage will be to pass one or more common objects (for example, a database object).

Returning Errors in a Request Handler

Listing Eight shows the code for the ErrorHandler request handler that demonstrates the simplest use of the three different mechanisms to return errors in a handler method.

Listing Eight: A simple error request handler.

class ErrorHandler(tornado.web.RequestHandler):
    def get(self, error_code):
        if error_code == 1:
            self.set_status(500)
        elif error_code == 2:
            self.send_error(500)
        else:
            raise tornado.web.HTTPError(500)

It is necessary to add the appropriate mapping to the Application constructor parameters with the following line:

(r"/error/([0-9]+)", ErrorHandler),

If error_code is equal to 1, the get method calls self.set_status, which sets the status code for the response. It is also possible to specify a reason string as the second parameter. If error_code is equal to 2, the get method calls self.send_error, which sends the specified HTTP error code to the browser. Any other error_code value will make the get method raise a tornado.web.HTTPError exception with the 500 status code.

The three different mechanisms to return errors return the default error page, which you can change by overriding the write_error method in your RequestHandler subclasses.

Conclusion

In this article, I've provided many examples to show how to easy it is to start developing RESTful APIs with Tornado. I've covered basic features, but you will definitely want to dive deeper into Tornado's additional useful features when building a complete RESTful API. Tornado's source code comes with good documentation for the different methods and variables, so you can easily build the methods you need to use as you write code.


Gaston Hillar is a frequent contributor to Dr. Dobb's.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video