Python 现代开发学习笔记

(十九)模板转义与错误页面

1. 沿文本进入 HTML 的位置核对

保存书名 <img src=x onerror=alert(1)> " & $,数据库和 JSON 仍保存原文。Jinja 把它放进标题时转义尖括号,放进输入框的 value 时也处理引号,浏览器显示相同文字,没有创建图像节点。

模板中的 |safe 会把值标成可以直接输出的标记。给这条书名加上它,会跳过这里需要的转义,因此书名、标签和用户名都保持普通文本输出。模板文件由程序提供,用户文本只是数据,不作为另一份模板执行。

数据库保留带尖括号和引号的书名,输出到标题或 value 属性时分别转义,浏览器恢复相同可见文字,JSON 仍交回原字符串

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,新请求核对事务结果
字段失败保留表单,身份失败跳登录或交回 JSON,归属与数据库失败进入错误处理,保留原状态和头后选择页面或 JSON 正文

笔记正文与全部题解 · Ctrl / ⌘ K