Tornado异步Web框架精讲

Tornado异步Web框架精讲

概述

Tornado是一个Python异步网络库和Web框架,最初由FriendFeed开发。它专为处理大量并发连接而设计,特别适合长轮询、WebSocket和其他需要与每个用户保持长期连接的应用程序。

核心特性

  • 异步非阻塞I/O:基于事件循环的异步处理
  • WebSocket支持:原生支持WebSocket协议
  • 高性能:单进程处理数千个并发连接
  • 简洁API:直观的请求处理器设计
  • 内置HTTP服务器:无需额外的WSGI服务器

基础架构和概念

异步请求处理器

import tornado.ioloop
import tornado.web
import tornado.httpclient
import asyncio
import json
from datetime import datetime

class BaseHandler(tornado.web.RequestHandler):
    """基础处理器"""
    
    def set_default_headers(self):
        """设置默认响应头"""
        self.set_header("Access-Control-Allow-Origin", "*")
        self.set_header("Access-Control-Allow-Headers", "Content-Type, Authorization")
        self.set_header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
    
    def options(self):
        """处理预检请求"""
        self.set_status(204)
        self.finish()
    
    def write_json(self, data):
        """写入JSON响应"""
        self.set_header("Content-Type", "application/json")
        self.write(json.dumps(data, ensure_ascii=False, default=str))

class MainHandler(BaseHandler):
    """主页处理器"""
    
    async def get(self):
        """异步GET请求处理"""
        # 模拟异步数据库查询
        await asyncio.sleep(0.1)
        
        data = {
            "message": "Hello, Tornado!",
            "timestamp": datetime.now(),
            "server": "Tornado Async Server"
        }
        
        self.write_json(data)

class DataHandler(BaseHandler):
    """数据处理器"""
    
    async def get(self):
        """获取外部数据"""
        http_client = tornado.httpclient.AsyncHTTPClient()
        
        try:
            # 并发请求多个API
            responses = await asyncio.gather(
                http_client.fetch("https://httpbin.org/json"),
                http_client.fetch("https://httpbin.org/uuid"),
                return_exceptions=True
            )
            
            results = []
            for i, response in enumerate(responses):
                if isinstance(response, Exception):
                    results.append({"error": str(response)})
                else:
                    results.append(json.loads(response.body))
            
            self.write_json({
                "status": "success",
                "data": results,
                "concurrent_requests": len(responses)
            })
            
        except Exception as e:
            self.set_status(500)
            self.write_json({"error": str(e)})
        finally:
            http_client.close()
    
    async def post(self):
        """处理POST请求"""
        try:
            # 解析JSON数据
            data = json.loads(self.request.body)
            
            # 模拟异步处理
            await asyncio.sleep(0.2)
            
            # 数据验证
            required_fields = ['name', 'email']
            for field in required_fields:
                if field not in data:
                    self.set_status(400)
                    self.write_json({"error": f"Missing field: {field}"})
                    return
            
            # 模拟保存到数据库
            result = {
                "id": 12345,
                "name": data['name'],
                "email": data['email'],
                "created_at": datetime.now(),
                "status": "created"
            }
            
            self.set_status(201)
            self.write_json(result)
            
        except json.JSONDecodeError:
            self.set_status(400)
            self.write_json({"error": "Invalid JSON"})
        except Exception as e:
            self.set_status(500)
            self.write_json({"error": str(e)})

WebSocket实时通信

import tornado.websocket
import json
import uuid
from collections import defaultdict

class WebSocketManager:
    """WebSocket连接管理器"""
    
    def __init__(self):
        self.connections = {}  # 存储所有连接
        self.rooms = defaultdict(set)  # 房间管理
        self.user_rooms = defaultdict(set)  # 用户房间映射
    
    def add_connection(self, connection_id, websocket):
        """添加连接"""
        self.connections[connection_id] = websocket
    
    def remove_connection(self, connection_id):
        """移除连接"""
        if connection_id in self.connections:
            # 从所有房间中移除
            rooms_to_leave = list(self.user_rooms[connection_id])
            for room in rooms_to_leave:
                self.leave_room(connection_id, room)
            
            del self.connections[connection_id]
            del self.user_rooms[connection_id]
    
    def join_room(self, connection_id, room):
        """加入房间"""
        self.rooms[room].add(connection_id)
        self.user_rooms[connection_id].add(room)
    
    def leave_room(self, connection_id, room):
        """离开房间"""
        self.rooms[room].discard(connection_id)
        self.user_rooms[connection_id].discard(room)
        
        # 如果房间为空,删除房间
        if not self.rooms[room]:
            del self.rooms[room]
    
    def broadcast_to_room(self, room, message, exclude=None):
        """向房间广播消息"""
        if room not in self.rooms:
            return
        
        for connection_id in self.rooms[room]:
            if exclude and connection_id == exclude:
                continue
            
            websocket = self.connections.get(connection_id)
            if websocket:
                try:
                    websocket.write_message(message)
                except Exception as e:
                    print(f"Error sending message to {connection_id}: {e}")
    
    def send_to_connection(self, connection_id, message):
        """发送消息给特定连接"""
        websocket = self.connections.get(connection_id)
        if websocket:
            try:
                websocket.write_message(message)
                return True
            except Exception as e:
                print(f"Error sending message to {connection_id}: {e}")
        return False

# 全局WebSocket管理器
ws_manager = WebSocketManager()

class ChatWebSocketHandler(tornado.websocket.WebSocketHandler):
    """聊天WebSocket处理器"""
    
    def check_origin(self, origin):
        """检查跨域请求"""
        return True  # 生产环境中应该更严格
    
    def open(self):
        """连接建立"""
        self.connection_id = str(uuid.uuid4())
        self.current_room = None
        
        # 添加到管理器
        ws_manager.add_connection(self.connection_id, self)
        
        print(f"WebSocket连接建立: {self.connection_id}")
        
        # 发送欢迎消息
        self.write_message(json.dumps({
            "type": "welcome",
            "connection_id": self.connection_id,
            "message": "连接成功建立"
        }))
    
    def on_message(self, message):
        """接收消息"""
        try:
            data = json.loads(message)
            message_type = data.get('type')
            
            if message_type == 'join_room':
                self.handle_join_room(data)
            elif message_type == 'leave_room':
                self.handle_leave_room(data)
            elif message_type == 'chat_message':
                self.handle_chat_message(data)
            elif message_type == 'private_message':
                self.handle_private_message(data)
            elif message_type == 'ping':
                self.handle_ping()
            else:
                self.send_error("Unknown message type")
                
        except json.JSONDecodeError:
            self.send_error("Invalid JSON format")
        except Exception as e:
            self.send_error(f"Error processing message: {str(e)}")
    
    def handle_join_room(self, data):
        """处理加入房间"""
        room = data.get('room')
        username = data.get('username', 'Anonymous')
        
        if not room:
            self.send_error("Room name is required")
            return
        
        # 离开当前房间
        if self.current_room:
            ws_manager.leave_room(self.connection_id, self.current_room)
            ws_manager.broadcast_to_room(self.current_room, json.dumps({
                "type": "user_left",
                "username": getattr(self, 'username', 'Anonymous'),
                "room": self.current_room
            }), exclude=self.connection_id)
        
        # 加入新房间
        self.current_room = room
        self.username = username
        ws_manager.join_room(self.connection_id, room)
        
        # 通知房间内其他用户
        ws_manager.broadcast_to_room(room, json.dumps({
            "type": "user_joined",
            "username": username,
            "room": room
        }), exclude=self.connection_id)
        
        # 确认加入成功
        self.write_message(json.dumps({
            "type": "room_joined",
            "room": room,
            "username": username
        }))
    
    def handle_leave_room(self, data):
        """处理离开房间"""
        if self.current_room:
            ws_manager.leave_room(self.connection_id, self.current_room)
            ws_manager.broadcast_to_room(self.current_room, json.dumps({
                "type": "user_left",
                "username": getattr(self, 'username', 'Anonymous'),
                "room": self.current_room
            }), exclude=self.connection_id)
            
            self.current_room = None
    
    def handle_chat_message(self, data):
        """处理聊天消息"""
        if not self.current_room:
            self.send_error("Not in any room")
            return
        
        message_content = data.get('message', '').strip()
        if not message_content:
            self.send_error("Message cannot be empty")
            return
        
        # 广播消息到房间
        broadcast_data = {
            "type": "chat_message",
            "username": getattr(self, 'username', 'Anonymous'),
            "message": message_content,
            "room": self.current_room,
            "timestamp": datetime.now().isoformat()
        }
        
        ws_manager.broadcast_to_room(self.current_room, json.dumps(broadcast_data))
    
    def handle_private_message(self, data):
        """处理私聊消息"""
        target_connection = data.get('target_connection')
        message_content = data.get('message', '').strip()
        
        if not target_connection or not message_content:
            self.send_error("Target connection and message are required")
            return
        
        # 发送私聊消息
        private_data = {
            "type": "private_message",
            "from": getattr(self, 'username', 'Anonymous'),
            "message": message_content,
            "timestamp": datetime.now().isoformat()
        }
        
        success = ws_manager.send_to_connection(target_connection, json.dumps(private_data))
        
        if success:
            self.write_message(json.dumps({
                "type": "private_message_sent",
                "to": target_connection,
                "message": message_content
            }))
        else:
            self.send_error("Failed to send private message")
    
    def handle_ping(self):
        """处理心跳"""
        self.write_message(json.dumps({
            "type": "pong",
            "timestamp": datetime.now().isoformat()
        }))
    
    def send_error(self, error_message):
        """发送错误消息"""
        self.write_message(json.dumps({
            "type": "error",
            "message": error_message
        }))
    
    def on_close(self):
        """连接关闭"""
        print(f"WebSocket连接关闭: {self.connection_id}")
        
        # 离开当前房间
        if self.current_room:
            ws_manager.broadcast_to_room(self.current_room, json.dumps({
                "type": "user_left",
                "username": getattr(self, 'username', 'Anonymous'),
                "room": self.current_room
            }), exclude=self.connection_id)
        
        # 从管理器中移除
        ws_manager.remove_connection(self.connection_id)

长轮询实现

import asyncio
from tornado.concurrent import Future

class LongPollingHandler(BaseHandler):
    """长轮询处理器"""
    
    # 存储等待的请求
    waiting_requests = []
    
    async def get(self):
        """长轮询GET请求"""
        timeout = int(self.get_argument('timeout', 30))  # 默认30秒超时
        last_id = self.get_argument('last_id', '0')
        
        # 检查是否有新数据
        new_data = await self.check_for_new_data(last_id)
        
        if new_data:
            # 有新数据,立即返回
            self.write_json({
                "status": "success",
                "data": new_data,
                "has_more": True
            })
        else:
            # 没有新数据,等待
            future = Future()
            
            # 添加到等待列表
            self.waiting_requests.append({
                'future': future,
                'handler': self,
                'last_id': last_id,
                'start_time': asyncio.get_event_loop().time()
            })
            
            try:
                # 等待新数据或超时
                result = await asyncio.wait_for(future, timeout=timeout)
                self.write_json({
                    "status": "success",
                    "data": result,
                    "has_more": True
                })
            except asyncio.TimeoutError:
                # 超时,返回空结果
                self.write_json({
                    "status": "timeout",
                    "data": [],
                    "has_more": False
                })
            finally:
                # 从等待列表中移除
                self.waiting_requests = [
                    req for req in self.waiting_requests 
                    if req['handler'] != self
                ]
    
    async def check_for_new_data(self, last_id):
        """检查新数据"""
        # 模拟数据检查
        await asyncio.sleep(0.1)
        
        # 这里应该查询数据库或其他数据源
        # 返回ID大于last_id的数据
        mock_data = [
            {"id": "1", "message": "New message 1", "timestamp": datetime.now()},
            {"id": "2", "message": "New message 2", "timestamp": datetime.now()},
        ]
        
        return [item for item in mock_data if item['id'] > last_id]
    
    @classmethod
    def notify_waiting_requests(cls, new_data):
        """通知等待的请求"""
        for request_info in cls.waiting_requests[:]:  # 复制列表避免修改时出错
            future = request_info['future']
            last_id = request_info['last_id']
            
            # 检查数据是否符合条件
            relevant_data = [
                item for item in new_data 
                if item.get('id', '0') > last_id
            ]
            
            if relevant_data and not future.done():
                future.set_result(relevant_data)

class DataPushHandler(BaseHandler):
    """数据推送处理器(用于触发长轮询)"""
    
    async def post(self):
        """推送新数据"""
        try:
            data = json.loads(self.request.body)
            
            # 模拟新数据
            new_data = [{
                "id": str(int(datetime.now().timestamp())),
                "message": data.get('message', 'New data'),
                "timestamp": datetime.now()
            }]
            
            # 通知所有等待的长轮询请求
            LongPollingHandler.notify_waiting_requests(new_data)
            
            self.write_json({
                "status": "success",
                "message": "Data pushed successfully",
                "notified_requests": len(LongPollingHandler.waiting_requests)
            })
            
        except Exception as e:
            self.set_status(500)
            self.write_json({"error": str(e)})

应用配置和启动

import tornado.options
from tornado.options import define, options

# 定义命令行选项
define("port", default=8888, help="运行端口", type=int)
define("debug", default=False, help="调试模式", type=bool)
define("autoreload", default=False, help="自动重载", type=bool)

class Application(tornado.web.Application):
    """自定义应用类"""
    
    def __init__(self):
        handlers = [
            (r"/", MainHandler),
            (r"/api/data", DataHandler),
            (r"/api/push", DataPushHandler),
            (r"/api/longpoll", LongPollingHandler),
            (r"/ws/chat", ChatWebSocketHandler),
            (r"/static/(.*)", tornado.web.StaticFileHandler, {"path": "static"}),
        ]
        
        settings = {
            "debug": options.debug,
            "autoreload": options.autoreload,
            "static_path": "static",
            "template_path": "templates",
            "cookie_secret": "your-secret-key-here",
            "xsrf_cookies": False,  # 根据需要启用CSRF保护
        }
        
        super().__init__(handlers, **settings)

def make_app():
    """创建应用"""
    return Application()

async def main():
    """主函数"""
    # 解析命令行参数
    tornado.options.parse_command_line()
    
    # 创建应用
    app = make_app()
    
    # 启动HTTP服务器
    app.listen(options.port)
    print(f"Tornado服务器启动在端口 {options.port}")
    print(f"访问 http://localhost:{options.port}")
    
    # 启动事件循环
    await asyncio.Event().wait()

if __name__ == "__main__":
    asyncio.run(main())

Tornado的核心优势在于其异步非阻塞的架构设计,这使得它能够在单个进程中处理数千个并发连接。通过事件循环和协程,Tornado可以高效地处理I/O密集型任务,特别适合需要实时通信的应用场景。

Tornado核心概念补充

IOLoop事件循环深度解析

import tornado.ioloop
import tornado.gen
from tornado.concurrent import Future
import asyncio
import time

class IOLoopManager:
    """IOLoop管理器"""
    
    def __init__(self):
        self.ioloop = tornado.ioloop.IOLoop.current()
        self.callbacks = []
        self.periodic_callbacks = []
    
    def add_callback(self, callback, *args, **kwargs):
        """添加回调到事件循环"""
        self.ioloop.add_callback(callback, *args, **kwargs)
    
    def call_later(self, delay, callback, *args, **kwargs):
        """延迟执行回调"""
        handle = self.ioloop.call_later(delay, callback, *args, **kwargs)
        self.callbacks.append(handle)
        return handle
    
    def add_periodic_callback(self, callback, callback_time):
        """添加周期性回调"""
        periodic = tornado.ioloop.PeriodicCallback(callback, callback_time)
        self.periodic_callbacks.append(periodic)
        periodic.start()
        return periodic
    
    def cleanup(self):
        """清理所有回调"""
        for handle in self.callbacks:
            self.ioloop.remove_timeout(handle)
        
        for periodic in self.periodic_callbacks:
            periodic.stop()

# 全局IOLoop管理器
ioloop_manager = IOLoopManager()

class AsyncTaskHandler(tornado.web.RequestHandler):
    """异步任务处理器"""
    
    async def get(self):
        """演示各种异步操作"""
        task_type = self.get_argument('type', 'basic')
        
        if task_type == 'basic':
            result = await self.basic_async_task()
        elif task_type == 'concurrent':
            result = await self.concurrent_tasks()
        elif task_type == 'timeout':
            result = await self.task_with_timeout()
        elif task_type == 'callback':
            result = await self.callback_to_future()
        else:
            result = {"error": "Unknown task type"}
        
        self.write(result)
    
    async def basic_async_task(self):
        """基础异步任务"""
        await asyncio.sleep(1)
        return {
            "message": "基础异步任务完成",
            "timestamp": time.time()
        }
    
    async def concurrent_tasks(self):
        """并发任务执行"""
        async def task(task_id, delay):
            await asyncio.sleep(delay)
            return f"任务{task_id}完成"
        
        # 并发执行多个任务
        tasks = [
            task(1, 0.5),
            task(2, 1.0),
            task(3, 0.3)
        ]
        
        results = await asyncio.gather(*tasks)
        
        return {
            "message": "并发任务完成",
            "results": results,
            "total_tasks": len(tasks)
        }
    
    async def task_with_timeout(self):
        """带超时的任务"""
        try:
            # 设置2秒超时
            result = await asyncio.wait_for(
                self.slow_task(), 
                timeout=2.0
            )
            return {"message": "任务完成", "result": result}
        except asyncio.TimeoutError:
            return {"error": "任务超时"}
    
    async def slow_task(self):
        """慢任务"""
        await asyncio.sleep(3)  # 模拟耗时操作
        return "慢任务结果"
    
    async def callback_to_future(self):
        """回调转Future"""
        future = Future()
        
        def callback_function():
            future.set_result("回调执行完成")
        
        # 1秒后执行回调
        ioloop_manager.call_later(1.0, callback_function)
        
        result = await future
        return {"message": result}

# 定时任务示例
class ScheduledTaskManager:
    """定时任务管理器"""
    
    def __init__(self):
        self.tasks = {}
        self.task_counter = 0
    
    def start_heartbeat(self):
        """启动心跳任务"""
        def heartbeat():
            print(f"心跳检测 - {time.strftime('%Y-%m-%d %H:%M:%S')}")
        
        # 每30秒执行一次心跳
        periodic = ioloop_manager.add_periodic_callback(heartbeat, 30000)
        self.tasks['heartbeat'] = periodic
    
    def start_cleanup_task(self):
        """启动清理任务"""
        def cleanup():
            print("执行清理任务...")
            # 这里可以添加清理逻辑
            # 如清理过期会话、临时文件等
        
        # 每小时执行一次清理
        periodic = ioloop_manager.add_periodic_callback(cleanup, 3600000)
        self.tasks['cleanup'] = periodic
    
    def stop_all_tasks(self):
        """停止所有定时任务"""
        for task_name, periodic in self.tasks.items():
            periodic.stop()
            print(f"定时任务已停止: {task_name}")

# 全局定时任务管理器
scheduled_manager = ScheduledTaskManager()

协程和生成器模式

import tornado.gen
from tornado.concurrent import Future

class CoroutineHandler(tornado.web.RequestHandler):
    """协程处理器示例"""
    
    @tornado.gen.coroutine
    def get(self):
        """使用tornado.gen.coroutine装饰器"""
        # 这是Tornado传统的协程写法
        result = yield self.async_operation()
        self.write({"result": result})
    
    @tornado.gen.coroutine
    def async_operation(self):
        """异步操作"""
        # 模拟异步I/O操作
        future = Future()
        
        def complete_operation():
            future.set_result("操作完成")
        
        # 1秒后完成操作
        tornado.ioloop.IOLoop.current().call_later(1.0, complete_operation)
        
        result = yield future
        raise tornado.gen.Return(result)  # Tornado 5.0之前的返回方式

class ModernAsyncHandler(tornado.web.RequestHandler):
    """现代异步处理器"""
    
    async def get(self):
        """使用现代async/await语法"""
        operation_type = self.get_argument('type', 'simple')
        
        if operation_type == 'simple':
            result = await self.simple_async_operation()
        elif operation_type == 'chain':
            result = await self.chained_operations()
        elif operation_type == 'error':
            result = await self.error_handling_example()
        
        self.write(result)
    
    async def simple_async_operation(self):
        """简单异步操作"""
        await asyncio.sleep(0.5)
        return {"message": "简单异步操作完成"}
    
    async def chained_operations(self):
        """链式异步操作"""
        # 第一步
        step1_result = await self.step_one()
        
        # 第二步(依赖第一步的结果)
        step2_result = await self.step_two(step1_result)
        
        # 第三步
        step3_result = await self.step_three(step2_result)
        
        return {
            "message": "链式操作完成",
            "steps": [step1_result, step2_result, step3_result]
        }
    
    async def step_one(self):
        await asyncio.sleep(0.2)
        return "步骤1完成"
    
    async def step_two(self, previous_result):
        await asyncio.sleep(0.3)
        return f"步骤2完成,基于:{previous_result}"
    
    async def step_three(self, previous_result):
        await asyncio.sleep(0.1)
        return f"步骤3完成,基于:{previous_result}"
    
    async def error_handling_example(self):
        """错误处理示例"""
        try:
            # 模拟可能失败的操作
            await self.risky_operation()
            return {"message": "操作成功"}
        except ValueError as e:
            return {"error": f"值错误: {str(e)}"}
        except Exception as e:
            return {"error": f"未知错误: {str(e)}"}
    
    async def risky_operation(self):
        """可能失败的操作"""
        await asyncio.sleep(0.1)
        import random
        if random.random() < 0.5:
            raise ValueError("随机失败")
        return "操作成功"

Tornado模板系统

import tornado.template
import os

class TemplateHandler(tornado.web.RequestHandler):
    """模板处理器"""
    
    def get(self):
        """渲染模板"""
        template_name = self.get_argument('template', 'index.html')
        
        # 模板数据
        data = {
            'title': 'Tornado模板示例',
            'user': {
                'name': 'John Doe',
                'email': 'john@example.com',
                'is_admin': True
            },
            'items': [
                {'id': 1, 'name': '项目1', 'price': 100},
                {'id': 2, 'name': '项目2', 'price': 200},
                {'id': 3, 'name': '项目3', 'price': 300},
            ],
            'current_time': datetime.now(),
            'settings': {
                'debug': True,
                'version': '1.0.0'
            }
        }
        
        self.render(template_name, **data)

# 自定义模板函数
def format_currency(value):
    """格式化货币"""
    return f"¥{value:.2f}"

def truncate_text(text, length=50):
    """截断文本"""
    if len(text) <= length:
        return text
    return text[:length] + "..."

# 在应用中注册模板函数
class Application(tornado.web.Application):
    def __init__(self):
        handlers = [
            (r"/", MainHandler),
            (r"/template", TemplateHandler),
            (r"/async", AsyncTaskHandler),
            (r"/modern", ModernAsyncHandler),
            (r"/ws/chat", ChatWebSocketHandler),
        ]
        
        settings = {
            "template_path": "templates",
            "static_path": "static",
            "debug": True,
            "autoreload": True,
            # 注册模板函数
            "ui_methods": {
                "format_currency": format_currency,
                "truncate_text": truncate_text
            }
        }
        
        super().__init__(handlers, **settings)

Tornado安全特性

import hashlib
import hmac
import time
import jwt
from tornado.web import RequestHandler

class SecureHandler(RequestHandler):
    """安全处理器基类"""
    
    def check_xsrf_cookie(self):
        """XSRF保护"""
        if not self.settings.get("xsrf_cookies"):
            return
        
        token = self.get_argument("_xsrf", None)
        if not token:
            token = self.request.headers.get("X-Xsrftoken")
        
        if not token:
            raise tornado.web.HTTPError(403, "XSRF cookie missing")
        
        expected_token = self.xsrf_token
        if not self._check_xsrf_token(token, expected_token):
            raise tornado.web.HTTPError(403, "XSRF token mismatch")
    
    def _check_xsrf_token(self, token, expected):
        """检查XSRF令牌"""
        return hmac.compare_digest(token, expected)
    
    def get_current_user(self):
        """获取当前用户"""
        # 从cookie或header中获取用户信息
        auth_header = self.request.headers.get("Authorization")
        if auth_header and auth_header.startswith("Bearer "):
            token = auth_header[7:]
            try:
                payload = jwt.decode(token, "secret-key", algorithms=["HS256"])
                return payload.get("user_id")
            except jwt.InvalidTokenError:
                return None
        
        return self.get_secure_cookie("user_id")
    
    def set_secure_cookie_custom(self, name, value, expires_days=30):
        """设置安全cookie"""
        timestamp = str(int(time.time()))
        value_with_timestamp = f"{value}|{timestamp}"
        
        # 生成签名
        signature = hmac.new(
            self.settings["cookie_secret"].encode(),
            value_with_timestamp.encode(),
            hashlib.sha256
        ).hexdigest()
        
        signed_value = f"{value_with_timestamp}|{signature}"
        
        self.set_cookie(
            name,
            signed_value,
            expires_days=expires_days,
            httponly=True,
            secure=True  # 仅HTTPS
        )

class AuthHandler(SecureHandler):
    """认证处理器"""
    
    def post(self):
        """用户登录"""
        username = self.get_argument("username")
        password = self.get_argument("password")
        
        # 验证用户(简化版本)
        if self.verify_user(username, password):
            # 生成JWT令牌
            payload = {
                "user_id": username,
                "exp": time.time() + 3600  # 1小时过期
            }
            token = jwt.encode(payload, "secret-key", algorithm="HS256")
            
            # 设置安全cookie
            self.set_secure_cookie_custom("user_id", username)
            
            self.write({
                "success": True,
                "token": token,
                "message": "登录成功"
            })
        else:
            self.set_status(401)
            self.write({
                "success": False,
                "message": "用户名或密码错误"
            })
    
    def verify_user(self, username, password):
        """验证用户凭据"""
        # 这里应该查询数据库
        # 简化版本:硬编码用户
        users = {
            "admin": "admin123",
            "user": "user123"
        }
        
        return users.get(username) == password

class ProtectedHandler(SecureHandler):
    """受保护的处理器"""
    
    @tornado.web.authenticated
    def get(self):
        """需要认证的端点"""
        current_user = self.get_current_user()
        self.write({
            "message": f"欢迎,{current_user}",
            "protected_data": "这是受保护的数据"
        })

应用启动和配置管理

import tornado.options
from tornado.options import define, options
import logging

# 定义命令行选项
define("port", default=8888, help="运行端口", type=int)
define("debug", default=False, help="调试模式", type=bool)
define("config", default="config.py", help="配置文件路径", type=str)

class ConfigManager:
    """配置管理器"""
    
    def __init__(self, config_file=None):
        self.config = {}
        if config_file:
            self.load_config(config_file)
    
    def load_config(self, config_file):
        """加载配置文件"""
        try:
            with open(config_file, 'r') as f:
                exec(f.read(), self.config)
            print(f"配置文件已加载: {config_file}")
        except FileNotFoundError:
            print(f"配置文件不存在: {config_file}")
        except Exception as e:
            print(f"加载配置文件失败: {e}")
    
    def get(self, key, default=None):
        """获取配置值"""
        return self.config.get(key, default)

def setup_logging():
    """设置日志"""
    logging.basicConfig(
        level=logging.INFO,
        format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
        handlers=[
            logging.FileHandler('tornado.log'),
            logging.StreamHandler()
        ]
    )

async def main():
    """主函数"""
    # 解析命令行参数
    tornado.options.parse_command_line()
    
    # 设置日志
    setup_logging()
    
    # 加载配置
    config_manager = ConfigManager(options.config)
    
    # 创建应用
    app = Application()
    
    # 启动定时任务
    scheduled_manager.start_heartbeat()
    scheduled_manager.start_cleanup_task()
    
    # 启动HTTP服务器
    app.listen(options.port)
    logging.info(f"Tornado服务器启动在端口 {options.port}")
    
    try:
        # 启动事件循环
        await asyncio.Event().wait()
    except KeyboardInterrupt:
        logging.info("收到中断信号,正在关闭服务器...")
    finally:
        # 清理资源
        scheduled_manager.stop_all_tasks()
        ioloop_manager.cleanup()
        logging.info("服务器已关闭")

if __name__ == "__main__":
    asyncio.run(main())

这些补充内容涵盖了Tornado开发中的重要概念:

  1. IOLoop事件循环 - 深度理解Tornado的核心机制
  2. 协程和生成器 - 异步编程的不同模式
  3. 模板系统 - 服务端渲染和模板函数
  4. 安全特性 - XSRF保护、认证和授权
  5. 配置管理 - 生产环境的配置策略
  6. 定时任务 - 后台任务和周期性操作

相关资源

  • Tornado官方文档

  • Tornado GitHub仓库

  • Python异步编程指南

  • Tornado最佳实践clas
    s ConfigManager:
    """配置管理器"""

    def init(self):
    self.config = {}
    self.env = os.getenv('TORNADO_ENV', 'development')

    def load_config(self, config_file=None):
    """加载配置文件"""
    if config_file and os.path.exists(config_file):
    # 动态导入配置模块
    import importlib.util
    spec = importlib.util.spec_from_file_location("config", config_file)
    config_module = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(config_module)

          # 获取配置类
          config_class = getattr(config_module, f'{self.env.title()}Config', None)
          if config_class:
              for attr in dir(config_class):
                  if not attr.startswith('_'):
                      self.config[attr] = getattr(config_class, attr)
      
      # 从环境变量覆盖配置
      self.load_from_env()
    

    def load_from_env(self):
    """从环境变量加载配置"""
    env_mappings = {
    'TORNADO_DEBUG': ('debug', bool),
    'TORNADO_PORT': ('port', int),
    'TORNADO_SECRET_KEY': ('cookie_secret', str),
    'DATABASE_URL': ('database_url', str),
    'REDIS_URL': ('redis_url', str),
    }

      for env_key, (config_key, type_func) in env_mappings.items():
          value = os.getenv(env_key)
          if value:
              if type_func == bool:
                  self.config[config_key] = value.lower() in ('true', '1', 'yes')
              else:
                  self.config[config_key] = type_func(value)
    

    def get(self, key, default=None):
    """获取配置值"""
    return self.config.get(key, default)

全局配置管理器

config_manager = ConfigManager()

配置类示例

class BaseConfig:
"""基础配置"""
DEBUG = False
TESTING = False
SECRET_KEY = 'dev-secret-key'
DATABASE_URL = 'sqlite:///app.db'
REDIS_URL = 'redis://localhost:6379/0'

# Tornado特定配置
AUTORELOAD = False
COMPILED_TEMPLATE_CACHE = True
STATIC_HASH_CACHE = True
SERVE_TRACEBACK = False

class DevelopmentConfig(BaseConfig):
"""开发环境配置"""
DEBUG = True
AUTORELOAD = True
COMPILED_TEMPLATE_CACHE = False
STATIC_HASH_CACHE = False
SERVE_TRACEBACK = True

class ProductionConfig(BaseConfig):
"""生产环境配置"""
DEBUG = False
SECRET_KEY = os.getenv('SECRET_KEY', 'production-secret-key')
DATABASE_URL = os.getenv('DATABASE_URL', 'postgresql://user:pass@localhost/db')

# 性能优化配置
COMPILED_TEMPLATE_CACHE = True
STATIC_HASH_CACHE = True
GZIP = True

class TestingConfig(BaseConfig):
"""测试环境配置"""
TESTING = True
DATABASE_URL = 'sqlite:///:memory:'

高级应用类

class AdvancedApplication(tornado.web.Application):
"""高级应用类"""

def __init__(self, config=None):
    # 加载配置
    if config:
        config_manager.load_config(config)
    
    # 定义路由
    handlers = self.get_handlers()
    
    # 应用设置
    settings = self.get_settings()
    
    super().__init__(handlers, **settings)
    
    # 初始化组件
    self.setup_database()
    self.setup_cache()
    self.setup_logging()

def get_handlers(self):
    """获取路由处理器"""
    return [
        (r"/", MainHandler),
        (r"/api/data", DataHandler),
        (r"/api/push", DataPushHandler),
        (r"/api/longpoll", LongPollingHandler),
        (r"/ws/chat", ChatWebSocketHandler),
        (r"/auth/login", AuthHandler),
        (r"/protected", ProtectedHandler),
        (r"/template", TemplateHandler),
        (r"/async", AsyncTaskHandler),
        (r"/modern", ModernAsyncHandler),
        (r"/static/(.*)", tornado.web.StaticFileHandler, {"path": "static"}),
    ]

def get_settings(self):
    """获取应用设置"""
    return {
        "debug": config_manager.get('debug', False),
        "autoreload": config_manager.get('autoreload', False),
        "static_path": "static",
        "template_path": "templates",
        "cookie_secret": config_manager.get('cookie_secret', 'default-secret'),
        "xsrf_cookies": True,
        "gzip": config_manager.get('gzip', False),
        "compiled_template_cache": config_manager.get('compiled_template_cache', True),
        "static_hash_cache": config_manager.get('static_hash_cache', True),
        "serve_traceback": config_manager.get('serve_traceback', False),
    }

def setup_database(self):
    """设置数据库"""
    database_url = config_manager.get('database_url')
    if database_url:
        # 这里可以初始化数据库连接池
        print(f"数据库连接: {database_url}")

def setup_cache(self):
    """设置缓存"""
    redis_url = config_manager.get('redis_url')
    if redis_url:
        # 这里可以初始化Redis连接
        print(f"Redis连接: {redis_url}")

def setup_logging(self):
    """设置日志"""
    if not config_manager.get('debug', False):
        # 生产环境日志配置
        logging.basicConfig(
            level=logging.INFO,
            format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
            handlers=[
                logging.FileHandler('tornado.log'),
                logging.StreamHandler()
            ]
        )

Tornado性能优化

连接池和资源管理

import aioredis
import asyncpg
from tornado.httpclient import AsyncHTTPClient
import asyncio

class ConnectionPoolManager:
    """连接池管理器"""
    
    def __init__(self):
        self.db_pool = None
        self.redis_pool = None
        self.http_client = None
    
    async def setup_database_pool(self, database_url, min_size=10, max_size=20):
        """设置数据库连接池"""
        self.db_pool = await asyncpg.create_pool(
            database_url,
            min_size=min_size,
            max_size=max_size,
            command_timeout=60
        )
        print(f"数据库连接池已创建: {min_size}-{max_size}")
    
    async def setup_redis_pool(self, redis_url, max_connections=20):
        """设置Redis连接池"""
        self.redis_pool = aioredis.ConnectionPool.from_url(
            redis_url,
            max_connections=max_connections
        )
        print(f"Redis连接池已创建: {max_connections}")
    
    def setup_http_client(self, max_clients=100):
        """设置HTTP客户端"""
        self.http_client = AsyncHTTPClient(max_clients=max_clients)
        print(f"HTTP客户端已创建: {max_clients}")
    
    async def get_db_connection(self):
        """获取数据库连接"""
        if not self.db_pool:
            raise RuntimeError("数据库连接池未初始化")
        return await self.db_pool.acquire()
    
    async def release_db_connection(self, connection):
        """释放数据库连接"""
        await self.db_pool.release(connection)
    
    async def get_redis_connection(self):
        """获取Redis连接"""
        if not self.redis_pool:
            raise RuntimeError("Redis连接池未初始化")
        return aioredis.Redis(connection_pool=self.redis_pool)
    
    async def cleanup(self):
        """清理资源"""
        if self.db_pool:
            await self.db_pool.close()
        if self.redis_pool:
            await self.redis_pool.disconnect()
        if self.http_client:
            self.http_client.close()

# 全局连接池管理器
pool_manager = ConnectionPoolManager()

class OptimizedHandler(tornado.web.RequestHandler):
    """优化的处理器基类"""
    
    async def prepare(self):
        """请求准备"""
        self.start_time = time.time()
    
    def on_finish(self):
        """请求完成"""
        duration = time.time() - self.start_time
        if duration > 1.0:  # 记录慢请求
            logging.warning(f"慢请求: {self.request.uri} - {duration:.3f}s")
    
    async def execute_db_query(self, query, *args):
        """执行数据库查询"""
        conn = await pool_manager.get_db_connection()
        try:
            result = await conn.fetch(query, *args)
            return result
        finally:
            await pool_manager.release_db_connection(conn)
    
    async def cache_get(self, key):
        """从缓存获取数据"""
        redis = await pool_manager.get_redis_connection()
        try:
            return await redis.get(key)
        finally:
            await redis.close()
    
    async def cache_set(self, key, value, expire=3600):
        """设置缓存数据"""
        redis = await pool_manager.get_redis_connection()
        try:
            await redis.setex(key, expire, value)
        finally:
            await redis.close()

class HighPerformanceHandler(OptimizedHandler):
    """高性能处理器示例"""
    
    async def get(self):
        """高性能GET处理"""
        # 1. 尝试从缓存获取
        cache_key = f"data:{self.get_argument('id', 'default')}"
        cached_data = await self.cache_get(cache_key)
        
        if cached_data:
            self.set_header("X-Cache", "HIT")
            self.write(json.loads(cached_data))
            return
        
        # 2. 从数据库获取
        data_id = self.get_argument('id', '1')
        query = "SELECT * FROM data_table WHERE id = $1"
        result = await self.execute_db_query(query, int(data_id))
        
        if result:
            data = dict(result[0])
            # 3. 存入缓存
            await self.cache_set(cache_key, json.dumps(data, default=str))
            
            self.set_header("X-Cache", "MISS")
            self.write(data)
        else:
            self.set_status(404)
            self.write({"error": "Data not found"})

### 批量操作优化

class BatchOperationHandler(OptimizedHandler):
    """批量操作处理器"""
    
    async def post(self):
        """批量创建数据"""
        try:
            data_list = json.loads(self.request.body)
            
            if not isinstance(data_list, list) or len(data_list) > 1000:
                self.set_status(400)
                self.write({"error": "Invalid data or too many items"})
                return
            
            # 批量插入数据库
            results = await self.batch_insert(data_list)
            
            # 批量更新缓存
            await self.batch_update_cache(results)
            
            self.write({
                "message": f"Successfully created {len(results)} items",
                "items": results
            })
            
        except Exception as e:
            self.set_status(500)
            self.write({"error": str(e)})
    
    async def batch_insert(self, data_list):
        """批量插入数据"""
        conn = await pool_manager.get_db_connection()
        try:
            # 使用事务批量插入
            async with conn.transaction():
                query = """
                    INSERT INTO data_table (name, value, created_at) 
                    VALUES ($1, $2, $3) 
                    RETURNING id, name, value, created_at
                """
                
                results = []
                for item in data_list:
                    result = await conn.fetchrow(
                        query,
                        item['name'],
                        item['value'],
                        datetime.now()
                    )
                    results.append(dict(result))
                
                return results
        finally:
            await pool_manager.release_db_connection(conn)
    
    async def batch_update_cache(self, results):
        """批量更新缓存"""
        redis = await pool_manager.get_redis_connection()
        try:
            # 使用pipeline批量操作
            pipe = redis.pipeline()
            
            for item in results:
                cache_key = f"data:{item['id']}"
                pipe.setex(cache_key, 3600, json.dumps(item, default=str))
            
            await pipe.execute()
        finally:
            await redis.close()

性能监控和指标

import psutil
import gc
from collections import defaultdict, deque
import threading

class PerformanceMonitor:
    """性能监控器"""
    
    def __init__(self):
        self.request_times = deque(maxlen=1000)
        self.request_counts = defaultdict(int)
        self.error_counts = defaultdict(int)
        self.active_connections = 0
        self.start_time = time.time()
        
        # 启动监控线程
        self.monitor_thread = threading.Thread(target=self._monitor_loop, daemon=True)
        self.monitor_thread.start()
    
    def record_request(self, handler, duration, status_code):
        """记录请求信息"""
        self.request_times.append(duration)
        self.request_counts[handler.__class__.__name__] += 1
        
        if status_code >= 400:
            self.error_counts[status_code] += 1
    
    def get_stats(self):
        """获取统计信息"""
        if not self.request_times:
            return {}
        
        # 计算请求时间统计
        times = list(self.request_times)
        avg_time = sum(times) / len(times)
        max_time = max(times)
        min_time = min(times)
        
        # 系统资源使用情况
        process = psutil.Process()
        memory_info = process.memory_info()
        
        return {
            'uptime': time.time() - self.start_time,
            'requests': {
                'total': sum(self.request_counts.values()),
                'by_handler': dict(self.request_counts),
                'avg_response_time': avg_time,
                'max_response_time': max_time,
                'min_response_time': min_time
            },
            'errors': dict(self.error_counts),
            'system': {
                'cpu_percent': psutil.cpu_percent(),
                'memory_rss': memory_info.rss,
                'memory_vms': memory_info.vms,
                'memory_percent': process.memory_percent(),
                'open_files': len(process.open_files()),
                'connections': len(process.connections())
            },
            'gc': {
                'collections': gc.get_stats(),
                'objects': len(gc.get_objects())
            }
        }
    
    def _monitor_loop(self):
        """监控循环"""
        while True:
            try:
                # 每分钟记录一次系统状态
                stats = self.get_stats()
                logging.info(f"系统状态: CPU {stats['system']['cpu_percent']:.1f}%, "
                           f"内存 {stats['system']['memory_percent']:.1f}%, "
                           f"请求总数 {stats['requests']['total']}")
                
                # 检查内存使用情况
                if stats['system']['memory_percent'] > 80:
                    logging.warning("内存使用率过高,建议检查内存泄漏")
                    gc.collect()  # 强制垃圾回收
                
                time.sleep(60)
            except Exception as e:
                logging.error(f"监控线程错误: {e}")

# 全局性能监控器
performance_monitor = PerformanceMonitor()

class MonitoredHandler(tornado.web.RequestHandler):
    """带监控的处理器基类"""
    
    def prepare(self):
        """请求准备"""
        self.start_time = time.time()
        performance_monitor.active_connections += 1
    
    def on_finish(self):
        """请求完成"""
        duration = time.time() - self.start_time
        performance_monitor.record_request(self, duration, self.get_status())
        performance_monitor.active_connections -= 1

class StatsHandler(MonitoredHandler):
    """统计信息处理器"""
    
    def get(self):
        """获取性能统计"""
        stats = performance_monitor.get_stats()
        self.write(stats)

# 最终的应用启动函数
async def create_optimized_app():
    """创建优化的应用"""
    # 初始化连接池
    await pool_manager.setup_database_pool(
        config_manager.get('database_url', 'postgresql://localhost/tornado_db')
    )
    await pool_manager.setup_redis_pool(
        config_manager.get('redis_url', 'redis://localhost:6379')
    )
    pool_manager.setup_http_client()
    
    # 创建应用
    app = AdvancedApplication()
    
    # 添加性能监控路由
    app.add_handlers(r".*", [
        (r"/stats", StatsHandler),
        (r"/health", HealthCheckHandler),
    ])
    
    return app

class HealthCheckHandler(MonitoredHandler):
    """健康检查处理器"""
    
    async def get(self):
        """健康检查"""
        try:
            # 检查数据库连接
            await pool_manager.get_db_connection()
            
            # 检查Redis连接
            redis = await pool_manager.get_redis_connection()
            await redis.ping()
            await redis.close()
            
            self.write({
                "status": "healthy",
                "timestamp": datetime.now().isoformat(),
                "uptime": time.time() - performance_monitor.start_time
            })
            
        except Exception as e:
            self.set_status(503)
            self.write({
                "status": "unhealthy",
                "error": str(e),
                "timestamp": datetime.now().isoformat()
            })

# 优化的主函数
async def optimized_main():
    """优化的主函数"""
    # 解析命令行参数
    tornado.options.parse_command_line()
    
    # 加载配置
    config_file = options.config if hasattr(options, 'config') else None
    config_manager.load_config(config_file)
    
    # 创建优化的应用
    app = await create_optimized_app()
    
    # 启动定时任务
    scheduled_manager.start_heartbeat()
    scheduled_manager.start_cleanup_task()
    
    # 启动HTTP服务器
    port = config_manager.get('port', options.port)
    app.listen(port)
    
    print(f"优化的Tornado服务器启动在端口 {port}")
    print(f"环境: {config_manager.env}")
    print(f"调试模式: {config_manager.get('debug', False)}")
    
    try:
        # 启动事件循环
        await asyncio.Event().wait()
    except KeyboardInterrupt:
        print("正在关闭服务器...")
    finally:
        # 清理资源
        await pool_manager.cleanup()
        scheduled_manager.stop_all_tasks()
        ioloop_manager.cleanup()

if __name__ == "__main__":
    asyncio.run(optimized_main())

最佳实践总结

1. 异步编程最佳实践

  • 使用async/await语法:现代Python异步编程的标准方式
  • 避免阻塞操作:所有I/O操作都应该是异步的
  • 合理使用并发:使用asyncio.gather()进行并发操作
  • 超时控制:为异步操作设置合理的超时时间

2. 性能优化策略

  • 连接池管理:使用连接池减少连接开销
  • 缓存策略:合理使用Redis等缓存系统
  • 批量操作:减少数据库往返次数
  • 资源监控:实时监控系统资源使用情况

3. 安全性考虑

  • XSRF保护:启用跨站请求伪造保护
  • 安全Cookie:使用HttpOnly和Secure标志
  • 输入验证:严格验证所有用户输入
  • 错误处理:不暴露敏感的错误信息

4. 可维护性

  • 配置管理:使用配置文件和环境变量
  • 日志记录:完善的日志系统
  • 错误监控:实时错误监控和告警
  • 代码组织:清晰的代码结构和模块化设计

Tornado框架特别适合构建需要处理大量并发连接的实时应用,如聊天系统、实时数据推送、长轮询服务等。通过合理的架构设计和性能优化,可以构建出高性能、高可用的Web应用。

相关资源

posted @ 2025-07-18 11:20  春水鸿鹄  阅读(32)  评论(0)    收藏  举报