an introduction to tornado
DESCRIPTION
Given March 11th 2011 at PyCon 2011 in Atlanta, GeorgiaTRANSCRIPT
![Page 1: An Introduction to Tornado](https://reader034.vdocuments.net/reader034/viewer/2022051208/54736fe3b4af9f5e2e8b4569/html5/thumbnails/1.jpg)
AN INTRODUCTION TO TORNADO
Gavin M. RoyCTO
myYearbook.com
pyCon 2011Atlanta, GA
![Page 2: An Introduction to Tornado](https://reader034.vdocuments.net/reader034/viewer/2022051208/54736fe3b4af9f5e2e8b4569/html5/thumbnails/2.jpg)
TORNADO AT MYYEARBOOK.COM
• Currency Connect
• Marketing Website, Portal, RESTful API
• Redirect Engine
• Nerve
• Staplr 2
• Image Upload Service
![Page 3: An Introduction to Tornado](https://reader034.vdocuments.net/reader034/viewer/2022051208/54736fe3b4af9f5e2e8b4569/html5/thumbnails/3.jpg)
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
![Page 4: An Introduction to Tornado](https://reader034.vdocuments.net/reader034/viewer/2022051208/54736fe3b4af9f5e2e8b4569/html5/thumbnails/4.jpg)
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
![Page 5: An Introduction to Tornado](https://reader034.vdocuments.net/reader034/viewer/2022051208/54736fe3b4af9f5e2e8b4569/html5/thumbnails/5.jpg)
CLEAN, WELL DOCUMENTED CODE
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"}), ])
![Page 6: An Introduction to Tornado](https://reader034.vdocuments.net/reader034/viewer/2022051208/54736fe3b4af9f5e2e8b4569/html5/thumbnails/6.jpg)
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)
![Page 7: An Introduction to Tornado](https://reader034.vdocuments.net/reader034/viewer/2022051208/54736fe3b4af9f5e2e8b4569/html5/thumbnails/7.jpg)
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
![Page 8: An Introduction to Tornado](https://reader034.vdocuments.net/reader034/viewer/2022051208/54736fe3b4af9f5e2e8b4569/html5/thumbnails/8.jpg)
KEY MODULES
Take only what you need
![Page 9: An Introduction to Tornado](https://reader034.vdocuments.net/reader034/viewer/2022051208/54736fe3b4af9f5e2e8b4569/html5/thumbnails/9.jpg)
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
![Page 10: An Introduction to Tornado](https://reader034.vdocuments.net/reader034/viewer/2022051208/54736fe3b4af9f5e2e8b4569/html5/thumbnails/10.jpg)
TORNADO APPLICATION• tornado.web.Application: Main controller class
• Canonical Tornado Hello World:
import tornado.httpserverimport tornado.ioloopimport 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()
![Page 11: An Introduction to Tornado](https://reader034.vdocuments.net/reader034/viewer/2022051208/54736fe3b4af9f5e2e8b4569/html5/thumbnails/11.jpg)
REQUEST HANDLERS
• tornado.web.RequestHandler
• Extend RequestHandler for larger web apps
• Session Handling
• Database, Cache Connections
• Localization
• Implement for your Application
![Page 12: An Introduction to Tornado](https://reader034.vdocuments.net/reader034/viewer/2022051208/54736fe3b4af9f5e2e8b4569/html5/thumbnails/12.jpg)
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
![Page 13: An Introduction to Tornado](https://reader034.vdocuments.net/reader034/viewer/2022051208/54736fe3b4af9f5e2e8b4569/html5/thumbnails/13.jpg)
REQUEST HANDLER EXAMPLE
import redisimport 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()
![Page 14: An Introduction to Tornado](https://reader034.vdocuments.net/reader034/viewer/2022051208/54736fe3b4af9f5e2e8b4569/html5/thumbnails/14.jpg)
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
![Page 15: An Introduction to Tornado](https://reader034.vdocuments.net/reader034/viewer/2022051208/54736fe3b4af9f5e2e8b4569/html5/thumbnails/15.jpg)
REQUESTHANDLER.RENDER
class Home(RequestHandler):
def get(self):
self.render('home.html', username='Leeroy Jenkins');
<html> <body> Hi {{username}}, welcome to our site. </body></html>
Cod
eTe
mpl
ate
![Page 16: An Introduction to Tornado](https://reader034.vdocuments.net/reader034/viewer/2022051208/54736fe3b4af9f5e2e8b4569/html5/thumbnails/16.jpg)
BASE TEMPLATE
<html> <head> <title>My Site :: {% block title %}Unextended Template{% end %}</title> <link rel="stylesheet" type="text/css" href="{{ static_url('css/site.css') }}" /> <script type="text/javascript" src="{{ static_url('javascript/site.js') }}"></script> {% if not current_user %}
<script type="text/javascript" src="http://api.recaptcha.net/js/recaptcha_ajax.js"></script>
{% end %} </head> <body{% if current_user %} class="authenticated"{% end %}> {% include "header.html" %} {% if request.uri not in ['/', ''] and current_user %} {{ modules.MemberBar() }} {% end %} <div id="content"> {% block content %} No Content Specified {% end %} </div> <ul id="footer"> <li><a href="/terms">{{_("Terms and Conditions")}}</a></li> <li class="center">{{_("Version")}}: {{ handler.application.settings['version'] }}</li> <li class="right">{{_("Copyright")}} © {{ datetime.date.today().year }}</li> </ul> </body></html>
![Page 17: An Introduction to Tornado](https://reader034.vdocuments.net/reader034/viewer/2022051208/54736fe3b4af9f5e2e8b4569/html5/thumbnails/17.jpg)
CONTENT TEMPLATE
{% extends "base.html" %}{% block title %}{{_("Error Title")}}{% end %}{% block content %}<h1>{{_("Error Title")}}</h1><img src="/static/images/sad_robot.png" class="error_robot" /><p class="error_message">{{_("Error Message")}}</p><h2 class="error">{{status_code}} - {{exception}}</h2>{% end %}
![Page 18: An Introduction to Tornado](https://reader034.vdocuments.net/reader034/viewer/2022051208/54736fe3b4af9f5e2e8b4569/html5/thumbnails/18.jpg)
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.
![Page 19: An Introduction to Tornado](https://reader034.vdocuments.net/reader034/viewer/2022051208/54736fe3b4af9f5e2e8b4569/html5/thumbnails/19.jpg)
UI MODULES
• Extend templates with reusable widgets across the site
• One import assigned when Application is created
• Similar to RequestHandler in behavior
![Page 20: An Introduction to Tornado](https://reader034.vdocuments.net/reader034/viewer/2022051208/54736fe3b4af9f5e2e8b4569/html5/thumbnails/20.jpg)
UIMODULE EXAMPLE
![Page 21: An Introduction to Tornado](https://reader034.vdocuments.net/reader034/viewer/2022051208/54736fe3b4af9f5e2e8b4569/html5/thumbnails/21.jpg)
UIMODULE EXAMPLE
class HTTPSCheck(tornado.web.UIModule):
def render(self):
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"> <a href="https://{{request.host}}{{request.uri}}"> {{_("Click here to use a secure connection")}} </a></div>
<div>{{ modules.HTTPSCheck() }}</div>
UIM
odul
e C
lass
Tem
plat
eEm
bed
![Page 22: An Introduction to Tornado](https://reader034.vdocuments.net/reader034/viewer/2022051208/54736fe3b4af9f5e2e8b4569/html5/thumbnails/22.jpg)
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
![Page 23: An Introduction to Tornado](https://reader034.vdocuments.net/reader034/viewer/2022051208/54736fe3b4af9f5e2e8b4569/html5/thumbnails/23.jpg)
ADDING LOCALIZATION
import tornado.locale as localeimport 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
![Page 24: An Introduction to Tornado](https://reader034.vdocuments.net/reader034/viewer/2022051208/54736fe3b4af9f5e2e8b4569/html5/thumbnails/24.jpg)
USING LOCALIZATION
<html> <body> {{_("Welcome to our site.")}} </body></html>
![Page 25: An Introduction to Tornado](https://reader034.vdocuments.net/reader034/viewer/2022051208/54736fe3b4af9f5e2e8b4569/html5/thumbnails/25.jpg)
LOCALE FILE EXAMPLE: DE_DE"New","Neu""Donate","Spenden""New Paste","Neuer Paste""Secure, Private Pasting","Sicheres Pasten""Unclaimed Hostname","Sie benutzen einen offenen Hostnamen. Klicken Sie heir für weitere Informationen.""Paste Options","Paste Optionen""Formatting","Formatierung""No Formatting","Keine Formatierung""Line Numbers","Zeilennummern""On","An""Off","Aus""Minutes","Minuten""Hour","Stunde""Day","Tag""Week","Woche""Year","Jahr""Expire Paste","Wann soll der Paste gelöscht werden?""Encryption","Verschlüsselung""Encryption Key","Passwort-Verschlüsselung""Encryption Algorithm","Algorithm-Verschlüsselung""Save Paste","Paste speichern""All Rights Reserved","Alle Rechte vorbehalten"
![Page 26: An Introduction to Tornado](https://reader034.vdocuments.net/reader034/viewer/2022051208/54736fe3b4af9f5e2e8b4569/html5/thumbnails/26.jpg)
TEMPLATE EXAMPLE AGAIN
<html> <head> <title>My Site :: {% block title %}Unextended Template{% end %}</title> <link rel="stylesheet" type="text/css" href="{{ static_url('css/site.css') }}" /> <script type="text/javascript" src="{{ static_url('javascript/site.js') }}"></script> {% if not current_user %}
<script type="text/javascript" src="http://api.recaptcha.net/js/recaptcha_ajax.js"></script>
{% end %} </head> <body{% if current_user %} class="authenticated"{% end %}> {% include "header.html" %} {% if request.uri not in ['/', ''] and current_user %} {{ modules.MemberBar() }} {% end %} <div id="content"> {% block content %} No Content Specified {% end %} </div> <ul id="footer"> <li><a href="/terms">{{_("Terms and Conditions")}}</a></li> <li class="center">{{_("Version")}}: {{ handler.application.settings['version'] }}</li> <li class="right">{{_("Copyright")}} © {{ datetime.date.today().year }}</li> </ul> </body></html>
![Page 27: An Introduction to Tornado](https://reader034.vdocuments.net/reader034/viewer/2022051208/54736fe3b4af9f5e2e8b4569/html5/thumbnails/27.jpg)
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
![Page 28: An Introduction to Tornado](https://reader034.vdocuments.net/reader034/viewer/2022051208/54736fe3b4af9f5e2e8b4569/html5/thumbnails/28.jpg)
USING TORNADO.AUTH
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']
- [/login/form, site.auth_reg.LoginForm]- [/login/friendfeed, site.auth_reg.LoginFriendFeed]
![Page 29: An Introduction to Tornado](https://reader034.vdocuments.net/reader034/viewer/2022051208/54736fe3b4af9f5e2e8b4569/html5/thumbnails/29.jpg)
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
![Page 30: An Introduction to Tornado](https://reader034.vdocuments.net/reader034/viewer/2022051208/54736fe3b4af9f5e2e8b4569/html5/thumbnails/30.jpg)
TORNADO.IOLOOP EXAMPLE
class MyClient(object):
def connect(self, host, port):
# Create our socket self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) self.sock.connect((host, port)) self.sock.setblocking(0) self.io_loop = tornado.ioloop.IOLoop.instance()
# Append our handler to tornado's ioloop for our socket events = tornado.ioloop.IOLoop.READ | \ tornado.ioloop.IOLoop.ERROR self.io_loop.add_handler(self.sock.fileno(), self._handle_events, events)
https://github.com/pika/pika/blob/master/pika/adapters/tornado_connection.py
![Page 31: An Introduction to Tornado](https://reader034.vdocuments.net/reader034/viewer/2022051208/54736fe3b4af9f5e2e8b4569/html5/thumbnails/31.jpg)
OTHER MODULES OF NOTE• tornado.database
• MySQL wrapper
• tornado.escape
• Misc escape functions
• tornado.httpclient
• Async HTTP client
• tornado.iostream
• Non-blocking TCP helper class
• tornado.options
• Similar to optparse
• tornado.testing
• Test support classes
• tornado.websocket
• Websocket Support
![Page 32: An Introduction to Tornado](https://reader034.vdocuments.net/reader034/viewer/2022051208/54736fe3b4af9f5e2e8b4569/html5/thumbnails/32.jpg)
• Memcache
• MongoDB
• PostgreSQL
• RabbitMQ
• Redis
ASYNC DRIVERS
![Page 33: An Introduction to Tornado](https://reader034.vdocuments.net/reader034/viewer/2022051208/54736fe3b4af9f5e2e8b4569/html5/thumbnails/33.jpg)
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: [email protected]
![Page 34: An Introduction to Tornado](https://reader034.vdocuments.net/reader034/viewer/2022051208/54736fe3b4af9f5e2e8b4569/html5/thumbnails/34.jpg)
IMAGE CREDITS
• Lego by Craig A. Rodwayhttp://www.flickr.com/photos/m0php/530526644/
• Delta Clipper X courtesy of NASA
• United Nations San Francisco Conference by Youldhttp://www.flickr.com/photos/un_photo/3450033473/