跳至内容

请求与响应

Tornado 的 RequestHandler 封装了完整的 HTTP 请求/响应生命周期。本文详细介绍如何读取请求参数(查询字符串、请求体、路由参数)、构造各种响应(普通文本、JSON、重定向、错误)、操作 Cookie 以及提供静态文件。

请求对象

每个 Handler 实例都通过 self.request 访问当前请求,其类型为 tornado.httputil.HTTPServerRequest

属性说明
self.request.methodHTTP 方法(GETPOST 等)
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_argumentget_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"})  # 自动 JSON

finish() 显式结束请求并冲刷缓冲区。未手动调用时,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,需要 decode
cookie_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"] 目录下,模板语法详见模板引擎

最后更新于