Advanced Views and Routing
Tornado’s RequestHandler provides a complete set of lifecycle hooks that allow you to insert logic at various stages of request processing. This article covers advanced routing patterns (aliases and parameter injection), the execution order of view methods, the output buffering mechanism, and built-in user authentication support.
Advanced Routing
url() and initialize
When building routes with tornado.web.url(), you can pass a kwargs dictionary. After instantiating the Handler, Tornado calls initialize(**kwargs) to inject these parameters into the view:
from tornado.web import Application, url
app = Application([
url(r"/admin", AdminHandler, {"title": "Admin Panel", "require_auth": True}),
])
class AdminHandler(tornado.web.RequestHandler):
def initialize(self, title, require_auth=False):
self.title = title
self.require_auth = require_auth
def get(self):
self.write(f"Welcome to {self.title}")A typical use of initialize is to inject shared dependencies (database connections, configuration values, etc.) so you don’t have to retrieve them in every method.
reverse_url — Reverse URL Resolution
By naming routes, you can generate URLs from route names, avoiding hard-coded paths:
app = Application([
url(r"/user/(\d+)", UserHandler, name="user_detail"),
])Call it from a Handler:
url = self.reverse_url("user_detail", 42) # Produces "/user/42"
self.redirect(url)Call it from a template:
<a href="{{ reverse_url('user_detail', user.id) }}">View User</a>View Lifecycle
The following methods are triggered in sequence for each request:
Normal Execution Order
set_default_headers()
↓
initialize(**kwargs)
↓
prepare()
↓
get() / post() / put() / delete() / ... (corresponding HTTP method)
↓
on_finish()Execution Order When an Exception is Raised
When an exception is raised in prepare() or an HTTP method, write_error() replaces the normal method execution:
set_default_headers()
↓
initialize(**kwargs)
↓
prepare() ← if an exception is raised here, jump to write_error
↓
get() / post() / ... ← if an exception is raised here, jump to write_error
↓
write_error(status_code, **kwargs)
↓
on_finish()prepare
prepare() is called before the HTTP method executes. It is well-suited for common pre-processing tasks such as parsing a JSON request body or validating shared parameters:
import json
class BaseApiHandler(tornado.web.RequestHandler):
def prepare(self):
content_type = self.request.headers.get("Content-Type", "")
if "application/json" in content_type:
try:
self.json_body = json.loads(self.request.body)
except json.JSONDecodeError:
self.send_error(400, reason="Invalid JSON")
else:
self.json_body = {}prepare() can also be async def, enabling asynchronous operations before the request is handled (such as querying a database to verify a token).
on_finish
on_finish() is called after the response has been sent, making it suitable for logging, resource cleanup, and other teardown work:
import time
class TimedHandler(tornado.web.RequestHandler):
def prepare(self):
self._start_time = time.time()
def on_finish(self):
duration = time.time() - self._start_time
print(f"{self.request.method} {self.request.uri} took {duration:.3f}s")on_finish() runs after the response has already been sent. You cannot call write() or modify response headers at this point.write_error
Override write_error() to customize error response formatting:
class BaseHandler(tornado.web.RequestHandler):
def write_error(self, status_code, **kwargs):
self.set_header("Content-Type", "application/json")
message = self._reason
if "exc_info" in kwargs:
# kwargs["exc_info"] is a (type, value, traceback) tuple
exc = kwargs["exc_info"][1]
message = str(exc)
self.write({"error": status_code, "message": message})Output Buffering
Tornado uses a buffered output model: write() appends content to an in-memory buffer without sending it immediately. The response is only actually sent to the client when finish() is called.
class StreamHandler(tornado.web.RequestHandler):
async def get(self):
for i in range(5):
self.write(f"chunk {i}\n")
await self.flush() # Flush the buffer immediately, sending buffered content
# finish() is called automatically by the framework after the method returnsThree key methods:
| Method | Purpose |
|---|---|
write(chunk) | Append content to the buffer |
flush() | Send buffered content to the client (does not end the request) |
finish(chunk=None) | Send buffered content and close the connection; ends the request |
User Authentication
@authenticated Decorator
tornado.web.authenticated is the built-in authentication decorator. A decorated method first calls get_current_user(); if it returns None or False, the user is automatically redirected to settings["login_url"]:
import tornado.web
class ProfileHandler(tornado.web.RequestHandler):
def get_current_user(self):
return self.get_secure_cookie("user_id")
@tornado.web.authenticated
def get(self):
user_id = self.current_user.decode()
self.write(f"Current user: {user_id}")Required settings:
settings = {
"cookie_secret": "your-secret-key",
"login_url": "/login", # Redirect unauthenticated users here
}Login and Logout
class LoginHandler(tornado.web.RequestHandler):
def post(self):
username = self.get_body_argument("username")
password = self.get_body_argument("password")
# Verification logic (query database, etc.)
if verify_user(username, password):
self.set_secure_cookie("user_id", str(user.id))
self.redirect("/")
else:
self.render("login.html", error="Invalid username or password")
class LogoutHandler(tornado.web.RequestHandler):
def get(self):
self.clear_cookie("user_id")
self.redirect("/login")Unified Authentication in prepare
For API endpoints, it is recommended to verify tokens uniformly in prepare() rather than decorating each method individually:
class ApiBaseHandler(tornado.web.RequestHandler):
async def prepare(self):
token = self.request.headers.get("Authorization", "")
if not token.startswith("Bearer "):
self.set_status(401)
self.write({"error": "Unauthorized"})
self.finish()
return
# Optionally verify the token asynchronously against a database
self.current_user = await verify_token(token[7:])