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
89
Going Full Monty with full.monty
karlis
1
120
The Transatlantic Struggle
karlis
0
88
Two Scoops of Scala
karlis
0
120
Valsts pārvaldes atvērto datu semantiskās integrācijas procesi
karlis
1
210
Other Decks in Programming
See All in Programming
MySQLとPostgreSQLって何が違うの?
akagami
0
140
Cloudflare is Agents
chimame
0
180
Go を使い始めて 2 ヶ月の学び / My first two months with Go
contour_gara
0
380
Loosening the Reins: Go Generics Get More Flexible
kuro_kurorrr
0
330
30年振りにコンパイラの定数整数除算を改善した
herumi
9
4.2k
Press start. Python's next generation.
willingc
PRO
3
150
freeeにおけるEvalsの実践例の紹介
freee
PRO
0
150
Jindong: Introducing Declarative Haptics in Compose Multiplatform
l2hyunwoo
0
130
Claude Code全社展開のためにやったことn選~プラグイン302個・コミッター271人を支えるために~
kenchan
5
1.8k
ソフトウェアラスタライザ
fadis
1
660
言葉の格闘技のススメ~紙とペンと言葉から始める、キャリアの描き方~
progresscicada
2
180
DynamoDBの基礎を振り返りながらベクトル検索機能を理解する
musan
2
210
Featured
See All Featured
Embracing the Ebb and Flow
colly
88
5.1k
Agile that works and the tools we love
rasmusluckow
331
22k
Building the Perfect Custom Keyboard
takai
2
850
Game over? The fight for quality and originality in the time of robots
wayneb77
1
250
Connecting the Dots Between Site Speed, User Experience & Your Business [WebExpo 2025]
tammyeverts
11
1k
Rails Girls Zürich Keynote
gr2m
96
14k
Taking LLMs out of the black box: A practical guide to human-in-the-loop distillation
inesmontani
PRO
3
2.4k
Making Projects Easy
brettharned
120
6.7k
How to Grow Your eCommerce with AI & Automation
katarinadahlin
PRO
1
250
Put a Button on it: Removing Barriers to Going Fast.
kastner
60
4.5k
<Decoding/> the Language of Devs - We Love SEO 2024
nikkihalliwell
1
300
Breaking role norms: Why Content Design is so much more than writing copy - Taylor Woolridge
uxyall
1
380
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!