Tornado is a Python web framework and asynchronous networking library. It is a scalable, non-blocking web server that allows applications to handle multiple requests simultaneously using a single thread. Some key features include lightweight and fast templates, asynchronous request handlers, and integrations with databases, caches and other services. Tornado is best suited for building real-time web services and can be used alongside other front-end web servers.
1. AN INTRODUCTION TO
TORNADO
Gavin M. Roy
CTO
myYearbook.com
pyCon 2011
Atlanta, GA
2. TORNADO AT
MYYEARBOOK.COM
• Currency Connect
• Marketing Website, Portal, RESTful API
• Redirect Engine
• Nerve
• Staplr 2
• Image Upload Service
3. WHAT IS TORNADO?
•A scalable, non-blocking web server and micro-framework in
Python 2.5 & 2.6.
• Python 3 port underway
• Developed at FriendFeed and open-sourced by Facebook
• Similar to web.py in use
• Fast: ~1,500 requests/sec backend*
* Your milage will vary
4. FEATURES
• Small barrier to entry to quickly developing applications
• Third Party Authentication via OpenID, OAuth Mixins
• Light-weight template system
• Auto-magical cross-site forgery protection
• WSGI && Google App Engine Support
• Develop using debug mode and automatically reload code and
templates when changed on disk
5. class Application(object):
"""A collection of request handlers that make up a web application.
Instances of this class are callable and can be passed directly to
HTTPServer to serve the application:
application = web.Application([
(r"/", MainPageHandler),
])
http_server = httpserver.HTTPServer(application)
http_server.listen(8080)
ioloop.IOLoop.instance().start()
The constructor for this class takes in a list of URLSpec objects
or (regexp, request_class) tuples. When we receive requests, we
iterate over the list in order and instantiate an instance of the
first request class whose regexp matches the request path.
Each tuple can contain an optional third element, which should be a
dictionary if it is present. That dictionary is passed as keyword
arguments to the contructor of the handler. This pattern is used
for the StaticFileHandler below:
application = web.Application([
(r"/static/(.*)", web.StaticFileHandler, {"path": "/var/www"}),
])
CLEAN, WELL DOCUMENTED CODE
6. WHAT TORNADO ISN’T
•A full stack framework like Django
• Based on Twisted
• There is an unmaintained port, Tornado on Twisted
• Influenced the Cyclone project
•A replacement for a front-end web server
• Run behind a reverse proxy http server (nginx, Cherokee)
7. TORNADO VS TWISTED
• Tornado doesn’t have to be asynchronous
• It doesn’t have as many asynchronous drivers
• Can introduce blocking behaviors
• The Tornado code is smaller and very easy to understand
• Less mature than Twisted
• You don’t need to buy into a development methodology
• Write Python not Twisted
9. TORNADO.WEB
• Most development is focused around this module
• Multiple classes used in a web application
• Includes decorators
• Asynchronous function: @tornado.web.asynchronous
• Authentication Required: @tornado.web.authenticated
10. TORNADO APPLICATION
• tornado.web.Application: Main controller class
• Canonical Tornado Hello World:
import tornado.httpserver
import tornado.ioloop
import tornado.web
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, world")
if __name__ == "__main__":
application = tornado.web.Application([
(r"/", MainHandler),
])
http_server = tornado.httpserver.HTTPServer(application)
http_server.listen(8888)
tornado.ioloop.IOLoop.instance().start()
11. REQUEST HANDLERS
• tornado.web.RequestHandler
• Extend RequestHandler for larger web apps
• Session Handling
• Database, Cache Connections
• Localization
• Implement for your Application
12. REQUEST HANDLERS
• Classes implementing define functions for processing
• get, head, post, delete, put, options
• Hooks on Initialization, Prepare, Close
• Functions for setting HTTP Status, Headers, Cookies, Redirects
and more
13. REQUEST HANDLER EXAMPLE
import redis
import tornado.web
class MyRequestHandler(tornado.web.RequestHandler):
def initialize(self):
host = self.application.settings['Redis']['host']
port = self.application.settings['Redis']['port']
self.redis = redis.Redis(host, port)
class Homepage(MyRequestHandler):
@tornado.web.asynchronous
def get(self):
content = self.redis.get('homepage')
self.write(content)
self.finish()
14. TORNADO.TEMPLATE
• Not required
• Similar to other engines
• Limited python exposure in template
• Fast, extensible
• Built-in support in the RequestHandler class
• Adds cache busting static content delivery
15. REQUESTHANDLER.RENDER
Code
class Home(RequestHandler):
def get(self):
self.render('home.html', username='Leeroy Jenkins');
Template
<html>
<body>
Hi {{username}}, welcome to our site.
</body>
</html>
18. TEMPLATE XSRF EXAMPLE
<form action="/login" method="post">
{{ xsrf_form_html() }}
<div>Username: <input type="text" name="username"/></div>
<div>Password: <input type="password" name="password"/></div>
<div><input type="submit" value="Sign in"/></div>
</form>
No additional work required.
19. UI MODULES
• Extend templates with
reusable widgets across the
site
• One import assigned when
Application is created
• Similar
to RequestHandler in
behavior
21. Embed
UIMODULE EXAMPLE
<div>{{ modules.HTTPSCheck() }}</div>
class HTTPSCheck(tornado.web.UIModule):
def render(self):
UIModule Class
if 'X-Forwarded-Ssl' not in self.request.headers or
self.request.headers['X-Forwarded-Ssl'] != 'on':
return self.render_string("modules/ssl.html")
return ''
<div class="information">
Template
<a href="https://{{request.host}}{{request.uri}}">
{{_("Click here to use a secure connection")}}
</a>
</div>
22. TORNADO.LOCALE
• Locale files in one directory
• In a csv format
• Named as locale.csv, e.g.
en_US.csv
• tornado.locale.load_translations
(path)
• Pass path where files are located
• Invoked as _ method in templates
23. ADDING LOCALIZATION
import tornado.locale as locale
import tornado.web
class RequestHandler(tornado.web.RequestHandler):
def get_user_locale(self):
# Fake user object has a get_locale() function
user_locale = self.user.get_locale()
# If our locale is supported return it
if user_locale in locale.get_supported_locales(None):
return user_locale
# Defaults to Accept-Language header if supported
return None
27. TORNADO.AUTH
• Built in Mixins for OpenID, OAuth, OAuth2
• Google, Twitter, Facebook, Facebook Graph, Friendfeed
• Use RequestHandler to extend your own login functions with
the mixins if wanted
• Is asynchronous
• Not supported in WSGI and Google App Engine
28. USING TORNADO.AUTH
- [/login/form, site.auth_reg.LoginForm]
- [/login/friendfeed, site.auth_reg.LoginFriendFeed]
class LoginFriendFeed(RequestHandler, tornado.auth.FriendFeedMixin):
@tornado.web.asynchronous
def get(self):
if self.get_argument("oauth_token", None):
self.get_authenticated_user(self.async_callback(self._on_auth))
return
self.authorize_redirect()
def _on_auth(self, ffuser):
if not ffuser:
raise tornado.web.HTTPError(500, "FriendFeed auth failed")
return
username = ffuser['username']
29. TORNADO.IOLOOP
• Protocol independent
• tornado.httpserver.HTTPServer uses ioloop.IOLoop
• RabbitMQ driver Pika uses ioloop.IOLoop
• Built in timer functionality
• tornado.ioloop.add_timeout
• tornado.ioloop.PeriodicCallback
33. FIN
• Follow me on Twitter @crad
• Blog: http://gavinroy.com
• Pika: http://github.com/pika
• Async RabbitMQ/AMQP Support for Tornado
• We’re hiring at myYearbook.com
• Drop me an email: gmr@myyearbook.com
34. IMAGE CREDITS
• Lego by Craig A. Rodway
http://www.flickr.com/photos/m0php/530526644/
• Delta Clipper X courtesy of NASA
• United Nations San Francisco Conference by Yould
http://www.flickr.com/photos/un_photo/3450033473/