Vous êtes sur la page 1sur 30

An Introduction to Tornado

Gavin M. Roy
CTO
myYearbook.com

PhillyPug November 2010


Tornado at myYearbook.com

> 12,000 requests/sec across 7 servers


Currency Connect
Redirect Engine
Nerve
Staplr 2
Image Upload
What is Tornado?

A scalable, non-blocking web server and


micro-framework
Developed at FriendFeed and open-
sourced by Facebook
Similar to web.py in use
Fast: 1,500 requests/sec backend*
* Your milage may and most likely will vary
Well Documented
What Tornado Isn’t

A full stack framework


Based on Twisted
There is an unmaintained port,
Tornado on Twisted
Influenced the Cyclone project
A replacement for a front-end web server
Key Modules
Take only what you need
tornado.web

Most development is focused around this


module
Multiple classes used in a web
application
Includes decorators as well
tornado.web.Application

Main controller class


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()
tornado.web.Application

Initialization Options:
Route to request handlers
Default host
Settings
Transforms
WSGI
tornado.web.Application Settings

debug: Reload on save of code and templates


gzip: Enable GZip Content Encoding
login_url: When using
@tornado.web.authenticated decorator
static_path: Base path for static assets
template_path: Base path for template files
ui_modules: “blocks” that plug into templates
xsrf_cookies: Cross-site forgery protection
tornado.web.RequestHandler

Extend in most projects


Session Handling
Database, Cache Connections
Localization
Implement for your Application
tornado.web.RequestHandler

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
RequestHandler 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()
tornado.ioloop

Protocol independent
tornado.httpserver.HTTPServer implemented
using the ioloop
Implemented a Tornado adapter for Pika
using it
Built in timer functionality
tornado.ioloop.add_timeout
tornado.ioloop.PeriodicCallback
tornado.ioloop Example

class MyServer(object):

def connect(self, host, port):

        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()
        self.handle_connection_open()
          
        # 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/gmr/pika/blob/master/pika/tornado_adapter.py
tornado.template

Not required
Similar to other engines
Limited python exposure in template
Fast, extensible
Built in to RequestHandler functionality
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>
Template Example
<html>
<head>
<title>eMuse :: {% block title %}Unextended Template{% end %}</title>
<link rel="stylesheet" type="text/css" href="{{static_url('css/site.css')}}" />{% block css %}{%
end %}
<script type="text/javascript" src="{{static_url('javascript/site.js')}}"></script>
{% block javascript %}{% end %}
{% 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")}} &copy; {{ datetime.date.today().year }} Poison Pen,
LLC</li>
</ul>
</body>
</html>
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>
tornado.web.UIModule

Reusable widgets across the site


Modules instantiated through templates
One import assigned when Application is
instantiated
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 ''

<li class="information">
<a href="https://{{request.host}}{{request.uri}}">
{{_("Click here to use a secure connection")}}
</a>
</li>

 {{ modules.HTTPSCheck() }}
tornado.locale

Locale files in one directory


In a csv format
Named for locale.csv, e.g. en_US.csv
tornado.locale.load_translations
Pass in directory where files are located
tornado.locale.get_supported_locales
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"
Using 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
Using Localization in Templates

<html>
<body>
{{_("Welcome to our site.")}}
</body>
</html>
tornado.auth

Built in OAuth Mixins for Google,


Twitter, Facebook, Friendfeed
Use RequestHandler to extend your own
Login functions with the mixins if wanted
Built to be asynchronous
Using tornado.auth

- [/login/form, emuse.auth_reg.LoginForm]
- [/login/friendfeed, emuse.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']
Other Modules of Note
tornado.database tornado.options

MySQL wrapper Similar to optparse

tornado.escape tornado.testing

Misc escape functions Test support classes

tornado.httpclient tornado.wsgi

Async HTTP client WSGI Support

tornado.iostream tornado.websocket

Non-blocking TCP helper Websocket Support


class
Questions?

Follow me on Twitter @crad


Blog: http://gavinroy.com
Tinman: http://github.com/gmr/Tinman
Pika: http://github.com/gmr/Pika
Image Credits

Lego by Craig A. Rodway


http://www.flickr.com/photos/m0php/
530526644/