Tornado – асинхронный веб-сервер

онлайн тренажер по питону
Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

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>&copy; 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. Его асинхронная природа и гибкость делают его отличным выбором для современных веб-сервисов, особенно тех, которые требуют обработки большого количества одновременных соединений или работы в реальном времени.

Новости