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
エンジニアと一緒にテストコードの設計と実装を改善した話
mototakatsu
0
210
フロントエンドとバックエンドで「1文字」を揃えよう
youkidearitai
PRO
0
710
Javaの型とAI時代に型が大事な理由 / java types and type in AI era
kishida
2
140
技術記事、AIに書かせるか、自分で書くか? 〜それでも私が自分の手で書く理由〜 / #QiitaConference
jnchito
2
1.4k
依存関係から依存物へ―Dependencyという言葉の歴史をひも解く
j_lee
0
120
Snowflake Summitでの新機能 CoCo / CoWork / snowflake-summit-2026-overall-what-new-coco
tatsuhiro
1
150
AIとASP.NET Coreで雑Webアプリを作った話
mayuki
0
660
IBM Bobを活用したレガシーアプリの最新化
oniak3ibm
PRO
1
200
「AIで開発し、AIを届ける」をEvalでつなぐ 〜AIネイティブに始めるプロダクト開発の実践〜 / Connecting "Develop with AI, deliver AI" with Eval
rkaga
4
5.3k
LLMによるContent Moderationの本番運用の裏側と品質担保への挑戦
suikabar
3
710
Make SRE Operations Easier with Azure SRE Agent
kkamegawa
0
6.9k
Vue × Nuxt × Oxc どこまで使える?実運用の現在地
andpad
0
270
Featured
See All Featured
実際に使うSQLの書き方 徹底解説 / pgcon21j-tutorial
soudai
PRO
201
75k
Let's Do A Bunch of Simple Stuff to Make Websites Faster
chriscoyier
508
140k
What’s in a name? Adding method to the madness
productmarketing
PRO
24
4.1k
Navigating Weather and Climate Data
rabernat
0
220
Gemini Prompt Engineering: Practical Techniques for Tangible AI Outcomes
mfonobong
2
440
The Web Performance Landscape in 2024 [PerfNow 2024]
tammyeverts
12
1.2k
From π to Pie charts
rasagy
0
210
The AI Search Optimization Roadmap by Aleyda Solis
aleyda
1
5.9k
Measuring Dark Social's Impact On Conversion and Attribution
stephenakadiri
2
220
4 Signs Your Business is Dying
shpigford
187
22k
Chasing Engaging Ingredients in Design
codingconduct
0
220
The untapped power of vector embeddings
frankvandijk
2
1.8k
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!