Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Tornado in 1 Hour (or Less)
Search
Kārlis Lauva
December 04, 2013
Programming
200
4
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Tornado in 1 Hour (or Less)
Tornado crash-course
Kārlis Lauva
December 04, 2013
More Decks by Kārlis Lauva
See All by Kārlis Lauva
Let's talk about PureScript
karlis
0
88
Going Full Monty with full.monty
karlis
1
110
The Transatlantic Struggle
karlis
0
84
Two Scoops of Scala
karlis
0
120
Valsts pārvaldes atvērto datu semantiskās integrācijas procesi
karlis
1
200
Other Decks in Programming
See All in Programming
Honoでのサプライチェーン侵害対策 〜 3つのライブラリに学ぶ
yusukebe
6
1.4k
AI 時代のソフトウェア設計の学び方
masuda220
PRO
29
13k
ローカルLLMでどこまでコードが書けるか -拡張版 / How much code can be written on a local LLM Extended
kishida
11
4.3k
RTSPクライアントを自作してみた話
simotin13
0
620
Creating Composable Callables in Contemporary C++
rollbear
0
150
dRuby over BLE
makicamel
2
380
The ROI of Quarkus for Spring Boot Applications
hollycummins
0
120
LLMによるContent Moderationの本番運用の裏側と品質担保への挑戦
suikabar
3
710
正しくソフトウェアを作る、前提を疑うための認知の視点 / doubt-premise
minodriven
21
6.8k
フロントエンドとバックエンドで「1文字」を揃えよう
youkidearitai
PRO
0
710
過去最大のMCPアップデート! 2026-07-28 RC版の謎に迫る
licux
6
370
生成AI時代にこそ効くGo | Why Go Works in the Age of Generative AI
mom0tomo
8
3.3k
Featured
See All Featured
Have SEOs Ruined the Internet? - User Awareness of SEO in 2025
akashhashmi
0
370
Designing for Performance
lara
611
70k
Impact Scores and Hybrid Strategies: The future of link building
tamaranovitovic
0
310
Fantastic passwords and where to find them - at NoRuKo
philnash
52
3.7k
Pawsitive SEO: Lessons from My Dog (and Many Mistakes) on Thriving as a Consultant in the Age of AI
davidcarrasco
0
160
BBQ
matthewcrist
89
10k
Unsuck your backbone
ammeep
672
58k
Code Review Best Practice
trishagee
74
20k
Making the Leap to Tech Lead
cromwellryan
135
9.9k
Chrome DevTools: State of the Union 2024 - Debugging React & Beyond
addyosmani
10
1.2k
The B2B funnel & how to create a winning content strategy
katarinadahlin
PRO
1
390
Evolution of real-time – Irina Nazarova, EuRuKo, 2024
irinanazarova
9
1.4k
Transcript
TORNADO IN 1 HOUR (OR LESS) Kārlis Lauva @skazhy python.lv
meetup 2013.12.04
Tornado in 3 bullet points • High performance™ web framework
• Async networking library • Gets out of your way (most of the time)
Timeline • 2007.10 - FriendFeed is launched • 2009.08 -
Facebook acquires FriendFeed • 2009.09 - Tornado is open sourced • 2013.09 - Last stable release (3.1.1)
Who’s using it? …..and your company.
Installing pip install tornado pip install git+https://github.com/facebook/tornado.git
“Hello, World!” import tornado.web import tornado.ioloop class Handler(tornado.web.RequestHandler): def get(self):
self.finish("Hello World!") handlers = [tornado.web.URLSpec(r"/$", Handler)] app = tornado.web.Application(handlers) app.listen(1337) tornado.ioloop.IOLoop.instance().start()
MAIN INGREDIENTS
tornado.ioloop • I/O loop for non-blocking sockets • One per
application (or thread) • Uses epoll or kqueue
Example - TCP server def connection_ready(sock, fd, events): while True:
try: connection, address = sock.accept() except socket.error: return connection.setblocking(0) connection.send("Hello") sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.setblocking(0) sock.bind(("", 1337)) sock.listen(128) io_loop = ioloop.IOLoop.instance() callback = functools.partial(connection_ready, sock) io_loop.add_handler(sock.fileno(), callback, io_loop.READ) io_loop.start()
tornado.httpserver • Async web server • Utilizes a singleton IOLoop
• Subclass of TCP server (duh)
Example - HTTP server import tornado.httpserver import tornado.ioloop def handle_request(request):
message = "Hello world\n" request.write("HTTP/1.1 200 OK\r\nContent-Length: %d\r\n\r\n%s" % \ (len(message), message)) request.finish() http_server = tornado.httpserver.HTTPServer(handle_request) http_server.listen(8080) tornado.ioloop.IOLoop.instance().start()
tornado.web.Application • A collection of resource - handler mappings •
Setting container • Manages UI modules & rendering • Wraps tornado.httpserver • Supports virtual hosts
tornado.web.RequestHandler • Basic building block • If method exists -
call it, else raise HTTP 405 • Not limited to “standard” verbs • Unified request preparation / finalizing methods
A (blocking) request handler # in handler listing URLSpec(r"/users/(\w+)$", SomeHandler)
class Somehandler(tornado.web.RequestHandler): def post(self, user_id): if not self.get_argument(message): raise HTTPError(400, "No message") payload = { "user_id": user_id, "message": self.get_argument(message), } self.application.db.messages.insert(payload) response = HTTPClient().fetch("http://foo.bar.baz") self.render("template.html", {"data": response})
What else is there? • HTTP client • Templating system
• Option management / collection system • Generator interface for async calls • Auth mixins for OAuth • ...and more
ADDING ASYNC-SAUCE
Blocking request redux def post(self, user_id): if not self.get_argument(message): raise
HTTPError(400, "No message") payload = { "user_id": user_id, "message": self.get_argument(message), } self.application.db.messages.insert(payload) response = HTTPClient().fetch("http://foo.bar.baz") self.render("template.html", {"data": response})
Non-blocking request with callback @web.asynchronous def post(self, user_id): def callback(response):
self.render("template.html", {"data": response}) if not self.get_argument(message): raise HTTPError(400, "No message") payload = { "user_id": user_id, "message": self.get_argument(message), } self.application.db.messages.insert(payload) AsyncHTTPClient().fetch("http://foo.bar.baz", callback)
A non-blocking request with @gen @web.asynchronous @gen.engine def post(self, user_id):
def callback(response): if not self.get_argument(message): raise HTTPError(400, "No message") payload = { "user_id": user_id, "message": self.get_argument(message), } self.application.db.messages.insert(payload) resp = yield gen.Task(AsyncHTTPClient().fetch, "http://foo.bar.baz") self.render("template.html", {"data": resp})
Non-blocking calls for pymongo @gen.engine @web.asynchronous def post(self, user_id): def
callback(response): if not self.get_argument(message): raise HTTPError(400, "No message") payload = { "user_id": user_id, "message": self.get_argument(message), } motor.Op(self.application.db.messages.insert, payload) resp = yield gen.Task(AsyncHTTPClient().fetch, "http://foo.bar.baz") self.render("template.html", {"data": resp})
Caveats • Always raise in main thread • Don’t forget
to add decorators • @gen.Tasks are toxic
tornado.websocket class EchoHandler(websocket.WebSocketHandler): def open(self): # the same as handler.get()
logging.debug("WebSocket opened") def on_message(self, message): self.write_message(message) def on_close(self): logging.debug("WebSocket closed") // Client side var ws = new WebSocket("ws://localhost:1337/echo"); ws.onopen = function() { ws.send("Hello, world"); }; ws.onmessage = function (evt) { alert(evt.data); };
TORNADO TEMPLATING
• Django & Jinja2-esque • Easily extendible • Built-in helpers
for reverse urls, etc tornado.template
Example context = { "bugs": {"123": "Fix color scheme"} "uppercase":
lambda x: x.upper() } <ul> {% for bug_id, description in bugs.items() %} <li>{{ bug_id }} - {{ description }}</li> <p>{{ shout(“whoa”) }}</p> {% end %} </li>
TESTING + DEBUGGING
tornado.testing • Wrappers for unittest.TestCase • Helpers for testing async
handlers
PDB • Works as you'd expect app.listen(options.port) pdb.set_trace() tornado.ioloop.IOLoop.instance().start()
REAL WORLD TORNADO
Managing instances with supervisord [program:app-8000] directory=/srv/app/ command=python server.py --port=8101 --mongodb_host=localhost:27017
user=nginx stdout_logfile=/srv/app/logs/app.8000.log [program:app-8001] directory=/srv/app/ command=python server.py --port=8001 --mongodb_host=localhost:27017 user=nginx stdout_logfile=/srv/app/logs/app.8001.log [group:webapp] programs=app-8000,app-8001
Load balancing with nginx upstream webapp { server localhost:8000; server
localhost:8001; } server { server_name foobar; location / { proxy_pass http://webapp; } }
Utilize ngnix to it's full power • Do not serve
static files from Tornado • Whitelist routing (don't bug Tornado with 403s)
Load balancing while you load balance with Amazon ELB
Deployment / production settings python server.py --port=8001 --mongodb_host=localhost:27017 # In
application file from tornado.options import define, options define("port", default=1337, type=int) define("mongodb_host", default="localhost:27017", type=str) define("version", default="1", type=str) class Application(tornado.web.Application): def __init__(self): self.db = MongoClient(options.mongodb_host) handlers = [....] super(Application, self).__init__(handlers, version=options.version) def main(): options.parse_command_line() app = Application() application.listen(options.port)
Tornado and HTTPS • As easy as adding an option
to the application instance ssl_options={ "certfile": path_to_certfile, "keyfile": path_to_keyfile, } app = Application(handlers, ssl_options=ssl_options)
Error collection • Override RequestHandler.send_error() • Use error collection mixins
Sentry integration with Raven • Using mixins to capture exceptions
class Hadler(SentryMixin, tornado.web.RequestHandler): @tornado.web.asynchronous @tornado.gen.engine def get(self): try: raise ValueError() except Exception as e: response = yield tornado.gen.Task( self.captureException, exc_info=True ) self.finish()
Where to go next? • Read through the source •
Start building rad hacks • ??? • PROFIT!!
QUESTION TIME!