Go text/template与html/template选择输出边界的对比
我第一次把 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。
Go copy返回长度小于预期时的切片容量分析
- 上一篇
- Go copy返回长度小于预期时的切片容量分析
- 下一篇
- Go time.Timer复用时停止旧计时并排空通道的写法
-
- Golang · Go教程 | 41分钟前 | 数据结构 · go · sort sort.Search 切片插入
- Go sort.Search实现有序切片插入位置查找
- 245浏览 收藏
-
- Golang · Go教程 | 53分钟前 | 并发 · 标准库 · go · time.Timer Go计时器 Timer复用
- Go time.Timer复用时停止旧计时并排空通道的写法
- 469浏览 收藏
-
- Golang · Go教程 | 54分钟前 | 错误处理 · go ·
- Go errors.As提取自定义错误类型的分层处理方案
- 471浏览 收藏
-
- Golang · Go教程 | 55分钟前 |
- Go errors.Join保留多个失败原因并支持errors.Is判断
- 459浏览 收藏
-
- Golang · Go教程 | 56分钟前 | 命令行工具 · Go教程 · flag.NewFlagSet ContinueOnError Go flag.FlagSet Go 子命令 Go 命令行参数解析
- Go flag.FlagSet构建独立子命令解析器的组织方式
- 355浏览 收藏
-
- Golang · Go教程 | 2小时前 |
- Go reflect方法集影响接口实现判断的解释与示例
- 491浏览 收藏
-
- Golang · Go教程 | 2天前 | go · testing ·
- Go Benchmark报告allocs/op并定位临时对象来源的方法
- 409浏览 收藏
-
- Golang · Go教程 | 2天前 |
- Go fuzz测试把崩溃输入写入回归语料的流程
- 195浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 187次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 243次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 201次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 183次使用
-
- CMMLU
- 深入了解CMMLU中文评估基准,涵盖67个学科主题,提供数据集下载、Zero-shot/Five-shot评估方法及排行榜,助力优化中文语言模型性能。
- 172次使用
-
- GScript 编写标准库示例详解
- 2022-12-30 369浏览
-
- 关于Golang标准库flag的全面讲解
- 2023-02-25 344浏览
-
- Golang标准库unsafe源码解读
- 2022-12-29 464浏览
-
- 快速掌握Go语言HTTP标准库的实现方法
- 2022-12-30 327浏览
-
- 深入解析Gotemplate模板使用详解
- 2022-12-28 206浏览

