Skip to content

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 returns

Three key methods:

MethodPurpose
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:])
Last updated on