请求与响应
Tornado 的 RequestHandler 封装了完整的 HTTP 请求/响应生命周期。本文详细介绍如何读取请求参数(查询字符串、请求体、路由参数)、构造各种响应(普通文本、JSON、重定向、错误)、操作 Cookie 以及提供静态文件。
请求对象
每个 Handler 实例都通过 self.request 访问当前请求,其类型为 tornado.httputil.HTTPServerRequest:
| 属性 | 说明 |
|---|---|
self.request.method | HTTP 方法(GET、POST 等) |
self.request.uri | 完整请求路径,含查询字符串 |
self.request.path | 不含查询字符串的路径 |
self.request.query | 查询字符串部分(a=1&b=2) |
self.request.headers | 请求头字典 |
self.request.body | 请求体字节串 |
self.request.remote_ip | 客户端 IP |
self.request.files | 上传文件字典(multipart/form-data) |
self.request.arguments | 所有请求参数的原始字典 |
获取请求参数
查询字符串参数
get_argument 和 get_arguments 同时处理查询字符串(?key=value)和表单体(application/x-www-form-urlencoded):
class SearchHandler(tornado.web.RequestHandler):
def get(self):
# 获取单个参数,参数不存在时返回 default
keyword = self.get_argument("q", default="")
page = self.get_argument("page", default="1")
# 获取同名多个参数(如 ?tag=python&tag=web),返回列表
tags = self.get_arguments("tag") # 不存在时返回空列表
self.write(f"搜索: {keyword}, 页码: {page}, 标签: {tags}")若参数必填且不提供默认值,参数缺失时会抛出 400 Bad Request:
user_id = self.get_argument("id") # 缺失时抛出 MissingArgumentError路由参数
路由正则中的捕获组会作为位置参数传入 HTTP 方法:
# 位置参数:按捕获顺序传入
app = Application([
(r"/article/(\d+)/(\w+)", ArticleHandler),
])
class ArticleHandler(tornado.web.RequestHandler):
def get(self, article_id, slug):
self.write(f"文章 ID: {article_id}, Slug: {slug}")命名捕获组((?P<name>pattern))同样按位置传入,但可读性更强:
app = Application([
(r"/user/(?P<user_id>\d+)", UserHandler),
])
class UserHandler(tornado.web.RequestHandler):
def get(self, user_id): # 参数名需与捕获组名一致
self.write(f"用户 ID: {user_id}")请求体参数
POST 表单体(application/x-www-form-urlencoded):
class LoginHandler(tornado.web.RequestHandler):
def post(self):
username = self.get_body_argument("username", default="")
password = self.get_body_argument("password", default="")JSON 请求体(application/json)需手动解析:
import json
class ApiHandler(tornado.web.RequestHandler):
def post(self):
data = json.loads(self.request.body)
name = data.get("name")
self.write({"received": name})实践中常在 prepare() 中统一解析 JSON,详见视图进阶与路由进阶。
构造响应
write 与 finish
write(chunk) 将内容写入输出缓冲区,接受字符串或字节串;传入字典时自动序列化为 JSON 并设置 Content-Type: application/json:
self.write("纯文本响应")
self.write(b"<html>...</html>")
self.write({"code": 0, "message": "ok"}) # 自动 JSONfinish() 显式结束请求并冲刷缓冲区。未手动调用时,Tornado 在 Handler 方法返回后自动调用。
设置响应头
self.set_header("Content-Type", "application/json; charset=UTF-8")
self.add_header("X-Custom", "value") # 添加同名头(不覆盖)
self.clear_header("X-Powered-By") # 删除指定头批量设置默认响应头,可重写 set_default_headers():
class BaseHandler(tornado.web.RequestHandler):
def set_default_headers(self):
self.set_header("Access-Control-Allow-Origin", "*")
self.set_header("Content-Type", "application/json; charset=UTF-8")状态码与错误响应
# 设置状态码
self.set_status(201)
# 发送错误(调用 write_error 并终止请求)
self.send_error(404, reason="Not Found")
# 自定义错误页(覆盖 write_error)
def write_error(self, status_code, **kwargs):
self.set_header("Content-Type", "application/json")
self.write({"error": status_code, "message": self._reason})重定向
self.redirect("/new-path") # 302 临时重定向
self.redirect("/new-path", permanent=True) # 301 永久重定向Cookie 操作
普通 Cookie
# 设置 Cookie
self.set_cookie("username", "alice")
self.set_cookie(
"session",
"abc123",
expires_days=7, # 有效期天数
httponly=True, # 禁止 JS 读取
secure=True, # 仅 HTTPS 传输
samesite="Lax", # SameSite 策略
)
# 读取 Cookie(不存在时返回 None 或 default)
username = self.get_cookie("username", default="")
# 删除单个 Cookie
self.clear_cookie("username")
# 清空所有 Cookie
self.clear_all_cookies()加密 Cookie
加密 Cookie 使用 HMAC 签名防止客户端篡改,需在 Application 设置中配置 cookie_secret:
# settings 中配置密钥
settings = {"cookie_secret": "随机长字符串,生产环境从环境变量读取"}
# 写入加密 Cookie
self.set_secure_cookie("user_id", "42")
# 读取加密 Cookie(签名校验失败时返回 None)
user_id = self.get_secure_cookie("user_id")
if user_id:
user_id = user_id.decode() # 返回 bytes,需要 decodecookie_secret 是安全敏感配置,不要硬编码在源码中。使用 os.environ.get("COOKIE_SECRET") 从环境变量读取,并在部署时注入。静态文件
在 settings 中配置 static_path,Tornado 会自动处理以 /static/ 开头的 URL:
settings = {
"static_path": "static", # 静态文件根目录
"static_url_prefix": "/static/", # URL 前缀,默认即 /static/
}目录结构示例:
project/
├── app.py
└── static/
├── css/
│ └── main.css
└── js/
└── app.js在模板中使用 static_url() 生成带版本哈希的 URL(可破坏缓存):
<link rel="stylesheet" href="{{ static_url('css/main.css') }}">
<script src="{{ static_url('js/app.js') }}"></script>模板渲染
self.render() 加载模板文件并渲染为 HTML 响应:
class IndexHandler(tornado.web.RequestHandler):
def get(self):
self.render(
"index.html",
title="首页",
items=["Python", "Tornado", "异步"],
)模板需位于 settings["template_path"] 目录下,模板语法详见模板引擎。