Go html/template 如何区分安全模板与纯文本模板
我第一次把 Go 模板从邮件正文迁到网页页面时,最容易混淆的就是 html/template 和 text/template:两者都能解析 {{.Name}},但输出目标不同,安全边界也不同。结论很简单——要生成 HTML,就选 html/template;要生成纯文本,再选 text/template。不要因为接口相似,就把纯文本模板当成 HTML 模板使用。
普通数据交给html/template时,包会根据它所在的 HTML、属性、JavaScript、CSS 或 URI 上下文自动转义;text/template不承担这层 HTML 安全职责。只有确定来自可信来源的标记片段,才考虑template.HTML等安全类型。
- HTML 页面、属性和脚本中的动态值,默认使用
html/template。 - 纯文本邮件、日志或配置输出使用
text/template,但它不会替你防止 HTML 注入。 template.HTML是“已确认安全”的类型,不是把用户输入变安全的函数。
先按输出格式选择模板包
text/template 的职责是把数据渲染成文本,输出可以是 Markdown、邮件、SQL 片段或配置文件;它不知道这段文本最后会不会被浏览器当成 HTML。官方文档也明确建议:如果输出是 HTML,应改用 html/template。
html/template 复用了 text/template 的大部分接口,但会把动作放回 HTML 结构中分析。例如同一个值出现在标签正文和 href 属性里,所需的转义规则并不一样。这个选择应在包导入处完成,而不是等输出异常后再补一个手工替换。
普通字符串为什么在两个包里表现不同
假设数据中有一段 。用 text/template 渲染时,它会原样出现在文本结果里;如果这段结果又被浏览器当作 HTML 解析,就可能变成代码注入入口。用 html/template 时,普通字符串会被编码为文本,尖括号等特殊字符不再改变页面结构。
更关键的是,html/template 不只做一次固定的 HTML 替换,而是按上下文处理。下面的写法用别名区分两个包,示例输出中的转义结果表示它们的安全职责差异:
package main
import (
htmltmpl "html/template"
"os"
texttmpl "text/template"
)
func main() {
data := struct{ Value string }{Value: ""}
// 纯文本模板只负责替换动作,不判断结果会被什么解析器消费。
plain := texttmpl.Must(texttmpl.New("plain").Parse("{{.Value}}"))
_ = plain.Execute(os.Stdout, data)
// HTML 模板把普通字符串当作不可信文本,并按所在上下文添加转义。
page := htmltmpl.Must(htmltmpl.New("page").Parse("{{.Value}}
"))
_ = page.Execute(os.Stdout, data)
}
在真实页面中还要留意属性和脚本上下文,例如 与标签正文需要不同处理。不要先用 html.EscapeString 把所有值处理一遍再塞进模板;这会让上下文判断失去意义,也容易造成重复转义。

纯文本模板并不等于不安全
text/template 本身没有问题,关键是它的输出协议。生成纯文本邮件、命令行帮助或配置文件时,保留原字符通常正是需求;但如果模板结果随后会进入浏览器、HTML 邮件或某个会执行标记的消费者,就应该在边界处改用适合该格式的编码策略。
我通常把判断写成一句工程规则:模板包跟着最终解释器走。浏览器解释 HTML,就让 html/template 负责上下文转义;纯文本阅读器不解释标签,才使用 text/template。不要只看文件名是 .tmpl 还是 .tpl,也不要用“当前输入看起来没有尖括号”来证明选择正确。
template.HTML 不是用户输入的快捷通行证
html/template 默认把执行数据当作普通文本。确实有些场景需要输出由程序或可信内容系统生成的 HTML 片段,这时可以使用 template.HTML 等带语义的安全类型,让它不要被再次当成普通文本转义。但类型转换只是在代码中声明“这段内容已经经过安全审查”,不会替你清理输入。
package view
import (
"html/template"
"io"
)
type Data struct {
Title string
Body template.HTML
}
func Render(w io.Writer, title string, trustedBody template.HTML) error {
const page = "{{.Title}}
{{.Body}} "
tmpl, err := template.New("page").Parse(page)
if err != nil {
// 模板是受信任代码的一部分,解析失败应直接返回并修复模板。
return err
}
// Title 仍是普通 string;只有已经过业务审核的 Body 才使用安全类型。
return tmpl.Execute(w, Data{Title: title, Body: trustedBody})
}
这里的 trustedBody 不应直接来自表单、评论、URL 参数或数据库中未经处理的用户内容。更稳妥的做法是让富文本清洗器先输出允许的有限标签,或者把内容设计成结构化数据,由模板负责生成固定 HTML。能不用安全类型时,优先不用。

迁移时只检查四个边界
- 检查导入包。网页渲染的文件统一确认导入的是
html/template,不要只因为旧代码能编译就保留text/template。 - 检查数据去向。列出每个动态值最终由谁解释:浏览器、邮件客户端、日志查看器还是配置加载器。
- 检查安全类型转换。搜索
template.HTML、template.JS、template.URL等转换,逐个标注来源和审查理由。 - 检查恶意字符测试。至少覆盖尖括号、引号、反斜杠和属性值中的 URI;测试目标是页面结构不被输入改变,而不是只比较字符串是否“好看”。
模板作者本身也属于信任边界。html/template 的模型是模板作者可信、执行数据不一定可信;如果模板文件来自不可信用户,不能把换包当成沙箱。最后再记一遍:选择包解决的是输出格式与默认转义,不能替代输入校验、富文本清洗、权限控制和 CSP 等其他措施。
常见问题
text/template 能不能渲染 HTML?
能生成 HTML 字符串,但不会自动按 HTML 上下文转义。只要结果会交给浏览器解析,优先改用 html/template。
html/template 会不会把所有 HTML 都转义掉?
普通字符串会按上下文安全编码;明确使用 template.HTML 等安全类型的值可能保留标记。因此安全类型的来源必须可解释、可审查。
参考资料:https://pkg.go.dev/html/template、https://pkg.go.dev/text/template、https://go.dev/doc/
Lovart设计师使用指南:从准备素材到导出结果
- 上一篇
- Lovart设计师使用指南:从准备素材到导出结果
- 下一篇
- GitHub Actions runner 镜像更新前如何验证项目依赖
-
- Golang · Go教程 | 21分钟前 |
- Go archive/zip 如何只读取压缩包中央目录信息
- 231浏览 收藏
-
- Golang · Go教程 | 30分钟前 | HTTP · gzip · Go教程 · HTTP响应 compress/gzip 流式压缩
- Go compress/gzip 如何流式压缩 HTTP 响应
- 373浏览 收藏
-
- Golang · Go教程 | 58分钟前 | go · FuncMap · text/template ·
- Go text/template 如何给模板提供自定义函数
- 125浏览 收藏
-
- Golang · Go教程 | 1小时前 | 标准库 · go · 正则表达式 · regexp 命名捕获组 SubexpIndex SubexpNames
- Go regexp 如何提取命名捕获组结果
- 374浏览 收藏
-
- Golang · Go教程 | 1小时前 |
- Go time.Date 如何处理月末日期自动进位
- 306浏览 收藏
-
- Golang · Go教程 | 1小时前 | 标准库 · go · 时间处理 · Go 毫秒 time.Duration time.ParseDuration 秒
- Go time.Duration 如何安全解析用户输入的毫秒和秒
- 485浏览 收藏
-
- Golang · Go教程 | 1小时前 | 切片 · unsafe · Go教程 · 内存边界 · 零拷贝 · Go 字节切片 零拷贝 unsafe.Pointer unsafe.Slice 指针生命周期
- Go unsafe.Slice 如何从指针构造字节切片
- 225浏览 收藏
-
- Golang · Go教程 | 2小时前 | 反射 · go · 类型系统 · Go reflect.StructOf reflect.StructField 动态结构 重复字段
- Go reflect.StructOf 动态字段重复时如何处理
- 243浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 104次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 21次使用
-
- OpenCompass
- OpenCompass是上海AI实验室推出的开源大模型评测平台,提供CompassKit、CompassHub和CompassRank三大核心组件,支持LLM及多模态模型的一站式标准化评估与排行榜查询。
- 31次使用
-
- AGI-Eval
- AGI-Eval是由上海交大等高校联合发布的大模型评测社区,提供公正透明的LLM能力榜单、多领域评测集及Data Studio数据服务,助力AI模型性能评估与NLP科研开发。
- 22次使用
-
- SuperCLUE
- SuperCLUE是权威的中文大语言模型综合评测基准,涵盖语言理解、知识应用、AI Agent智能体及安全性等12项核心能力。通过多轮对话与客观测试,定期发布榜单与技术报告,为模型研发、优化及行业选型提供科学依据。
- 259次使用
-
- Go error wrapping 实战:别让错误日志只剩一句 failed
- 2026-06-01 151浏览
-
- Go pprof 排查慢接口:别只会看火焰图,先把问题问对
- 2026-06-01 101浏览
-
- Go Flight Recorder 实战:线上偶发卡顿,别再只靠日志碰运气
- 2026-06-01 323浏览
-
- Go testing/synctest 实战:别再用 time.Sleep 赌并发测试会过
- 2026-06-01 428浏览
-
- Go slog 生产实践:日志别只会打印 error,要能帮你排障
- 2026-06-01 143浏览

