Building Robust Tornado Applications: Data Persistence and Security Controls

Database Integration

Unlike frameworks such as Django, Tornado does not include a built-in Object-Relational Mappping (ORM) system. Developers must configure external libraries to manage database interactions. For MySQL environments, the torndb package is commonly used. Note that starting from Tornado version 3.0, the built-in database module was removed in favor of this standalone package. It acts as a wrapper around MySQLdb and supports Python 2, though community forks exist for Python 3 compatibility.

Setup and Initialization

Install the dependency:

pip install torndb

A database connection should be established once during application startup so it can be shared across request handlers. This instance is typically attached to the Application object.

import torndb

class MainApplication(tornado.web.Application):
    def __init__(self):
        routes = [
            (r"/", HomeHandler),
        ]
        settings = {
            "template_path": "./templates",
            "static_path": "./statics",
            "debug": True
        }
        super().__init__(routes, **settings)
        self.db_pool = torndb.Connection(
            host="localhost",
            database="inventory_db",
            user="admin_user",
            password="secure_pass"
        )

Creating Schema

Prepare the table structure using standard SQL commands:

CREATE DATABASE inventory_db CHARACTER SET utf8;
USE inventory_db;

CREATE TABLE products (
    id INT UNSIGNED NOT NULL AUTO_INCREMENT,
    item_name VARCHAR(100) NOT NULL DEFAULT '',
    location_code VARCHAR(50) NOT NULL DEFAULT '',
    cost DECIMAL(10, 2) NOT NULL DEFAULT 0.0,
    stock_count INT NOT NULL DEFAULT 10,
    reviews INT NOT NULL DEFAULT 0,
    PRIMARY KEY(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Execution Methods

For non-query operations like inserts or updates, use execute. This returns the ID of the last inserted row.

class AddProductHandler(RequestHandler):
    def post(self):
        name = self.get_argument("name")
        loc = self.get_argument("loc")
        price = float(self.get_argument("price"))
        try:
            new_id = self.application.db_pool.execute(
                "INSERT INTO products(item_name, location_code, cost) VALUES(%s, %s, %s)",
                name, loc, price
            )
        except Exception as error:
            self.write(f"Error: {error}")
        else:
            self.write(f"Inserted ID: {new_id}")

To retrieve single records, use get, which returns a Row-like dictionary or None. Use query for multiple results returning a list of Rows.

class ViewProductHandler(RequestHandler):
    def get(self):
        pid = self.get_argument("id")
        try:
            record = self.application.db_pool.get(
                "SELECT * FROM products WHERE id=%s", pid
            )
            if record:
                print(record.item_name)
                print(record["item_name"])
                self.render("display.html", item=record)
            else:
                self.set_status(404)
                self.write("Not found")
        except Exception as e:
            self.write(f"Query failed: {e}")

Security Mechanisms

Cookie Handling

Tornado extends standard HTTP cookies by providing methods to manage session data on the client side.

Basic Operations

Standard cookies can be set with optional expiration paths:

class CookieDemoHandler(RequestHandler):
    def get(self):
        # Set a simple cookie
        self.set_cookie("session_token", "abc123")
        
        # Set an expired cookie relative to days
        self.set_cookie("pref_theme", "dark", expires_days=7)
        
        # Read value back
        token = self.get_cookie("session_token")
        self.write(f"Token retrieved: {token}")

Clearing a cookie involves setting its value to empty and adjusting the expiration date immediately to the past. The browser then removes it.

def clear_tokens(self):
    self.clear_cookie("session_token")
    self.clear_all_cookies()

Secure Cookies

Since clients store cookies, they are vulnerable to tampering. Tornado mitigates this by cryptographically signing values. A secret key (cookie_secret) is required in the application configuration.

app = tornado.web.Application(
    handlers=[...],
    cookie_secret="a-random-secret-key-generated-for-production"
)

Use set_secure_cookie to sign data and get_secure_cookie to verify it.

class CounterHandler(RequestHandler):
    def get(self):
        count_val = self.get_secure_cookie("visits")
        count = int(count_val) + 1 if count_val else 1
        self.set_secure_cookie("visits", str(count), expires_days=1)
        self.write(f"Total views: {count}")

The payload includes a timestamp and signature. Do not store sensitive PII in secure cookies; they prevent modification but do not encrypt data against network sniffing.

CSRF Protection

Cross-Site Request Forgery (CSRF) allows atackers to trick authenticated users into submitting unwanted requests. Tornado enforces Same-Origin Policy requirements for state-changing actions via tokens.

Configuration

Enable protection globally:

application = tornado.web.Application([
    (r"/", PageHandler)
], xsrf_cookies=True)

This rejects POST, PUT, DELETE requests lacking a valid token argument.

Template Usage

When rendering HTML forms, inject the token automatically:

<form method="post">
  {% module xsrf_form_html() %}
  <input name="data" type="text">
  <button>Submit</button>
</form>

This renders a hidden input field matching the _xsrf cookie value.

Non-Template Contexts

For AJAX requests or manual handling, retrieve the token manually:

function getXSRFToken() {
    var x = document.cookie.split('; ').filter(row => row.startsWith('_xsrf='));
    return x[0] ? x[0].split('=')[1] : null;
}

// Send via Header for JSON payloads
$.ajax({
    url: '/api/process',
    headers: {"X-XSRFToken": getXSRFToken()},
    method: 'POST'
});

Alternatively, append _xsrf to the form body parameters for application/x-www-form-urlencoded requests.

Access Control

Authentication ensures only authorized users access specific resources.

Decorator Enforcement

Apply @authenticated to restrict handler access. If the user is unverified, GET requests redirect to a login URL.

class DashboardHandler(RequestHandler):
    @tornado.web.authenticated
    def get(self):
        self.write("Welcome to your dashboard.")

Custom Logic

Override get_current_user() to define verification logic. Return the user identity or None.

def get_current_user(self):
    token = self.get_secure_cookie("auth_token")
    if token:
        return decode_token(token)
    return None

Redirection Settings

Configure where users land upon failing authentication check.

application = tornado.web.Application([
    (r"/dashboard", DashboardHandler),
    (r"/login", LoginView)
], login_url="/login")

Pass a next parameter during redirection to allow return navigation after successful login.

Posted on Sun, 12 Jul 2026 16:58:07 +0000 by melsi