Getting Started with Tornado
Tornado is a Python web framework and asynchronous networking library originally developed by FriendFeed and later open-sourced by Facebook. It does not rely on WSGI; instead, it processes requests directly using non-blocking I/O and an event loop. A single Tornado process can handle thousands of concurrent connections, making it especially suited for scenarios that require persistent connections such as long polling and WebSocket.
This article introduces Tornado’s installation and core concepts, and walks through routing configuration, debug mode, and multi-process deployment via minimal runnable examples.
Installation
pip install tornadoTornado 6.x requires Python 3.8+. The official recommendation is to use native async/await coroutines in a Python 3.10+ environment.
Comparison with Django and Flask
| Dimension | Django | Flask | Tornado |
|---|---|---|---|
| Purpose | Full-featured synchronous framework | Lightweight synchronous framework | Async framework + networking library |
| Concurrency model | Multi-process / multi-thread | Multi-process / multi-thread | Async event loop |
| ORM | Built-in | None | None |
| Use cases | Traditional web / admin backends | Small services / prototypes | High concurrency / persistent connections |
| Learning curve | Medium | Low | Medium (requires understanding async) |
Your First Tornado Application
import tornado.ioloop
import tornado.web
class IndexHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, Tornado!")
def make_app():
return tornado.web.Application([
(r"/", IndexHandler),
])
if __name__ == "__main__":
app = make_app()
app.listen(8888)
tornado.ioloop.IOLoop.current().start()Three core objects:
RequestHandler: Each route maps to a Handler class; each HTTP method corresponds to a same-named method (get,post,put,delete, etc.).Application: The application itself, accepting a list of routes and global settings.IOLoop: The event loop.IOLoop.current().start()blocks and runs until the process exits.
Route Configuration
Each entry in the route list is a tuple (pattern, handler) or constructed via tornado.web.url():
from tornado.web import Application, url
app = Application([
url(r"/", IndexHandler, name="index"),
url(r"/user/(\d+)", UserHandler, name="user_detail"),
url(r"/admin", AdminHandler, {"title": "Admin Panel"}, name="admin"),
])The four parameters of url():
| Parameter | Meaning |
|---|---|
pattern | Regular expression URL pattern |
handler | The Handler class for this route |
kwargs (optional) | Keyword argument dictionary passed to initialize() |
name (optional) | Route name, used for reverse URL resolution via reverse_url() |
Global Settings
The second argument to Application accepts a settings dictionary:
settings = {
"debug": True, # Debug mode
"template_path": "templates", # Template directory
"static_path": "static", # Static files directory
"static_url_prefix": "/static/", # Static files URL prefix
"cookie_secret": "your-secret-key", # Signed cookie secret key
"login_url": "/login", # Auth redirect URL
"xsrf_cookies": True, # Enable XSRF protection
}
app = Application(handlers, **settings)Debug Mode
When debug: True is enabled:
- The server automatically restarts on code changes
- Caught exceptions display full tracebacks in the browser
- Never enable this in production
Command-Line Options
The tornado.options module provides command-line argument parsing without depending on argparse:
from tornado.options import define, options, parse_command_line
define("port", default=8888, type=int, help="Listen port")
define("debug", default=False, type=bool, help="Debug mode")
if __name__ == "__main__":
parse_command_line() # Parse sys.argv
app = make_app()
app.listen(options.port)
tornado.ioloop.IOLoop.current().start()Pass arguments at startup:
python app.py --port=9000 --debug=trueMulti-Process Mode
By default, app.listen() is single-process. To take full advantage of multi-core CPUs, use HTTPServer with server.start(n):
import tornado.httpserver
if __name__ == "__main__":
app = make_app()
server = tornado.httpserver.HTTPServer(app)
server.bind(8888)
server.start(0) # 0 means start one subprocess per CPU core
tornado.ioloop.IOLoop.current().start()server.start(n) parameter meaning:
0: Automatically equals the number of CPU cores on the current machine1: Single process (equivalent toapp.listen())n > 1: Manually specify the number of processes
server.start() must be called before IOLoop.current().start(), and it cannot be used on Windows (fork is not supported). For production environments, it is recommended to manage multiple Tornado processes using tools like Supervisor or Gunicorn instead.