Introduction to Tornado
Tornado is a Python web framework and asynchronous networking library originally developed at FriendFeed (and later acquired by Facebook). Unlike traditional web frameworks that rely on a thread-per-request model, Tornado uses a single-threaded, non-blocking I/O loop to handle thousands of concurrent connections. This makes it particularly well-suited for applications that require long-lived connections, such as real-time chat systems, streaming services, and APIs that aggregate data from multiple sources.
At its core, Tornado is not just a web framework — it is a complete networking toolkit. It includes its own HTTP server, HTTP client, WebSocket support, a template engine, an authentication system, and utilities for coroutines. This self-contained nature means you can build production-grade applications without pulling in a dozen third-party dependencies.
Why Tornado Matters
To understand why Tornado matters, you need to understand the limitations of the traditional WSGI model. Frameworks like Flask and Django are built on WSGI, which assumes a synchronous request-response cycle. Each incoming request occupies a worker thread until the response is sent. If your application needs to wait on a slow database query, an external API call, or a long-running computation, that thread is blocked and unavailable for other requests.
Tornado takes a fundamentally different approach. It runs a single event loop (based on epoll on Linux, kqueue on BSD/macOS, or select as a fallback) that monitors all open connections for activity. When a connection has data ready to be read or written, the loop dispatches the appropriate handler. While one handler is waiting for I/O, the loop can service other connections. This model allows a single Python process to handle tens of thousands of simultaneous connections with minimal memory overhead.
Key Use Cases
- Real-time applications: Chat servers, live dashboards, and collaborative tools that rely on WebSockets or long polling.
- Streaming APIs: Services that push data to clients over time rather than returning a single response.
- API gateways and proxies: Applications that fan out requests to multiple backends and aggregate results asynchronously.
- Long-polling applications: Systems where clients hold connections open waiting for server-side events.
Getting Started: Installation and Setup
Tornado requires Python 3.8 or later. Installation is straightforward using pip:
pip install tornado
You can verify the installation by checking the version:
python -c "import tornado; print(tornado.version)"
There are no mandatory additional dependencies for basic usage. If you plan to use Tornado with a specific database or caching layer, you will need to install the appropriate async drivers separately (for example, motor for MongoDB or asyncpg for PostgreSQL).
Your First Tornado Application
Let us build a minimal Tornado application that responds to HTTP requests. This example demonstrates the fundamental building blocks: an application instance, a request handler, and the I/O loop.
import tornado.ioloop
import tornado.web
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, Tornado!")
def make_app():
return tornado.web.Application([
(r"/", MainHandler),
])
if __name__ == "__main__":
app = make_app()
app.listen(8888)
print("Server running on http://localhost:8888")
tornado.ioloop.IOLoop.current().start()
Save this file as server.py and run it with python server.py. When you visit http://localhost:8888 in your browser, you will see the response "Hello, Tornado!".
Let us break down what is happening here:
MainHandlersubclassesRequestHandlerand defines agetmethod that handles HTTP GET requests.self.write()sends the response body. Tornado automatically sets theContent-Typeheader based on the type of the argument.Applicationtakes a list of URL route tuples. Each tuple maps a regular expression to a handler class.app.listen(8888)starts an HTTP server on port 8888.IOLoop.current().start()starts the event loop, which runs until the process is terminated.
Request Handlers and Routing
Routing in Tornado is based on regular expressions. Each route is a tuple of (pattern, HandlerClass) or (pattern, HandlerClass, kwargs). The pattern is matched against the URL path, and any captured groups are passed as arguments to the handler method.
URL Parameters and Path Matching
import tornado.ioloop
import tornado.web
class UserHandler(tornado.web.RequestHandler):
def get(self, user_id):
self.write(f"Profile for user {user_id}")
class ArticleHandler(tornado.web.RequestHandler):
def get(self, slug):
self.write(f"Viewing article: {slug}")
def make_app():
return tornado.web.Application([
(r"/users/(\d+)", UserHandler),
(r"/articles/([a-z0-9-]+)", ArticleHandler),
])
if __name__ == "__main__":
app = make_app()
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
In this example, /users/42 matches UserHandler and passes "42" as user_id. Similarly, /articles/my-first-post matches ArticleHandler with "my-first-post" as slug. Named groups can also be used for clarity:
(r"/users/(?P<user_id>\d+)", UserHandler)
Named URL Specifiers
Tornado also supports URLSpec objects, which allow you to give routes names for reverse URL generation:
from tornado.web import Application, RequestHandler, url
class UserHandler(RequestHandler):
def get(self, user_id):
profile_url = self.reverse_url("user_profile", user_id)
self.write(f"User {user_id}, profile at {profile_url}")
def make_app():
return Application([
url(r"/users/(\d+)", UserHandler, name="user_profile"),
])
Handling Different HTTP Methods
A single handler can support multiple HTTP methods by defining the corresponding methods:
import tornado.ioloop
import tornado.web
import json
class TaskHandler(tornado.web.RequestHandler):
tasks = []
def get(self):
self.set_header("Content-Type", "application/json")
self.write(json.dumps(self.tasks))
def post(self):
body = json.loads(self.request.body)
self.tasks.append(body)
self.set_status(201)
self.write(json.dumps({"message": "Task created", "task": body}))
def delete(self, task_id):
idx = int(task_id)
if 0 <= idx < len(self.tasks):
removed = self.tasks.pop(idx)
self.write(json.dumps({"message": "Task deleted", "task": removed}))
else:
self.set_status(404)
self.write(json.dumps({"error": "Task not found"}))
def make_app():
return tornado.web.Application([
(r"/tasks/?", TaskHandler),
(r"/tasks/(\d+)", TaskHandler),
])
if __name__ == "__main__":
app = make_app()
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
Accessing Request Data
The self.request object provides access to all incoming request data:
class DebugHandler(tornado.web.RequestHandler):
def get(self):
info = {
"method": self.request.method,
"uri": self.request.uri,
"path": self.request.path,
"query": self.request.query,
"headers": dict(self.request.headers),
"remote_ip": self.request.remote_ip,
"body": self.request.body.decode("utf-8"),
}
self.set_header("Content-Type", "application/json")
self.write(json.dumps(info, indent=2))
For query parameters and form data, use the get_argument and get_arguments methods:
class SearchHandler(tornado.web.RequestHandler):
def get(self):
query = self.get_argument("q", default="")
page = int(self.get_argument("page", default="1"))
tags = self.get_arguments("tag") # Returns a list for repeated params
self.write(f"Searching for '{query}', page {page}, tags: {tags}")
Asynchronous and Non-Blocking I/O
This is where Tornado truly shines. The framework's async capabilities allow you to handle I/O-bound operations without blocking the event loop. Tornado supports both coroutines (using async/await) and callback-based patterns, though coroutines are the modern and recommended approach.
Using async/await
Since Python 3.5, the async and await keywords provide a clean syntax for writing asynchronous code. Tornado fully supports this syntax in request handlers:
import tornado.ioloop
import tornado.web
import asyncio
import json
class AsyncHandler(tornado.web.RequestHandler):
async def get(self):
# Simulate a slow operation (e.g., a database query or API call)
result = await self.fetch_data()
self.set_header("Content-Type", "application/json")
self.write(json.dumps({"data": result}))
async def fetch_data(self):
await asyncio.sleep(2) # Simulate network latency
return {"items": [1, 2, 3], "count": 3}
def make_app():
return tornado.web.Application([
(r"/async", AsyncHandler),
])
if __name__ == "__main__":
app = make_app()
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
While fetch_data is sleeping for two seconds, the event loop is free to handle other requests. This is the key difference from a synchronous framework — the server remains responsive even when individual requests take time.
Making Async HTTP Requests
Tornado includes a built-in asynchronous HTTP client. This is useful when your application needs to call external APIs:
import tornado.ioloop
import tornado.web
import tornado.httpclient
import json
class WeatherHandler(tornado.web.RequestHandler):
async def get(self):
city = self.get_argument("city", default="London")
http_client = tornado.httpclient.AsyncHTTPClient()
try:
# Example using a hypothetical weather API
url = f"https://wttr.in/{city}?format=j1"
response = await http_client.fetch(url)
data = json.loads(response.body)
current = data.get("current_condition", [{}])[0]
self.set_header("Content-Type", "application/json")
self.write(json.dumps({
"city": city,
"temp_c": current.get("temp_C"),
"humidity": current.get("humidity"),
"description": current.get("weatherDesc", [{}])[0].get("value"),
}))
except tornado.httpclient.HTTPError as e:
self.set_status(e.code)
self.write(json.dumps({"error": str(e)}))
except Exception as e:
self.set_status(500)
self.write(json.dumps({"error": "Internal server error"}))
def make_app():
return tornado.web.Application([
(r"/weather", WeatherHandler),
])
if __name__ == "__main__":
app = make_app()
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
Concurrent Operations with asyncio.gather
One of the most powerful patterns in async programming is running multiple operations concurrently. If your handler needs data from three different APIs, you do not have to wait for them sequentially:
import tornado.ioloop
import tornado.web
import tornado.httpclient
import asyncio
import json
class DashboardHandler(tornado.web.RequestHandler):
async def get(self):
http_client = tornado.httpclient.AsyncHTTPClient()
# Launch all requests concurrently
results = await asyncio.gather(
self.fetch_json(http_client, "https://api.example.com/users"),
self.fetch_json(http_client, "https://api.example.com/orders"),
self.fetch_json(http_client, "https://api.example.com/metrics"),
return_exceptions=True
)
response = {}
labels = ["users", "orders", "metrics"]
for label, result in zip(labels, results):
if isinstance(result, Exception):
response[label] = {"error": str(result)}
else:
response[label] = result
self.set_header("Content-Type", "application/json")
self.write(json.dumps(response))
async def fetch_json(self, client, url):
response = await client.fetch(url)
return json.loads(response.body)
def make_app():
return tornado.web.Application([
(r"/dashboard", DashboardHandler),
])
if __name__ == "__main__":
app = make_app()
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
If each API takes one second to respond, the total time is approximately one second rather than three. This is a dramatic improvement for I/O-bound workloads.
Templates and Rendering
Tornado includes a lightweight but capable template engine. Templates are compiled to Python code and can contain expressions and control flow statements.
Basic Template Usage
First, create a directory called templates in your project. Then create a file called index.html:
<!DOCTYPE html>
<html>
<head>
<title>{{ title }}</title>
</head>
<body>
<h1>{{ title }}</h1>
<ul>
{% for item in items %}
<li>{{ item }}</li>
{% end %}
</ul>
</body>
</html>
Now configure the application to use the template directory and render it from a handler:
import tornado.ioloop
import tornado.web
class IndexHandler(tornado.web.RequestHandler):
def get(self):
self.render(
"index.html",
title="My Tornado App",
items=["Apple", "Banana", "Cherry"]
)
def make_app():
return tornado.web.Application(
[
(r"/", IndexHandler),
],
template_path="templates",
)
if __name__ == "__main__":
app = make_app()
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
Template Syntax Reference
Tornado templates support several constructs:
{{ expression }}— Evaluates an expression and inserts the result (auto-escaped for HTML).{% raw expression %}— Inserts without HTML escaping.{% if condition %}...{% elif %}...{% else %}...{% end %}— Conditional blocks.{% for item in collection %}...{% end %}— Loop blocks.{% set var = value %}— Variable assignment.{% include "partial.html" %}— Include another template.{% block name %}...{% end %}— Define blocks for inheritance.{% extends "base.html" %}— Inherit from a base template.
Template Inheritance Example
Create base.html:
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}Default Title{% end %}</title>
</head>
<body>
<nav>
<a href="/">Home</a> |
<a href="/about">About</a>
</nav>
<main>
{% block content %}{% end %}
</main>
<footer>© 2024 My App</footer>
</body>
</html>
Create about.html that extends it:
{% extends "base.html" %}
{% block title %}About Us{% end %}
{% block content %}
<h1>About Us</h1>
<p>We build things with Tornado.</p>
{% end %}
Render it from a handler:
class AboutHandler(tornado.web.RequestHandler):
def get(self):
self.render("about.html")
Static Files
Tornado can serve static files directly. Configure the static_path setting and optionally static_url_prefix:
import tornado.ioloop
import tornado.web
import os
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.render("index.html", title="Home")
def make_app():
base_dir = os.path.dirname(__file__)
return tornado.web.Application(
[
(r"/", MainHandler),
],
template_path=os.path.join(base_dir, "templates"),
static_path=os.path.join(base_dir, "static"),
static_url_prefix="/static/",
debug=True,
)
if __name__ == "__main__":
app = make_app()
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
In your templates, use static_url() to generate URLs for static assets. This automatically appends a cache-busting version hash:
<link rel="stylesheet" href="{{ static_url('css/style.css') }}">
<script src="{{ static_url('js/app.js') }}"></script>
WebSockets with Tornado
WebSockets are one of Tornado's strongest features. They enable bidirectional, real-time communication between the client and server over a single persistent connection. Tornado provides a WebSocketHandler class that makes implementing WebSocket servers straightforward.
A Real-Time Chat Server
Here is a complete WebSocket-based chat server that broadcasts messages to all connected clients:
import tornado.ioloop
import tornado.web
import tornado.websocket
import json
from datetime import datetime
class ChatWebSocketHandler(tornado.websocket.WebSocketHandler):
# Class-level set to track all connected clients
clients = set()
def open(self):
ChatWebSocketHandler.clients.add(self)
print(f"Client connected. Total: {len(ChatWebSocketHandler.clients)}")
self.write_message(json.dumps({
"type": "system",
"message": "Welcome to the chat!",
"timestamp": datetime.now().isoformat()
}))
# Notify others
ChatWebSocketHandler.broadcast({
"type": "system",
"message": f"A new user joined. {len(ChatWebSocketHandler.clients)} online.",
"timestamp": datetime.now().isoformat()
}, exclude=self)
def on_message(self, message):
try:
data = json.loads(message)
except json.JSONDecodeError:
data = {"text": message}
broadcast_msg = {
"type": "message",
"user": data.get("user", "Anonymous"),
"text": data.get("text", ""),
"timestamp": datetime.now().isoformat()
}
ChatWebSocketHandler.broadcast(broadcast_msg)
def on_close(self):
ChatWebSocketHandler.clients.discard(self)
print(f"Client disconnected. Total: {len(ChatWebSocketHandler.clients)}")
ChatWebSocketHandler.broadcast({
"type": "system",
"message": f"A user left. {len(ChatWebSocketHandler.clients)} online.",
"timestamp": datetime.now().isoformat()
})
def check_origin(self, origin):
# Allow connections from any origin (adjust for production)
return True
@classmethod
def broadcast(cls, message, exclude=None):
dead_clients = set()
for client in cls.clients:
if client is exclude:
continue
try:
client.write_message(json.dumps(message))
except Exception:
dead_clients.add(client)
# Clean up any dead connections
cls.clients -= dead_clients
def make_app():
return tornado.web.Application([
(r"/chat", ChatWebSocketHandler),
])
if __name__ == "__main__":
app = make_app()
app.listen(8888)
print("Chat server running on ws://localhost:8888/chat")
tornado.ioloop.IOLoop.current().start()
A Simple WebSocket Client
Here is an HTML page that connects to the chat server. Save it in your templates directory:
<!DOCTYPE html>
<html>
<head>
<title>Tornado Chat</title>
<style>
body { font-family: sans-serif; max-width: 600px; margin: 40px auto; }
#messages { border: 1px solid #ccc; height: 300px; overflow-y: auto;
padding: 10px; margin-bottom: 10px; }
.system { color: gray; font-style: italic; }
.message { margin: 4px 0; }
input { padding: 8px; margin: 4px; }
button { padding: 8px 16px; }
</style>
</head>
<body>
<h1>Tornado Chat</h1>
<div id="messages"></div>
<input id="user" placeholder="Your name" style="width: 120px;">
<input id="text" placeholder="Type a message..." style="width: 300px;">
<button onclick="sendMessage()">Send</button>
<script>
const ws = new WebSocket("ws://localhost:8888/chat");
const messagesDiv = document.getElementById("messages");
ws.onmessage = function(event) {
const data = JSON.parse(event.data);
const div = document.createElement("div");
div.className = data.type === "system" ? "system" : "message";
if (data.type === "system") {
div.textContent = data.message;
} else {
div.textContent = `${data.user}: ${data.text}`;
}
messagesDiv.appendChild(div);
messagesDiv.scrollTop = messagesDiv.scrollHeight;
};
function sendMessage() {
const user = document.getElementById("user").value || "Anonymous";
const text = document.getElementById("text").value;
if (!text) return;
ws.send(JSON.stringify({ user: user, text: text }));
document.getElementById("text").value = "";
}
document.getElementById("text").addEventListener("keypress", function(e) {
if (e.key === "Enter") sendMessage();
});
</script>
</body>
</html>
Error Handling and Status Codes
Tornado provides several methods for controlling HTTP responses. You can set status codes, custom headers, and handle errors gracefully:
import tornado.ioloop
import tornado.web
import json
class ApiHandler(tornado.web.RequestHandler):
def set_default_headers(self):
self.set_header("Content-Type", "application/json")
self.set_header("Access-Control-Allow-Origin", "*")
def write_error(self, status_code, **kwargs):
error_info = {
"error": self._reason,
"status_code": status_code,
}
# Include exception details in debug mode
if "exc_info" in kwargs and self.settings.get("debug"):
import traceback
error_info["traceback"] = traceback.format_exception(*kwargs["exc_info"])
self.write(json.dumps(error_info))
async def get(self):
item_id = self.get_argument("id", default=None)
if not item_id:
raise tornado.web.HTTPError(400, reason="Missing 'id' parameter")
# Simulate a database lookup
items = {"1": {"name": "Widget", "price": 9.99},
"2": {"name": "Gadget", "price": 19.99}}
if item_id not in items:
raise tornado.web.HTTPError(404, reason=f"Item {item_id} not found")
self.write(json.dumps({"item": items[item_id]}))
def options(self):
self.set_status(204)
self.finish()
def make_app():
return tornado.web.Application([
(r"/api/items", ApiHandler),
], debug=True)
if __name__ == "__main__":
app = make_app()
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
Application Configuration and Settings
The Application constructor accepts a settings dictionary as keyword arguments. Here are the most commonly used settings:
import tornado.ioloop
import tornado.web
import os
settings = {
"debug": True, # Auto-reload on code changes
"template_path": "templates", # Path to template directory
"static_path": "static", # Path to static files
"static_url_prefix": "/static/", # URL prefix for static files
"cookie_secret": "your-secret-key-here", # Secret for signed cookies
"xsrf_cookies": True, # Enable XSRF protection
"login_url": "/login", # Redirect target for @authenticated
"autoescape": "xhtml_escape", # Default auto-escaping for templates
"compress_response": True, # Enable gzip compression
}
def make_app():
return tornado.web.Application([
# routes here
], **settings)
Authentication and Cookies
Tornado provides built-in support for secure cookies and a simple authentication pattern. Here is a complete example showing login, logout, and protected routes:
import tornado.ioloop
import tornado.web
import hashlib
import time
# Mock user database
USERS = {
"admin": hashlib.sha256("password123".encode()).hexdigest(),
}
class BaseHandler(tornado.web.RequestHandler):
def get_current_user(self):
user_id = self.get_secure_cookie("user")
if user_id:
return user_id.decode("utf-8")
return None
class LoginHandler(tornado.web.RequestHandler):
def get(self):
self.write('<form method="post">'
'<input name="username" placeholder="Username">'
'<input name="password" type="password" placeholder="Password">'
'<button type="submit">Login</button>'
'</form>')
def post(self):
username = self.get_argument("username")
password = self.get_argument("password")
hashed = hashlib.sha256(password.encode()).hexdigest()
if username in USERS and USERS[username] == hashed:
self.set_secure_cookie("user", username, expires_days=1)
self.redirect("/dashboard")
else:
self.set_status(401)
self.write("Invalid credentials")
class LogoutHandler(tornado.web.RequestHandler):
def post(self):
self.clear_cookie("user")
self.redirect("/login")
class DashboardHandler(BaseHandler):
@tornado.web.authenticated
def get(self):
user = self.get_current_user()
self.write(f"<h1>Welcome, {user}!</h1>"
f"<form method='post' action='/logout'>"
f"<button type='submit'>Logout</button>"
f"</form>")
def make_app():
return tornado.web.Application(
[
(r"/login", LoginHandler),
(r"/logout", LogoutHandler),
(r"/dashboard", DashboardHandler),
],
cookie_secret="change-this-to-a-random-string-in-production",
login_url="/login",
)
if __name__ == "__main__":
app = make_app()
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
The @tornado.web.authenticated decorator checks get_current_user() and redirects to login_url if the user is not authenticated. The set_secure_cookie method signs cookies using cookie_secret, preventing tampering.
Periodic Callbacks and Background Tasks
Tornado's PeriodicCallback lets you run tasks on a schedule within the event loop. This is useful for polling, cleanup, or pushing updates to connected clients:
import tornado.ioloop
import tornado.web
import tornado.websocket
import json
from datetime import datetime
class LiveClockHandler(tornado.websocket.WebSocketHandler):
clients = set()
def open(self):
LiveClockHandler.clients.add(self)
self.send_time()
def on_close(self):
LiveClockHandler.clients.discard(self)
def check_origin(self, origin):
return True
@classmethod
def send_time(cls):
msg = json.dumps({
"time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"clients": len(cls.clients)
})
for client in cls.clients:
try:
client.write_message(msg)
except Exception:
cls.clients.discard(client)
def make_app():
return tornado.web.Application([
(r"/clock", LiveClockHandler),
])
async def main():
app = make_app()
app.listen(8888)
# Send time updates every second
callback = tornado.ioloop.PeriodicCallback(
LiveClockHandler.send_time,
1000 # milliseconds
)
callback.start()
await asyncio.Event().wait() # Run forever
import asyncio
if __name__ == "__main__":
asyncio.run(main())
Best Practices
1. Never Block the Event Loop
The single most important rule in Tornado development is to never perform blocking operations inside request handlers. If you call time.sleep(5) or use a synchronous database driver like psycopg2 without proper offloading, the entire server stalls for all clients during that operation. Always use async libraries (motor, asyncpg, aiomysql, aiohttp) or offload CPU-bound work to a thread pool:
import asyncio
import tornado.web
class CpuBoundHandler(tornado.web.RequestHandler):
async def get(self):
loop = asyncio.get_event_loop()
# Offload blocking work to a thread pool
result = await loop.run_in_executor(None, self.heavy_computation, 42)
self.write(f"Result: {result}")
def heavy_computation(self, n):
import time
time.sleep(3) # Simulate CPU-bound work
return n * n
2. Use Application-Level Settings for Configuration
Keep configuration in the application settings dictionary rather than hardcoding values in handlers. This makes it easy to swap configurations between development and production:
settings = {
"debug": os.environ.get("ENV") == "development",
"cookie_secret": os.environ.get("COOKIE_SECRET", "dev-secret"),
"static_path": os.path.join(os.path.dirname(__file__), "static"),
}
3. Structure Your Application with Multiple Modules
For anything beyond a simple script, split your application into modules. A typical structure looks like this:
myapp/
├── app.py # Application factory and entry point
├── handlers/
│ ├── __init__.py
│ ├── main.py # Home page, about, etc.
│ ├── api.py # REST API handlers
│ └── websocket.py # WebSocket handlers
├── services/
│ ├── __init__.py
│ ├── database.py # Database connection pool
│ └── cache.py # Redis/cache integration
├── templates/
│ ├── base.html
│ └── index.html
├── static/
│ ├── css/
│ └── js/
└── requirements.txt
4. Handle Exceptions Gracefully
Override write_error in a base handler class to ensure consistent error responses across your API. Always catch expected exceptions and convert them to appropriate HTTP status codes rather than letting them produce 500 errors.
5. Use Connection Pooling for Databases
Create database connection pools at application startup, not per-request. Share the pool across handlers via the application object:
import tornado.ioloop
import tornado.web
import asyncpg
class DatabaseMixin:
@property
def db(self):
return self.application.db_pool
class UserHandler(tornado.web.RequestHandler, DatabaseMixin):
async def get(self, user_id):
record = await self.db.fetchrow(
"SELECT id, name, email FROM users WHERE id = $1",
int(user_id)
)
if record is None:
raise tornado.web.HTTPError(404)
self.write(dict(record))
async def main():
pool = await asyncpg.create_pool(
host="localhost",
database="myapp",
user="postgres",
password="secret",
min_size=5,
max_size=20
)
app = tornado.web.Application([
(r"/users/(\d+)", UserHandler),
])
app.db_pool = pool # Attach pool to application
app.listen(8888)
await asyncio.Event().wait()
import asyncio
if __name__ == "__main__":
asyncio.run(main())
6. Enable Gzip Compression
Set compress_response=True in your application settings to automatically compress responses for clients that support it. This reduces bandwidth usage significantly for text-heavy responses.
7. Run Multiple Processes in Production
A single Tornado process uses one CPU core. For production, run multiple processes behind a reverse proxy like Nginx. Tornado can manage this for you using tornado.process.fork_processes, or you can use a process manager like Supervisor or systemd:
— Ad —
Google AdSense will appear here after approval