A Whirlwind Presentation


  • I am (not) Jeremy Kelley.
  • If I were actually Jeremy Kelley, I would work at Indeed and amaze you with my wit and knowledge.
  • I would also have the flu.

What is Tornado?

You know, non-meteorologically

Tornado is a simple web framework...

...around a fast HTTP server...

...around a low-level event system.

It can be used for...

...building web apps...


...authentication frameworks...

...even custom TCP-based services.

The Zen of Tornado

from tornado.web import Application, RequestHandler
from tornado.ioloop import IOLoop

class HomeHandler(RequestHandler):

    def get(self):
        self.set_header("Content-type", "text/plain")
        self.write("Hello, world!")

if __name__ == "__main__":
    app = Application([("/", HomeHandler)])


I'm going to talk through how Tornado works.

This means some libraries you might not use.

Tornado is actually very simple.

The Mighty Stack

Event Libraries

Tornado selects the best library for your platform.

For Linux, this means epoll.

For Mac, this means kqueue.

For Windows, this means old / boring select.

These are not the codes you are looking for.

IOLoop Library

The IOLoop abstracts those polling libraries away:

from tornado.ioloop import IOLoop
ioloop = IOLoop.instance()

# watch for events on a socket / file descriptor
ioloop.add_handler(sock.fileno(), callback, ioloop.READ)

# add a callback to run on the next "cycle"

# add a callback to run at (or after) a specific time
ioloop.add_timeout(time.time() + 60, callback)


Singletons -- great until they murder you

Use as default, but allow passing it in

class TCPServer(object):

    def __init__(self, ioloop=None):
        self._ioloop = ioloop or IOLoop.instance()

    def send(self, message):


All you need unless you're writing a TCP client / server.

Use these to split up blocking operations, defer tasks, etc.

# blocking operation...
results = DB.slow_find()

def parse_results(results):
    for result in results:
        # do something

# let other things work for a while
IOLoop.instance().add_callback(parse_results, results)


If you ARE writing a server, you need to:

# add a callback for a file descriptor on certain events
ioloop.add_handler(sock.fileno(), callback, ioloop.READ)

# update for new events
ioloop.update_handler(sock.fileno(), ioloop.WRITE)

# remove file descriptors when finished

...but stayed tuned...

IOStream Library

IOStream wraps sockets:

ioloop = IOLoop.instance()
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
stream = IOStream(sock, io_loop=ioloop)

stream.connect(("", 80), connect_callback)
stream.read_bytes(4096, read_callback)
# etc...

It provides plenty of helpers:

# read until exact string match
stream.read_until("\n\n", header_callback)
# read until a regular expression is matched
stream.read_until_regex("BYTES: \d+\s+", bytes_callback)
# read until the socket closes -- optional streaming
stream.read_until_close(finish_callback, streaming_callback)
# write out to the socket
stream.write("Foobar!", write_callback)
# if a socket is closed, call a callback
stream.closed() or stream.reading() or stream.writing()

Use IOStream when someone else hasn't done it already.

(For instance, your favorite new NoSQL database.)

Don't use IOStream for HTTP clients -- that's coming up.

HTTPServer Library

HTTPServer is (generally) invisible.

It leverages IOStream, etc. to support HTTP/1.1.

Runs implicitly when Application.listen() is called.

You probably won't need to touch it, but it's there.

Uses HTTPRequest.

HTTPRequest is used heavily in the request handlers.

It wraps information / helpers for client connections:

request.body # the request body string, if present
request.headers # dictionary of headers
request.cookies # dictionary of cookies
request.path # /foobar/path
request.query # ?key=value
request.remote_ip # actual request ip
request.scheme # actual request scheme (http[s])

Web Libraries

tornado.web contains the real meat of Tornado.

Most work (in web apps) is done here.

Encapsulates the "V" and "C" of "MVC"

Request Handler

from tornado.web import RequestHandler, Application

class Controller(RequestHandler):
    # regex groups in route passed to method
    def get(self, username):
        ip = self.request.remote_ip # request on handler
        # tornado includes its own templating system
          "template.html", ip=ip, username=username)

app = Application([
  # routes are regex passed into Application
  ("/(\w+)", Controller)

Template System

{% extends "base.html" %}
{% block content %}
<h1>Hello, {{ username.uppercase() }}.</h1>
{# it's mostly just python. #}
{% if ip == "" %}
<p>You must still be working on this.</p>
{% else %}
<p>You are accessing us from {{ ip }}.</p>
{% end if %}
{% end block %}

{# You can even... #}
{% import requests %}
{{ requests.get("").text }}
{# ...but don't. That's really, really stupid. #}

Template Module

It has just enough for what you need...

...with enough rope to hang yourself twice.

So be smart.


class Users(RequestHandler):
    def post(self):
        content = json.loads(self.request.body)
        # application is available from the handler
        id = self.application.settings["database"].save(content)
        # a dictionary passed to write == JSON
          "id": "%s" % (id),
          "name": content["name"]

app = Application([("/users", Users)],
    debug=True, # for automatic reloading


from tornado.wsgi import WSGIApplication
app = WSGIApplication([("/", Handler)])
# for giggles...
server = gevent.pywsgi.WSGIServer(('', 8080), app)

Can't do Tornado's async stuff, though.


  class BaseHandler(RequestHandler):

      def db(self):
        return self.application.settings["db"]

      def get_current_user(self):
          token = self.get_secure_cookie("token")
          user = self.db.users.find_one({"token": token})
          return user or None


  class LoginHandler(BaseHandler):

      def post(self):
          if self.current_user:
          username = self.get_argument("username")
          password = self.get_argument("password")
          user = self.db.find_one({"username": username})
          if not user or user.password != password:
              return self.redirect("/login")
          self.set_secure_cookie("token", user.token)


  class Dashboard(BaseHandler):

      def get(self):
          self.render("dashboard.htm", user=self.current_user)


class AsyncHandler(RequestHandler):

    def get(self):
        service.fetch("/other/thing", callback=self._callback)
        # request stays open...

    def _callback(self, response):
        if response:
          raise HTTPError(500, "We couldn't find it.")
        # without finish, the request never closes

And More

There are lots of other helpers in the web stack.

(Websockets, error helpers, etc.)

Dig around in the documentation.


Simple Usage

from tornado.httpclient import AsyncHTTPClient

def callback(response):
    print response.code
    print response.body

client = AsyncHTTPClient()
client.fetch("", callback)

More Involved

def fetch_callback(response):
    print response.headers
    print response.body

client = AsyncHTTPClient()
    method="POST", body=json.dumps({"key": "value"}),
    headers={"Content-type": "application/json"},
    auth_username="foo", auth_password="bar",
    validate_cert=False, callback=fetch_callback)

You'll probably use this a lot.

It's especially useful when used with auth.


Auth Libraries

Tornado includes lots of third party auth.

This fits nicely with the async nature of Tornado.

Also, you can be lazy about your auth system.

from tornado.auth import TwitterMixin

class TwitterAuthHandler(RequestHandler, TwitterMixin):
    def get(self):
        if self.get_argument("oauth_token", None):
            return self.get_authenticated_user(self._on_auth)

    def _on_auth(self, user):
        if not user:
            raise HTTPError(500, "Couldn't auth.")
        user = self.db.create({"username": user["username"]})
        self.set_secure_cookie("token", user.token)

TCP-Based Libraries

Items of Interest


To Async or Not?


Libraries of Import


@joshmarshall on the Twitters.

@joshmarshall on the GitHub.