2.6 JSP:一个装配工的没落

从页面脚本混写走到请求、模型、模板和转义响应分层,理解 JSP、标签库与模板引擎的责任边界。

学习目标

  • 能沿 HTTP 请求、控制器、模型、模板、标签库和 HTML 响应追踪一次服务器端页面渲染
  • 能解释 JSP、标签库和模板引擎如何减少脚本混写,同时保留转义、缓存和异常边界
  • 能用正常、边界和单一故障样本定位模板缺值、上下文转义错误、渲染失败和缓存污染

2.6 JSP:一个装配工的没落

本页依据刘欣《码农翻身》(2018 年第 1 版)及出版社公开书目信息,独立重构 2.6 JSP:一个装配工的没落。正文、代码、图示、实验和练习都是本课程重新设计的教学材料,不复制原书正文、插图、练习答案或代码。

服务器端动态页面的难题不是“能不能拼出 HTML”,而是如何让请求数据、业务查询、页面结构和安全输出各自有边界。JSP 曾让 Java 代码和标记快速靠近,标签库把常见控制流包装成标签,模板引擎进一步把视图变成可替换的渲染层;但任何工具都不能自动知道一个值将进入 HTML 文本、属性、URL、JavaScript 还是 CSS 上下文。

服务端页面链:模型进入模板,数据不能变成代码每个动态插槽都要声明上下文、转义、缓存和失败行为1请求身份与参数输入证据2控制器授权与业务输入证据3模型只读数据输出证据4模板标签与渲染当前表示层5响应转义与缓存输出证据HTML 文本、属性、URL、JavaScript 和 CSS 插槽不能共用一个转义假设
专属图示:把 JSP/模板渲染拆成请求、模型、表示和安全输出边界。

三个会让服务端页面失真的陷阱

七个目录节点到渲染证据

黑暗岁月

不是对 JSP 的道德评价,而是一个可观察的耦合形态。诊断时看页面是否直接拿连接、修改状态、读取会话和拼接未转义值;只要跨层隐式发生,改动就会同时影响业务和视图。

服务器端动态页面

应明确请求方法、参数、身份、模型版本、响应状态和缓存策略。模板负责把已批准的数据变成表示,不能替代授权、事务或输入校验。

标签库

是视图层工具,不是万能安全层。标签需要定义输入类型、空值行为、转义策略和异常传播;一个标签自动转义 HTML 文本,不代表它能安全生成 JavaScript 或 URL。

模板引擎

让视图可替换、可测试和可缓存。它的输入应是明确模型,模板语法和用户数据必须分开;渲染失败要返回可识别的错误响应,不能把半截 HTML 当成成功页面。

草根搅局

带来速度和生态,但扩展标签、模板片段和插件都要经过版本、权限、转义和依赖审查。可组合不等于可任意执行;把第三方片段当成代码依赖管理。

2.6 JSP:一个装配工的没落

标题中的“没落”指的是页面装配工作逐渐被控制器、标签库和模板引擎分担,而不是服务器端渲染失去价值。2.6 JSP:一个装配工的没落的工程结论是:表示层可以演进,但请求、模型、渲染、转义和缓存边界必须可追踪。

最小服务器端渲染合同

record PageModel(String title, List<String> items, String requestId) {}
 
PageModel loadModel(HttpServletRequest request) {
  String requestId = request.getHeader("X-Request-Id");
  String query = request.getParameter("q");
  return new PageModel(
      queryService.findTitle(query),
      queryService.findItems(query),
      requestId == null ? "generated" : requestId
  );
}
 
String render(PageModel model) {
  return template.render(model); // 每个插槽按自身上下文转义
}

合同把请求输入、业务读取、模型和渲染分开。模板只接收模型,不直接执行写入;每个动态插槽要记录 HTML 文本、属性、URL 或脚本上下文;响应状态、缓存头和错误页也属于合同。

四步复核一条页面渲染链

分步1 / 4

1. 接收请求并固定身份

记录方法、路径、参数、身份、请求 ID、语言和缓存条件。正常样本使用可信查询;边界样本为空值、超长值、未授权身份和重复请求,先确认控制器不会把原始输入当模板。

服务端页面链:模型进入模板,数据不能变成代码每个动态插槽都要声明上下文、转义、缓存和失败行为1请求身份与参数输入证据2控制器授权与业务输入证据3模型只读数据输出证据4模板标签与渲染当前表示层5响应转义与缓存输出证据HTML 文本、属性、URL、JavaScript 和 CSS 插槽不能共用一个转义假设
专属图示:把 JSP/模板渲染拆成请求、模型、表示和安全输出边界。

Lab

JSP 渲染边界实验

只改变一个模型、插槽或缓存条件,观察响应内容与安全判定如何变化。

控制器提供最小模型,模板生成完整 HTML

request-id → authorized model → escaped text/URL → 200 complete response

判定

accept:响应完整且缓存键可解释

当前样本:正常渲染;保存请求 ID、模型版本、插槽上下文、缓存键和响应头。

正常、边界与单一故障证据

页面证据矩阵:完整响应还要上下文正确正常样本看模型,边界样本看插槽,故障样本看泄露与缓存观察项正常边界故障输入参数受控长/空值未授权模型字段最小缺值敏感字段插槽上下文明确属性/URL脚本注入缓存键完整版本变化跨用户串页先绑定请求 ID、模型版本和输出上下文,再判断页面是否安全
专属图示:页面质量同时由数据、上下文转义、缓存和错误响应决定。
样本只改变的变量预期判定必存证据
正常模型完整、模板版本匹配、上下文明确生成完整响应并正确转义请求 ID、模型摘要、响应头、HTML
边界空值、长值、缓存身份或模板缺值明确空态/拒绝/失效,不串用户数据上下文、缓存键、状态码、响应片段
单一故障一个业务查询、标签或模板依赖失败错误页完整、无堆栈泄露、可重放首个失败、异常映射、响应、恢复

故障诊断:先定位数据进入了哪一层

  1. 请求层:检查参数、身份、CSRF/授权和请求 ID;原始用户输入不应直接成为模板或脚本。
  2. 模型层:检查业务查询、字段权限、空态、模型版本和缓存键;模型错误不能靠模板条件掩盖。
  3. 渲染层:检查标签/模板版本、插槽上下文、转义和异常;HTML 文本安全不代表 URL 或脚本安全。
  4. 响应层:检查状态码、Content-Type、字符集、安全头、缓存和完整性;失败响应不能泄露堆栈或半截敏感内容。

如果响应内容正确但用户之间串数据,先查缓存键;如果页面出现脚本执行,先查插槽上下文和转义;如果模板偶尔返回半页,查异常传播和响应缓冲。诊断要沿请求 ID 从输入到响应闭环。

最小页面证据包与反例

证据包包含请求方法/路径摘要、身份与租户、参数脱敏值、控制器分支、模型字段摘要、模板/标签版本、插槽上下文、转义动作、缓存键、响应状态/头、错误 ID 和重放结果。不要记录完整敏感内容,但要保留区分用户和版本所需的身份摘要。

反例一是 HTML 文本插槽转义后,把同一个值未经 URL 编码放入链接;反例二是模板缓存只按 URL 键,导致用户 A 的模型片段进入用户 B 的响应;反例三是标签失败后仍返回 200 和半页 HTML。固定单一变量重放并记录最早偏差。

术语表

名词解释

本章出现的专业名词,用大白话再讲一遍。

黑暗岁月

请求处理、业务查询、控制流和 HTML 拼接混在页面文件中的高耦合阶段。

服务器端动态页面

服务端依据 HTTP 请求和模型生成 HTML 等表示并返回响应的页面路径。

标签库

把循环、条件、格式化和输出操作封装成可组合视图标签的组件集合。

模板引擎

读取模板结构和模型数据并生成目标表示的渲染组件。

草根搅局

低门槛标签、模板和开源扩展带来的生态速度与组合风险。

练习

练习

问题 1: 页面显示的 HTML 文本已转义,但点击链接时仍能改变目标地址。为什么一个 escape 函数不够?

问题 2: 用户 A 访问后,用户 B 偶尔看到 A 的页面片段。你先查哪三项?

问题 3: 模板标签抛错,但服务仍返回 200 和半页 HTML。怎样改进?

资料与写作方式声明

本章以码农翻身权威目录界定学习范围,并结合正文列出的技术资料独立重写;不宣称复现原书正文,也不沿用原作表述。

原作版权归作者与出版社所有;本站原创教学结构与表述仅供学习交流。

本页小结

2.6 JSP:一个装配工的没落的关键不是把 JSP 简单换成另一个模板引擎,而是把请求、模型、标签、模板、上下文转义和响应缓存分层。完成标准是能从请求 ID 追到页面输出,在边界与故障样本中阻止脚本注入、跨用户缓存和半截响应。

讨论

评论区加载中…