模板引擎
Tornado 内置了一套轻量模板引擎,语法与 Jinja2 接近但并不完全相同。模板在首次加载时编译为 Python 代码并缓存,性能较高。本文介绍模板的配置、变量渲染、控制语句、继承机制以及内置函数的使用。
基础配置
在 Application 的 settings 中配置模板目录:
settings = {
"template_path": "templates", # 模板根目录(相对于启动脚本)
"autoescape": "xhtml_escape", # 自动转义(默认开启,防 XSS)
}目录结构示例:
project/
├── app.py
└── templates/
├── base.html
├── index.html
└── user/
└── profile.html在 Handler 中渲染模板:
class IndexHandler(tornado.web.RequestHandler):
def get(self):
self.render(
"index.html",
title="首页",
user={"name": "Alice", "age": 28},
items=["Python", "Go", "Rust"],
)模板语法
变量与表达式
使用双花括号 {{ }} 输出变量或表达式的值(输出内容会自动 HTML 转义):
<h1>{{ title }}</h1>
<p>用户:{{ user["name"] }},年龄:{{ user["age"] }}</p>
<p>标签数量:{{ len(items) }}</p>输出不需要转义的原始 HTML 时,使用 {% raw %}:
{% raw html_content %}控制语句
控制语句使用 {% %} 包裹,所有块结构以 {% end %} 结束:
<!-- if / elif / else -->
{% if user["age"] >= 18 %}
<p>成年用户</p>
{% elif user["age"] >= 12 %}
<p>青少年用户</p>
{% else %}
<p>儿童用户</p>
{% end %}
<!-- for 循环 -->
<ul>
{% for item in items %}
<li>{{ item }}</li>
{% end %}
</ul>
<!-- while 循环(较少使用) -->
{% set i = 0 %}
{% while i < 3 %}
<p>第 {{ i }} 行</p>
{% set i = i + 1 %}
{% end %}注释
{# 这是注释,不会出现在渲染结果中 #}模板继承
Tornado 模板支持与 Jinja2 类似的继承机制,通过 {% extends %} 和 {% block %} 实现布局复用。
基础模板 base.html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>{% block title %}默认标题{% end %} - 我的站点</title>
<link rel="stylesheet" href="{{ static_url('css/main.css') }}">
</head>
<body>
<nav>{% block nav %}{% end %}</nav>
<main>
{% block content %}{% end %}
</main>
<footer>Copyright 2026</footer>
</body>
</html>子模板 index.html
{% extends "base.html" %}
{% block title %}首页{% end %}
{% block content %}
<h1>欢迎,{{ user_name }}!</h1>
<ul>
{% for item in items %}
<li>{{ item }}</li>
{% end %}
</ul>
{% end %}包含片段
{% include %} 将另一个模板文件嵌入当前位置,共享父模板的上下文变量:
{% include "components/header.html" %}
<main>{% block content %}{% end %}</main>
{% include "components/footer.html" %}内置函数
模板中可直接调用以下内置函数:
| 函数 | 说明 |
|---|---|
escape(s) | HTML 转义字符串(将 <>、& 等转义) |
url_escape(s) | URL 编码字符串 |
json_encode(obj) | 将对象序列化为 JSON 字符串 |
static_url(path) | 生成带版本哈希的静态文件 URL |
reverse_url(name, *args) | 根据路由名称反解析 URL |
xsrf_form_html() | 生成 XSRF 隐藏表单字段 |
<!-- 防 XSS:对不信任内容手动转义 -->
<p>{{ escape(user_input) }}</p>
<!-- 静态文件 URL(自动添加版本哈希,例如 /static/css/main.css?v=3a2b1c) -->
<link rel="stylesheet" href="{{ static_url('css/main.css') }}">
<!-- XSRF 防护(需在 settings 中开启 xsrf_cookies=True) -->
<form method="post">
{% raw xsrf_form_html() %}
<input type="text" name="username">
<button type="submit">提交</button>
</form>向模板传递自定义函数
render() 的关键字参数可以传递函数,在模板中直接调用:
import datetime
class IndexHandler(tornado.web.RequestHandler):
def get(self):
def format_date(ts):
return datetime.datetime.fromtimestamp(ts).strftime("%Y-%m-%d")
self.render(
"index.html",
articles=articles,
format_date=format_date,
)模板中使用:
{% for article in articles %}
<article>
<h2>{{ article.title }}</h2>
<time>{{ format_date(article.created_at) }}</time>
</article>
{% end %}XSRF 防护
Tornado 内置 XSRF(跨站请求伪造)防护,在 settings 中启用:
settings = {"xsrf_cookies": True}启用后,所有 POST、PUT、DELETE 请求都必须携带有效的 XSRF Token,否则返回 403。
- HTML 表单:在表单中调用
{% raw xsrf_form_html() %}自动插入隐藏字段。 - AJAX 请求:从 Cookie
_xsrf读取 Token,并在请求头中以X-XSRFToken传递。
// jQuery 示例
function getCookie(name) {
const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
return match ? match[2] : null;
}
$.ajax({
url: "/api/submit",
method: "POST",
headers: {"X-XSRFToken": getCookie("_xsrf")},
data: JSON.stringify(payload),
});Tornado 的模板引擎功能完整但相对轻量。如果项目前端较复杂,推荐改用前后端分离架构(Tornado 仅提供 API),或引入 Jinja2(
pip install jinja2 后可通过自定义 render() 集成)。最后更新于