Go text/template与html/template选择输出边界的对比
来源:17golang原创
时间:2026-09-23 13:13:29 275浏览 收藏
我第一次把 Go 模板接进一个小型管理页时,真正容易混淆的不是 {{.Title}} 怎么写,而是“这段输出最终给谁消费”。日志、邮件正文和配置文件需要保留文本;浏览器 HTML 则必须让数据留在正确的 HTML、URI 或 JavaScript 上下文里。两套包的解析和执行 API 很像,选择却不能只看调用习惯。
结论很明确:输出是纯文本时使用text/template;输出会进入 HTML 页面时使用html/template。后者会根据上下文自动转义普通数据,不能用前者渲染网页,也不要把手动转义当成替代方案。
这篇用同一份数据做一个最小对比项目,重点看选择依据、代码差异、转义位置和可信 HTML 的边界。
项目目标:先按输出边界做选择
先不要急着把两个包抽象成同一个渲染器。我会先写一张输出清单:命令行日志、邮件纯文本、Markdown 或配置片段属于文本输出;服务端生成的网页属于 HTML 输出。前一组可以使用 text/template,后一组直接使用 html/template。
| 最终消费者 | 选择 | 关键理由 |
|---|---|---|
| 日志、邮件、文本文件 | text/template | 模板包把动作结果按文本写出,不主动做 HTML 上下文转义 |
| 浏览器 HTML | html/template | 按 HTML、CSS、JavaScript、URI 上下文处理普通数据 |
| 受信任的 HTML 片段 | 谨慎使用 template.HTML | 只在片段来源和拼接过程都由受信任代码控制时保留标记 |

核心代码:分别解析并执行两种模板
下面的示例让两种模板共享数据结构和模板内容,只替换 import 路径。代码中的错误不能省略:Parse 负责模板语法,Execute 负责把数据写入目标;两处失败都应该交给调用方处理。
package main
import (
"bytes"
"fmt"
htmltmpl "html/template"
texttmpl "text/template"
)
type PageData struct {
Title string
Link string
}
const pageTemplate = `{{.Title}}
查看详情`
func renderText(data PageData) (string, error) {
// 纯文本输出不承担 HTML 上下文安全职责,适合日志或邮件正文。
tmpl, err := texttmpl.New("text").Parse(pageTemplate)
if err != nil {
return "", fmt.Errorf("解析文本模板: %w", err)
}
var buf bytes.Buffer
// Execute 失败时返回部分结果也不能被当成完整页面继续使用。
if err := tmpl.Execute(&buf, data); err != nil {
return "", fmt.Errorf("执行文本模板: %w", err)
}
return buf.String(), nil
}
func renderHTML(data PageData) (string, error) {
// HTML 输出交给 html/template,根据值所在的上下文做转义。
tmpl, err := htmltmpl.New("html").Parse(pageTemplate)
if err != nil {
return "", fmt.Errorf("解析 HTML 模板: %w", err)
}
var buf bytes.Buffer
// 保留错误返回,让上层决定记录、重试还是返回 HTTP 错误。
if err := tmpl.Execute(&buf, data); err != nil {
return "", fmt.Errorf("执行 HTML 模板: %w", err)
}
return buf.String(), nil
}
实际 Go 源文件中的模板字符串应使用原始反引号,并把 HTML 标签直接写入字符串;上面为了让文章页面保留标签,展示时对尖括号做了 HTML 实体编码。两种函数的关键差异只有包路径,但这正是安全语义的分界。
本地运行:用同一份数据观察转义结果
测试数据要刻意包含标签字符和一个带查询参数的链接。不要把“页面看起来能打开”当作结论,应分别观察文本节点和属性值。
func main() {
data := PageData{
// 用特殊字符验证普通文本与 HTML 上下文的差异。
Title: `内部公告`,
Link: `/search?q=go&mode=full`,
}
plain, err := renderText(data)
if err != nil {
panic(err)
}
page, err := renderHTML(data)
if err != nil {
panic(err)
}
fmt.Println("text/template:", plain)
fmt.Println("html/template:", page)
}
在 text/template 中,数据更接近原样写入;如果这段结果直接作为 HTML 响应,数据中的标记就可能改变页面结构。html/template 会把标题中的尖括号编码成文本,并在 href 这样的 URI/属性上下文中应用对应处理。它不是“把所有字符无脑替换”,而是根据模板位置计算上下文。

验收结论:把 HTML、纯文本和可信片段分开
最后我会把选择规则写进代码评审清单。第一,模板作者可以是可信的,但 Execute 接收的数据默认不可信;第二,网页输出统一从 html/template 开始,不用 text/template 生成 HTML;第三,template.HTML 是信任承诺,不是“关闭转义”的快捷按钮。
只有在 HTML 片段由受信任代码构造、结构经过明确控制,而且调用方确实需要保留标签时,才考虑类型化的安全内容。用户输入、数据库字段或外部接口返回值不要直接转换成 template.HTML。同样,html/template 中也不必手动套 html 或 urlquery 来“加固”,让包根据上下文处理更容易维护。
速记:文本消费者选 text/template,HTML 消费者选 html/template;先判断输出边界,再判断数据是否可信,最后才考虑模板函数或安全类型。
相关问题
两个包的模板语法能共用吗
大多数基础动作和执行接口相同,因此可以共用不少模板文本,但最终输出格式和安全职责不同,不能因为语法相同就互换包。
为什么 html/template 输出里出现了转义实体
普通字符串被当作数据而不是模板作者写入的标记。转成实体后,浏览器会把它显示为文本,从而保持 HTML 结构不被数据改变。
什么时候可以使用 template.HTML
仅限于来源、拼接逻辑和允许的标签都由受信任代码控制的片段;不应把外部输入通过类型转换直接标记为安全 HTML。
参考资料:https://pkg.go.dev/text/template;https://pkg.go.dev/html/template;https://go.dev/src/html/template/doc.go。
-
369 收藏
-
344 收藏
-
464 收藏
-
327 收藏
-
206 收藏
-
245 收藏
-
110 收藏
-
469 收藏
-
471 收藏
-
459 收藏
-
Golang · Go教程 | 53分钟前 | 命令行工具 · Go教程 · flag.NewFlagSet ContinueOnError Go flag.FlagSet Go 子命令 Go 命令行参数解析355 收藏
-
395 收藏
-
491 收藏
-
348 收藏
-
409 收藏
-
195 收藏
-
228 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习