Tornado: Полное руководство по асинхронному веб-фреймворку Python
Что такое Tornado
Tornado — это мощный асинхронный веб-сервер и веб-фреймворк для Python, специально разработанный для обработки большого количества одновременных соединений. Созданный командой FriendFeed (позже приобретенной Facebook), Tornado стал одним из пионеров в области асинхронного программирования на Python.
Основная философия Tornado заключается в использовании неблокирующего ввода-вывода и архитектуры, основанной на событиях. Это позволяет одному процессу обслуживать тысячи соединений одновременно, что делает его идеальным выбором для создания высоконагруженных веб-приложений, чатов, систем реального времени и API-сервисов.
Ключевые особенности и преимущества Tornado
Асинхронная архитектура
Tornado использует модель событийно-ориентированного программирования, которая позволяет обрабатывать множество запросов в одном потоке. Это существенно снижает накладные расходы на переключение контекста и управление памятью.
Встроенный HTTP-сервер
В отличие от многих других фреймворков, Tornado включает собственный HTTP-сервер, что устраняет необходимость в дополнительных компонентах типа WSGI для развертывания приложений.
Поддержка WebSocket
Tornado предоставляет нативную поддержку WebSocket соединений, что делает его отличным выбором для создания интерактивных приложений реального времени.
Совместимость с asyncio
Начиная с версии 5.0, Tornado полностью совместим с asyncio, что позволяет использовать его вместе с другими асинхронными библиотеками Python.
Гибкая система маршрутизации
Tornado предоставляет мощную систему URL-маршрутизации с поддержкой регулярных выражений и параметров.
Встроенный шаблонизатор
Фреймворк включает собственный шаблонизатор для генерации HTML-страниц с динамическим содержимым.
Установка и настройка окружения
Установка Tornado
pip install tornado
Для работы с дополнительными возможностями можно установить дополнительные пакеты:
# Для работы с MongoDB
pip install motor
# Для работы с PostgreSQL
pip install asyncpg
# Для работы с Redis
pip install aioredis
Структура проекта
Рекомендуемая структура проекта Tornado:
project/
├── app.py # Основной файл приложения
├── handlers/ # Обработчики запросов
│ ├── __init__.py
│ ├── base.py
│ ├── auth.py
│ └── api.py
├── models/ # Модели данных
│ ├── __init__.py
│ └── user.py
├── templates/ # HTML-шаблоны
│ ├── base.html
│ ├── index.html
│ └── error.html
├── static/ # Статические файлы
│ ├── css/
│ ├── js/
│ └── images/
├── config/ # Конфигурационные файлы
│ ├── __init__.py
│ └── settings.py
└── requirements.txt # Зависимости проекта
Создание первого приложения
Базовый пример
import tornado.ioloop
import tornado.web
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Добро пожаловать в Tornado!")
class AboutHandler(tornado.web.RequestHandler):
def get(self):
self.write("Страница о нас")
def make_app():
return tornado.web.Application([
(r"/", MainHandler),
(r"/about", AboutHandler),
])
if __name__ == "__main__":
app = make_app()
app.listen(8888)
print("Сервер запущен на http://localhost:8888")
tornado.ioloop.IOLoop.current().start()
Настройка приложения с параметрами
import tornado.web
import tornado.ioloop
import os
class Application(tornado.web.Application):
def __init__(self):
handlers = [
(r"/", MainHandler),
(r"/api/users", UserHandler),
]
settings = {
"debug": True,
"template_path": os.path.join(os.path.dirname(__file__), "templates"),
"static_path": os.path.join(os.path.dirname(__file__), "static"),
"cookie_secret": "your-secret-key-here",
"xsrf_cookies": True,
}
super().__init__(handlers, **settings)
Система маршрутизации
Основные принципы маршрутизации
Tornado использует регулярные выражения для определения маршрутов. Каждый маршрут представляет собой кортеж из шаблона URL и класса обработчика.
# Простой маршрут
(r"/", MainHandler),
# Маршрут с параметром
(r"/user/([0-9]+)", UserHandler),
# Маршрут с именованными группами
(r"/post/(?P<slug>[-\w]+)", PostHandler),
# Маршрут с несколькими параметрами
(r"/category/([^/]+)/post/([0-9]+)", CategoryPostHandler),
Обработка параметров URL
class UserHandler(tornado.web.RequestHandler):
def get(self, user_id):
self.write(f"Профиль пользователя ID: {user_id}")
class PostHandler(tornado.web.RequestHandler):
def get(self, slug):
self.write(f"Статья: {slug}")
class CategoryPostHandler(tornado.web.RequestHandler):
def get(self, category, post_id):
self.write(f"Категория: {category}, Пост ID: {post_id}")
Обработчики запросов (Request Handlers)
Базовый класс RequestHandler
Все обработчики в Tornado наследуются от класса tornado.web.RequestHandler. Этот класс предоставляет методы для обработки HTTP-методов:
class MyHandler(tornado.web.RequestHandler):
def get(self):
# Обработка GET-запроса
self.write("GET запрос обработан")
def post(self):
# Обработка POST-запроса
name = self.get_argument("name")
self.write(f"Привет, {name}!")
def put(self):
# Обработка PUT-запроса
self.write("PUT запрос обработан")
def delete(self):
# Обработка DELETE-запроса
self.write("DELETE запрос обработан")
Работа с параметрами запроса
class FormHandler(tornado.web.RequestHandler):
def post(self):
# Получение обязательного параметра
name = self.get_argument("name")
# Получение параметра с значением по умолчанию
age = self.get_argument("age", "18")
# Получение нескольких значений
hobbies = self.get_arguments("hobbies")
# Получение всех параметров
all_args = self.request.arguments
self.write(f"Имя: {name}, Возраст: {age}, Хобби: {hobbies}")
Работа с заголовками
class HeaderHandler(tornado.web.RequestHandler):
def get(self):
# Получение заголовка
user_agent = self.request.headers.get("User-Agent")
# Установка заголовка ответа
self.set_header("X-Custom-Header", "MyValue")
# Установка нескольких заголовков
self.set_header("Cache-Control", "no-cache")
self.set_header("Content-Type", "application/json")
self.write(f"Ваш User-Agent: {user_agent}")
Асинхронное программирование в Tornado
Основы асинхронности
Tornado поддерживает асинхронное программирование с помощью async/await синтаксиса:
import asyncio
import tornado.web
from tornado.httpclient import AsyncHTTPClient
class AsyncHandler(tornado.web.RequestHandler):
async def get(self):
# Асинхронная задержка
await asyncio.sleep(1)
# Асинхронный HTTP-запрос
http_client = AsyncHTTPClient()
response = await http_client.fetch("https://api.github.com/users/octocat")
self.write(f"Получен ответ: {response.code}")
Параллельное выполнение задач
import asyncio
class ParallelHandler(tornado.web.RequestHandler):
async def get(self):
# Запуск нескольких задач параллельно
tasks = [
self.fetch_data("https://api.example1.com"),
self.fetch_data("https://api.example2.com"),
self.fetch_data("https://api.example3.com"),
]
results = await asyncio.gather(*tasks)
self.write(f"Получено {len(results)} ответов")
async def fetch_data(self, url):
http_client = AsyncHTTPClient()
response = await http_client.fetch(url)
return response.body
Работа с шаблонами
Основы шаблонизации
Tornado включает собственный шаблонизатор, который поддерживает Python-подобный синтаксис:
<!-- templates/base.html -->
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}Мой сайт{% end %}</title>
</head>
<body>
<nav>
<ul>
<li><a href="/">Главная</a></li>
<li><a href="/about">О нас</a></li>
</ul>
</nav>
<main>
{% block content %}{% end %}
</main>
<footer>
<p>© 2024 Мой сайт</p>
</footer>
</body>
</html>
<!-- templates/index.html -->
{% extends "base.html" %}
{% block title %}Главная - Мой сайт{% end %}
{% block content %}
<h1>Добро пожаловать, {{ name }}!</h1>
{% if user %}
<p>Вы вошли как {{ user.username }}</p>
{% else %}
<p><a href="/login">Войдите в систему</a></p>
{% end %}
<ul>
{% for item in items %}
<li>{{ item.title }} - {{ item.description }}</li>
{% end %}
</ul>
{% end %}
Использование шаблонов в обработчиках
class TemplateHandler(tornado.web.RequestHandler):
def get(self):
items = [
{"title": "Товар 1", "description": "Описание товара 1"},
{"title": "Товар 2", "description": "Описание товара 2"},
]
self.render("index.html",
name="Пользователь",
user={"username": "admin"},
items=items)
Пользовательские функции в шаблонах
def make_app():
return tornado.web.Application([
(r"/", MainHandler),
],
template_path="templates",
ui_methods={
"format_date": lambda handler, date: date.strftime("%d.%m.%Y"),
},
ui_modules={
"UserCard": UserCardModule,
})
Обработка форм и файлов
Работа с HTML-формами
<!-- templates/form.html -->
<form action="/submit" method="post">
{% raw xsrf_form_html() %}
<input type="text" name="name" placeholder="Имя" required>
<input type="email" name="email" placeholder="Email" required>
<textarea name="message" placeholder="Сообщение"></textarea>
<button type="submit">Отправить</button>
</form>
class FormHandler(tornado.web.RequestHandler):
def get(self):
self.render("form.html")
def post(self):
name = self.get_argument("name")
email = self.get_argument("email")
message = self.get_argument("message")
# Обработка данных формы
self.write(f"Спасибо, {name}! Ваше сообщение получено.")
Загрузка файлов
<!-- templates/upload.html -->
<form action="/upload" method="post" enctype="multipart/form-data">
{% raw xsrf_form_html() %}
<input type="file" name="file" accept=".jpg,.png,.gif" required>
<button type="submit">Загрузить</button>
</form>
import os
import uuid
class UploadHandler(tornado.web.RequestHandler):
def get(self):
self.render("upload.html")
def post(self):
if "file" not in self.request.files:
self.write("Файл не выбран")
return
file_info = self.request.files["file"][0]
filename = file_info["filename"]
file_body = file_info["body"]
# Генерация уникального имени файла
unique_filename = f"{uuid.uuid4()}_{filename}"
# Сохранение файла
upload_path = os.path.join("uploads", unique_filename)
with open(upload_path, "wb") as f:
f.write(file_body)
self.write(f"Файл {filename} успешно загружен")
WebSocket соединения
Основы WebSocket
import tornado.websocket
import json
class ChatWebSocket(tornado.websocket.WebSocketHandler):
clients = set()
def open(self):
print(f"WebSocket соединение открыто: {self.request.remote_ip}")
ChatWebSocket.clients.add(self)
self.write_message({"type": "info", "message": "Добро пожаловать в чат!"})
def on_message(self, message):
try:
data = json.loads(message)
if data["type"] == "chat":
# Рассылка сообщения всем подключенным клиентам
for client in ChatWebSocket.clients:
client.write_message({
"type": "chat",
"user": data["user"],
"message": data["message"]
})
except json.JSONDecodeError:
self.write_message({"type": "error", "message": "Неверный формат данных"})
def on_close(self):
print(f"WebSocket соединение закрыто: {self.request.remote_ip}")
ChatWebSocket.clients.discard(self)
def check_origin(self, origin):
# Проверка источника подключения
return True # В продакшене нужно ограничить
Клиентская часть WebSocket
<!-- templates/chat.html -->
<div id="messages"></div>
<input type="text" id="messageInput" placeholder="Введите сообщение...">
<button onclick="sendMessage()">Отправить</button>
<script>
const ws = new WebSocket("ws://localhost:8888/ws");
const messages = document.getElementById('messages');
const messageInput = document.getElementById('messageInput');
ws.onmessage = function(event) {
const data = JSON.parse(event.data);
const messageElement = document.createElement('div');
if (data.type === 'chat') {
messageElement.textContent = `${data.user}: ${data.message}`;
} else if (data.type === 'info') {
messageElement.textContent = data.message;
messageElement.style.color = 'green';
}
messages.appendChild(messageElement);
messages.scrollTop = messages.scrollHeight;
};
function sendMessage() {
const message = messageInput.value.trim();
if (message) {
ws.send(JSON.stringify({
type: 'chat',
user: 'Пользователь',
message: message
}));
messageInput.value = '';
}
}
messageInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
sendMessage();
}
});
</script>
Работа с базами данных
Асинхронная работа с MongoDB (Motor)
import motor.motor_tornado
from bson import ObjectId
class DatabaseHandler:
def __init__(self, db_url="mongodb://localhost:27017"):
self.client = motor.motor_tornado.MotorClient(db_url)
self.db = self.client.myapp
async def create_user(self, user_data):
result = await self.db.users.insert_one(user_data)
return str(result.inserted_id)
async def get_user(self, user_id):
return await self.db.users.find_one({"_id": ObjectId(user_id)})
async def get_all_users(self):
cursor = self.db.users.find({})
return await cursor.to_list(length=100)
# Использование в обработчике
class UserHandler(tornado.web.RequestHandler):
def initialize(self, db_handler):
self.db = db_handler
async def get(self, user_id=None):
if user_id:
user = await self.db.get_user(user_id)
if user:
user["_id"] = str(user["_id"]) # Преобразование ObjectId в строку
self.write(user)
else:
self.set_status(404)
self.write({"error": "Пользователь не найден"})
else:
users = await self.db.get_all_users()
for user in users:
user["_id"] = str(user["_id"])
self.write({"users": users})
async def post(self):
user_data = {
"name": self.get_argument("name"),
"email": self.get_argument("email"),
"created_at": datetime.utcnow()
}
user_id = await self.db.create_user(user_data)
self.write({"id": user_id, "message": "Пользователь создан"})
Работа с PostgreSQL (AsyncPG)
import asyncpg
import asyncio
class PostgresHandler:
def __init__(self, db_url="postgresql://user:password@localhost/dbname"):
self.db_url = db_url
self.pool = None
async def init_pool(self):
self.pool = await asyncpg.create_pool(self.db_url)
async def create_user(self, name, email):
async with self.pool.acquire() as conn:
query = """
INSERT INTO users (name, email, created_at)
VALUES ($1, $2, NOW())
RETURNING id
"""
return await conn.fetchval(query, name, email)
async def get_user(self, user_id):
async with self.pool.acquire() as conn:
query = "SELECT * FROM users WHERE id = $1"
return await conn.fetchrow(query, user_id)
Аутентификация и авторизация
Простая система аутентификации
import hashlib
import tornado.web
class BaseHandler(tornado.web.RequestHandler):
def get_current_user(self):
user_id = self.get_secure_cookie("user_id")
if user_id:
return self.db.get_user(user_id.decode())
return None
class LoginHandler(BaseHandler):
def get(self):
self.render("login.html", error=None)
def post(self):
username = self.get_argument("username")
password = self.get_argument("password")
# Проверка пользователя в базе данных
user = self.authenticate_user(username, password)
if user:
self.set_secure_cookie("user_id", str(user["id"]))
self.redirect("/dashboard")
else:
self.render("login.html", error="Неверные данные")
def authenticate_user(self, username, password):
# Хеширование пароля
password_hash = hashlib.sha256(password.encode()).hexdigest()
# Поиск пользователя в базе данных
return self.db.find_user(username, password_hash)
class LogoutHandler(BaseHandler):
def post(self):
self.clear_cookie("user_id")
self.redirect("/")
class DashboardHandler(BaseHandler):
@tornado.web.authenticated
def get(self):
user = self.get_current_user()
self.render("dashboard.html", user=user)
Декораторы для авторизации
import functools
def require_role(role):
def decorator(method):
@functools.wraps(method)
def wrapper(self, *args, **kwargs):
user = self.get_current_user()
if not user or user.get("role") != role:
self.set_status(403)
self.write({"error": "Доступ запрещен"})
return
return method(self, *args, **kwargs)
return wrapper
return decorator
class AdminHandler(BaseHandler):
@tornado.web.authenticated
@require_role("admin")
def get(self):
self.write("Панель администратора")
Middleware и обработка запросов
Метод prepare()
class BaseHandler(tornado.web.RequestHandler):
def prepare(self):
# Выполняется перед каждым HTTP-методом
self.set_header("X-Custom-Header", "MyApp")
# Проверка Content-Type для POST запросов
if self.request.method in ["POST", "PUT"]:
content_type = self.request.headers.get("Content-Type", "")
if not content_type.startswith("application/json"):
self.set_status(400)
self.write({"error": "Требуется Content-Type: application/json"})
self.finish()
return
def set_default_headers(self):
# Выполняется для каждого запроса
self.set_header("Access-Control-Allow-Origin", "*")
self.set_header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
self.set_header("Access-Control-Allow-Headers", "Content-Type, Authorization")
Обработка ошибок
class ErrorHandler(tornado.web.RequestHandler):
def write_error(self, status_code, **kwargs):
self.set_header("Content-Type", "application/json")
if status_code == 404:
self.write({"error": "Страница не найдена"})
elif status_code == 500:
self.write({"error": "Внутренняя ошибка сервера"})
else:
self.write({"error": f"Ошибка {status_code}"})
def options(self):
# Обработка preflight запросов для CORS
self.set_status(204)
self.finish()
Безопасность
Защита от CSRF
class SecureHandler(tornado.web.RequestHandler):
def post(self):
# Автоматическая проверка CSRF токена
# если в настройках приложения xsrf_cookies=True
name = self.get_argument("name")
self.write(f"Данные получены: {name}")
Безопасные куки
class CookieHandler(tornado.web.RequestHandler):
def post(self):
# Установка безопасной куки
self.set_secure_cookie("user_session", "session_data", expires_days=7)
# Получение безопасной куки
session_data = self.get_secure_cookie("user_session")
self.write("Сессия установлена")
Валидация данных
import re
class ValidationHandler(tornado.web.RequestHandler):
def post(self):
email = self.get_argument("email")
# Валидация email
if not self.is_valid_email(email):
self.set_status(400)
self.write({"error": "Некорректный email"})
return
# Дополнительная валидация
name = self.get_argument("name")
if len(name) < 2:
self.set_status(400)
self.write({"error": "Имя должно содержать минимум 2 символа"})
return
self.write({"message": "Данные корректны"})
def is_valid_email(self, email):
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
return re.match(pattern, email) is not None
Кеширование
Кеширование в памяти
import time
from functools import lru_cache
class CacheHandler(tornado.web.RequestHandler):
_cache = {}
async def get(self):
cache_key = "expensive_data"
# Проверка кеша
if cache_key in self._cache:
data, timestamp = self._cache[cache_key]
if time.time() - timestamp < 300: # 5 минут
self.write({"data": data, "from_cache": True})
return
# Получение данных
data = await self.get_expensive_data()
# Сохранение в кеш
self._cache[cache_key] = (data, time.time())
self.write({"data": data, "from_cache": False})
async def get_expensive_data(self):
# Имитация дорогой операции
await asyncio.sleep(2)
return {"result": "expensive computation"}
Кеширование с Redis
import aioredis
import json
class RedisHandler(tornado.web.RequestHandler):
def initialize(self, redis_pool):
self.redis = redis_pool
async def get(self):
cache_key = "user_data"
# Попытка получить из Redis
cached_data = await self.redis.get(cache_key)
if cached_data:
data = json.loads(cached_data)
self.write({"data": data, "from_cache": True})
return
# Получение данных из базы
data = await self.get_user_data()
# Сохранение в Redis на 1 час
await self.redis.setex(cache_key, 3600, json.dumps(data))
self.write({"data": data, "from_cache": False})
Тестирование Tornado приложений
Базовое тестирование
import unittest
import tornado.testing
import tornado.web
class TestHandler(tornado.web.RequestHandler):
def get(self):
self.write({"message": "Hello, World!"})
class TestApp(tornado.testing.AsyncHTTPTestCase):
def get_app(self):
return tornado.web.Application([
(r"/", TestHandler),
])
def test_homepage(self):
response = self.fetch("/")
self.assertEqual(response.code, 200)
self.assertIn(b"Hello, World!", response.body)
if __name__ == "__main__":
unittest.main()
Тестирование WebSocket
import tornado.testing
import tornado.websocket
import json
class EchoWebSocket(tornado.websocket.WebSocketHandler):
def on_message(self, message):
self.write_message(f"Echo: {message}")
class WebSocketTest(tornado.testing.AsyncHTTPTestCase):
def get_app(self):
return tornado.web.Application([
(r"/ws", EchoWebSocket),
])
@tornado.testing.gen_test
def test_websocket(self):
ws_url = "ws://localhost:" + str(self.get_http_port()) + "/ws"
ws = yield tornado.websocket.websocket_connect(ws_url)
ws.write_message("Hello")
response = yield ws.read_message()
self.assertEqual(response, "Echo: Hello")
ws.close()
Развертывание в продакшн
Настройка для продакшн
import tornado.web
import tornado.httpserver
import tornado.ioloop
import tornado.options
import os
def create_app():
settings = {
"debug": False,
"template_path": os.path.join(os.path.dirname(__file__), "templates"),
"static_path": os.path.join(os.path.dirname(__file__), "static"),
"cookie_secret": os.environ.get("COOKIE_SECRET"),
"xsrf_cookies": True,
"login_url": "/login",
}
return tornado.web.Application([
(r"/", MainHandler),
(r"/api/users", UserHandler),
(r"/ws", WebSocketHandler),
], **settings)
def main():
tornado.options.parse_command_line()
app = create_app()
# Настройка SSL для HTTPS
ssl_options = None
if os.path.exists("cert.pem") and os.path.exists("key.pem"):
ssl_options = {
"certfile": "cert.pem",
"keyfile": "key.pem",
}
server = tornado.httpserver.HTTPServer(app, ssl_options=ssl_options)
server.listen(8888)
print("Сервер запущен на порту 8888")
tornado.ioloop.IOLoop.current().start()
if __name__ == "__main__":
main()
Использование с Docker
FROM python:3.9-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8888
CMD ["python", "app.py"]
Настройка с Nginx
upstream tornado {
server 127.0.0.1:8888;
server 127.0.0.1:8889;
}
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://tornado;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /static/ {
alias /path/to/static/files/;
expires 30d;
}
location /ws {
proxy_pass http://tornado;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
Таблица методов и функций Tornado
| Класс/Модуль | Метод/Функция | Описание |
|---|---|---|
| tornado.web.RequestHandler | get() |
Обработка GET запросов |
post() |
Обработка POST запросов | |
put() |
Обработка PUT запросов | |
delete() |
Обработка DELETE запросов | |
prepare() |
Вызывается перед любым HTTP методом | |
on_finish() |
Вызывается после завершения запроса | |
write(chunk) |
Запись данных в ответ | |
render(template, **kwargs) |
Рендеринг шаблона | |
redirect(url) |
Перенаправление | |
set_status(status_code) |
Установка HTTP статуса | |
set_header(name, value) |
Установка заголовка | |
get_argument(name, default=None) |
Получение параметра запроса | |
get_arguments(name) |
Получение списка параметров | |
get_cookie(name) |
Получение cookie | |
set_cookie(name, value) |
Установка cookie | |
get_secure_cookie(name) |
Получение защищенной cookie | |
set_secure_cookie(name, value) |
Установка защищенной cookie | |
get_current_user() |
Получение текущего пользователя | |
finish() |
Завершение запроса | |
| tornado.web.Application | __init__(handlers, **settings) |
Создание приложения |
listen(port, address='') |
Запуск сервера | |
add_handlers(host_pattern, handlers) |
Добавление обработчиков | |
| tornado.websocket.WebSocketHandler | open() |
Вызывается при открытии соединения |
on_message(message) |
Обработка входящих сообщений | |
on_close() |
Вызывается при закрытии соединения | |
write_message(message) |
Отправка сообщения | |
close() |
Закрытие соединения | |
check_origin(origin) |
Проверка источника подключения | |
| tornado.ioloop.IOLoop | current() |
Получение текущего цикла событий |
start() |
Запуск цикла событий | |
stop() |
Остановка цикла событий | |
run_sync(func) |
Синхронный запуск асинхронной функции | |
call_later(delay, callback) |
Отложенный вызов функции | |
| tornado.httpclient.AsyncHTTPClient | fetch(url, **kwargs) |
Асинхронный HTTP запрос |
configure(impl) |
Настройка HTTP клиента | |
| tornado.httpserver.HTTPServer | __init__(app, **kwargs) |
Создание HTTP сервера |
listen(port, address='') |
Запуск сервера | |
bind(port, address='') |
Привязка к порту | |
start(num_processes=1) |
Запуск с несколькими процессами | |
| tornado.auth | authenticate_redirect() |
Перенаправление на OAuth |
get_authenticated_user() |
Получение данных OAuth пользователя | |
| tornado.escape | json_encode(value) |
Кодирование в JSON |
json_decode(value) |
Декодирование JSON | |
url_escape(value) |
Кодирование URL | |
url_unescape(value) |
Декодирование URL | |
xhtml_escape(value) |
Экранирование HTML | |
| tornado.options | define(name, default, type, help) |
Определение опции |
parse_command_line() |
Парсинг аргументов командной строки | |
parse_config_file(path) |
Парсинг конфигурационного файла | |
| tornado.testing | AsyncHTTPTestCase |
Базовый класс для тестов |
gen_test |
Декоратор для асинхронных тестов | |
| tornado.log | enable_pretty_logging() |
Включение красивого логирования |
access_log |
Логгер для доступа | |
app_log |
Логгер приложения | |
| tornado.template | Template(template_string) |
Создание шаблона |
Loader(root_directory) |
Загрузчик шаблонов |
Полезные декораторы и утилиты
| Декоратор/Утилита | Описание |
|---|---|
@tornado.web.authenticated |
Требует аутентификации |
@tornado.web.removeslash |
Удаляет завершающий слеш |
@tornado.web.addslash |
Добавляет завершающий слеш |
@tornado.gen.coroutine |
Устаревший декоратор для корутин |
@tornado.concurrent.run_on_executor |
Запуск в отдельном потоке |
Практические примеры использования
REST API сервер
import tornado.web
import tornado.ioloop
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", "*")
self.set_header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
self.set_header("Access-Control-Allow-Headers", "Content-Type, Authorization")
def options(self):
self.set_status(204)
self.finish()
class UsersHandler(APIHandler):
def get(self):
users = [
{"id": 1, "name": "Иван", "email": "ivan@example.com"},
{"id": 2, "name": "Мария", "email": "maria@example.com"},
]
self.write({"users": users})
def post(self):
try:
data = json.loads(self.request.body)
# Валидация данных
if not data.get("name") or not data.get("email"):
self.set_status(400)
self.write({"error": "Имя и email обязательны"})
return
# Создание пользователя
user = {
"id": 3,
"name": data["name"],
"email": data["email"]
}
self.set_status(201)
self.write({"user": user, "message": "Пользователь создан"})
except json.JSONDecodeError:
self.set_status(400)
self.write({"error": "Неверный JSON"})
def make_app():
return tornado.web.Application([
(r"/api/users", UsersHandler),
])
if __name__ == "__main__":
app = make_app()
app.listen(8888)
print("API сервер запущен на http://localhost:8888")
tornado.ioloop.IOLoop.current().start()
Чат в реальном времени
import tornado.web
import tornado.websocket
import tornado.ioloop
import json
import uuid
class ChatHandler(tornado.web.RequestHandler):
def get(self):
self.render("chat.html")
class ChatWebSocket(tornado.websocket.WebSocketHandler):
connections = {}
def open(self):
self.id = str(uuid.uuid4())
self.username = None
ChatWebSocket.connections[self.id] = self
print(f"Новое подключение: {self.id}")
def on_message(self, message):
try:
data = json.loads(message)
if data["type"] == "join":
self.username = data["username"]
self.broadcast({
"type": "info",
"message": f"{self.username} присоединился к чату"
})
elif data["type"] == "message":
if self.username:
self.broadcast({
"type": "message",
"username": self.username,
"message": data["message"],
"timestamp": data.get("timestamp")
})
except json.JSONDecodeError:
self.write_message({
"type": "error",
"message": "Неверный формат сообщения"
})
def on_close(self):
if self.id in ChatWebSocket.connections:
del ChatWebSocket.connections[self.id]
if self.username:
self.broadcast({
"type": "info",
"message": f"{self.username} покинул чат"
})
print(f"Соединение закрыто: {self.id}")
def broadcast(self, message):
"""Рассылка сообщения всем подключенным клиентам"""
for connection in ChatWebSocket.connections.values():
try:
connection.write_message(message)
except:
pass # Игнорировать ошибки отправки
def check_origin(self, origin):
return True
def make_app():
return tornado.web.Application([
(r"/", ChatHandler),
(r"/ws", ChatWebSocket),
],
template_path="templates",
static_path="static",
debug=True)
if __name__ == "__main__":
app = make_app()
app.listen(8888)
print("Чат-сервер запущен на http://localhost:8888")
tornado.ioloop.IOLoop.current().start()
Оптимизация производительности
Пулы соединений
import asyncio
import aioredis
import motor.motor_tornado
class ConnectionManager:
def __init__(self):
self.redis_pool = None
self.mongo_client = None
async def init_connections(self):
# Пул соединений Redis
self.redis_pool = aioredis.ConnectionPool.from_url(
"redis://localhost:6379",
max_connections=20
)
# MongoDB клиент
self.mongo_client = motor.motor_tornado.MotorClient(
"mongodb://localhost:27017",
maxPoolSize=50
)
async def close_connections(self):
if self.redis_pool:
await self.redis_pool.disconnect()
if self.mongo_client:
self.mongo_client.close()
# Глобальный менеджер соединений
connection_manager = ConnectionManager()
class OptimizedHandler(tornado.web.RequestHandler):
async def get(self):
# Использование пула соединений
redis = aioredis.Redis(connection_pool=connection_manager.redis_pool)
# Параллельное выполнение запросов
tasks = [
redis.get("key1"),
redis.get("key2"),
connection_manager.mongo_client.db.collection.find_one({})
]
results = await asyncio.gather(*tasks)
self.write({"results": results})
Кеширование результатов
import functools
import time
def cache_result(ttl=300):
def decorator(func):
cache = {}
@functools.wraps(func)
async def wrapper(*args, **kwargs):
# Создание ключа кеша
cache_key = f"{func.__name__}:{hash(str(args) + str(kwargs))}"
# Проверка кеша
if cache_key in cache:
data, timestamp = cache[cache_key]
if time.time() - timestamp < ttl:
return data
# Вычисление результата
result = await func(*args, **kwargs)
# Сохранение в кеш
cache[cache_key] = (result, time.time())
return result
return wrapper
return decorator
class CachedHandler(tornado.web.RequestHandler):
@cache_result(ttl=600) # Кеш на 10 минут
async def get_expensive_data(self):
# Дорогая операция
await asyncio.sleep(2)
return {"data": "expensive result"}
async def get(self):
result = await self.get_expensive_data()
self.write(result)
Мониторинг и логирование
Настройка логирования
import logging
import sys
from tornado.log import enable_pretty_logging
def setup_logging():
# Настройка корневого логгера
logging.getLogger().setLevel(logging.INFO)
# Включение красивого логирования Tornado
enable_pretty_logging()
# Создание файлового логгера
file_handler = logging.FileHandler("app.log")
file_formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
file_handler.setFormatter(file_formatter)
# Добавление обработчика к корневому логгеру
logging.getLogger().addHandler(file_handler)
# Настройка логгера для доступа
access_log = logging.getLogger("tornado.access")
access_log.setLevel(logging.INFO)
class LoggingHandler(tornado.web.RequestHandler):
def prepare(self):
# Логирование входящих запросов
logging.info(f"Запрос: {self.request.method} {self.request.uri}")
def on_finish(self):
# Логирование завершенных запросов
logging.info(f"Ответ: {self.get_status()} за {self.request.request_time():.2f}s")
Метрики производительности
import time
import psutil
import tornado.web
class MetricsHandler(tornado.web.RequestHandler):
def get(self):
# Системные метрики
cpu_percent = psutil.cpu_percent()
memory = psutil.virtual_memory()
# Метрики приложения
active_connections = len(getattr(ChatWebSocket, 'connections', {}))
metrics = {
"timestamp": time.time(),
"system": {
"cpu_percent": cpu_percent,
"memory_percent": memory.percent,
"memory_available": memory.available,
},
"application": {
"active_websockets": active_connections,
"uptime": time.time() - self.application.start_time,
}
}
self.write(metrics)
Часто задаваемые вопросы
Что такое Tornado и для чего он используется?
Tornado — это асинхронный веб-фреймворк и HTTP-сервер для Python, разработанный для обработки большого количества одновременных соединений. Он особенно эффективен для создания веб-приложений реального времени, чатов, API-сервисов и систем с высокой нагрузкой.
Чем Tornado отличается от Flask и Django?
Tornado специализируется на асинхронности и высокой производительности. В отличие от Flask и Django, которые используют синхронную модель обработки запросов, Tornado использует неблокирующий ввод-вывод и может обрабатывать тысячи соединений на одном потоке.
Поддерживает ли Tornado современный Python asyncio?
Да, начиная с версии 5.0, Tornado полностью совместим с asyncio. Это означает, что вы можете использовать стандартные async/await конструкции и интегрировать Tornado с другими асинхронными библиотеками.
Можно ли использовать Tornado для создания REST API?
Абсолютно. Tornado отлично подходит для создания REST API благодаря своей гибкости и производительности. Он предоставляет все необходимые инструменты для обработки HTTP-методов, JSON-данных и аутентификации.
Как обеспечить безопасность Tornado приложения?
Tornado предоставляет встроенные механизмы безопасности: защита от CSRF-атак, безопасные куки, поддержка HTTPS. Также важно валидировать входные данные, использовать параметризованные запросы к базе данных и правильно настроить CORS.
Подходит ли Tornado для production окружения?
Да, Tornado используется в production многими крупными компаниями. Для продакшн развертывания рекомендуется использовать его за reverse proxy (например, Nginx), настроить SSL, логирование и мониторинг.
Как масштабировать Tornado приложение?
Tornado можно масштабировать вертикально (увеличивая мощность сервера) и горизонтально (добавляя больше серверов). Для горизонтального масштабирования используйте load balancer и внешние хранилища данных (Redis, MongoDB).
Есть ли готовые решения для аутентификации?
Tornado предоставляет базовые механизмы аутентификации и поддержку OAuth. Для более сложных сценариев можно использовать сторонние библиотеки или реализовать собственную систему аутентификации.
Tornado остается мощным и актуальным инструментом для разработки высокопроизводительных веб-приложений на Python. Его асинхронная природа и гибкость делают его отличным выбором для современных веб-сервисов, особенно тех, которые требуют обработки большого количества одновременных соединений или работы в реальном времени.
Настоящее и будущее развития ИИ: классической математики уже недостаточно
Эксперты предупредили о рисках фейковой благотворительности с помощью ИИ
В России разработали универсального ИИ-агента для роботов и индустриальных процессов