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开发中的重要概念:
- IOLoop事件循环 - 深度理解Tornado的核心机制
- 协程和生成器 - 异步编程的不同模式
- 模板系统 - 服务端渲染和模板函数
- 安全特性 - XSRF保护、认证和授权
- 配置管理 - 生产环境的配置策略
- 定时任务 - 后台任务和周期性操作
相关资源
-
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应用。

浙公网安备 33010602011771号