(十九)模板转义与错误页面
1. 沿文本进入 HTML 的位置核对
保存书名 <img src=x onerror=alert(1)> " & $,数据库和 JSON 仍保存原文。Jinja 把它放进标题时转义尖括号,放进输入框的 value 时也处理引号,浏览器显示相同文字,没有创建图像节点。
模板中的 |safe 会把值标成可以直接输出的标记。给这条书名加上它,会跳过这里需要的转义,因此书名、标签和用户名都保持普通文本输出。模板文件由程序提供,用户文本只是数据,不作为另一份模板执行。
2. 保留状态与协议头,再选择 HTML 或 JSON
# 项目文件:shelf_project/src/shelfweb/errors.py
import json
import sqlite3
from flask import current_app, render_template, request
from werkzeug.exceptions import HTTPException, InternalServerError
def http_error(error):
response = error.get_response() # 保留 405 的 Allow 等响应头。
if request.path.startswith("/api/"):
response.set_data(
json.dumps(
{"error": {"status": error.code, "message": error.description}},
ensure_ascii=False,
)
)
response.content_type = "application/json"
else:
response.set_data(
render_template("error.html", status=error.code, message=error.description)
)
response.content_type = "text/html; charset=utf-8"
return response
def database_error(error):
current_app.logger.error("数据库操作失败:%s", type(error).__name__)
return http_error(InternalServerError(description="数据库操作失败,请稍后重试"))
def init_app(app):
app.register_error_handler(HTTPException, http_error)
app.register_error_handler(sqlite3.DatabaseError, database_error)abort 抛出 Werkzeug 的 HTTP 异常,error.get_response() 先取得该异常对应的响应,保留 Allow 等头;再按请求路径替换 HTML 或 JSON 正文。set_data 同时更新正文长度,content_type 告诉客户端按哪种内容解释。
数据库异常由事务回滚后交给处理器,服务日志记录异常类别,页面显示简短失败说明。原始 SQL 参数和密码摘要不会塞入错误页面。
<!-- 项目文件:shelf_project/src/shelfweb/templates/error.html -->
{% extends 'base.html' %}
{% block title %}请求未完成{% endblock %}
{% block content %}
<section class="panel form-panel"><p class="eyebrow">HTTP {{ status }}</p>
<h1>请求未完成</h1><p role="alert">{{ message }}</p>
<p><a href="{{ url_for('shelf.index') }}">返回书架</a> · <a href="{{ url_for('auth.login') }}">前往登录</a></p>
</section>
{% endblock %}| 实际操作 | 状态与页面行为 |
|---|---|
| 未登录打开书架 | 303 到登录页 |
未登录读取 /api/books |
JSON 401 |
| 字段不合格 | 表单 422,保留非密码输入 |
| CSRF 令牌错误 | 400,提示刷新页面 |
| 访问其他用户的书 | 403 |
| 书籍不存在或路径不匹配 | 404 |
| 对删除地址发送 GET | 405,保留 Allow |
| 同名保存 | 表单 409,记录保持原值 |
| 数据库执行失败 | 500,新请求核对事务结果 |