Go html/template 怎样区分文本和 HTML 内容
来源:17golang原创
时间:2026-10-14 03:14:46 281浏览 收藏
Go 里的 html/template 默认把传入值当成普通文本,再根据它所在的 HTML 上下文自动转义。真正需要保留标签时,应该把“可信 HTML 片段”作为一个明确的类型边界传入,而不是把所有字符串都转换成 template.HTML。
最稳妥的判断标准是:用户输入、数据库原文和第三方接口字段继续使用普通string;只有模板作者或经过可靠清洗的 HTML 片段,才允许进入template.HTML。这样既能显示可信的加粗标签,也不会让普通文本里的尖括号变成页面结构。
- 普通
string:按上下文转义,适合标题、摘要、用户昵称和外部输入。 template.HTML:按可信 HTML 片段原样保留,使用它就是在声明安全责任。- 不安全 URL:放进
href或src时可能被过滤为#ZgotmplZ,不要用类型转换掩盖来源问题。
先把 string 和 HTML 片段放进不同边界
假设页面同时有文章标题、摘要和一段由站点内部生成的强调内容。标题和摘要即使包含 ,也应该作为文字显示;内部生成且已经经过信任判断的片段,才有理由保留标签。这个区分比“页面能不能显示加粗”更重要,因为它决定了谁可以影响最终 HTML 结构。

html/template 会按上下文自动转义
html/template 与 text/template 的接口相近,但用途不同。输出 HTML 时应优先使用前者:普通值放在文本节点、属性、URL 或脚本上下文中,模板会分别加入对应的转义和过滤逻辑。
因此不要为了“看起来已经转义过”而在模板里机械追加 html、urlquery 等旧式管道。让模板根据实际位置处理普通值,通常更容易保持上下文正确。
用一段小模板看清三种输出状态
下面的示例故意放入三类值:普通标题、普通摘要和可信 HTML 片段。示例只展示数据类型如何影响输出,不把任何外部输入强行标为安全内容。
package main
import (
"html/template"
"log"
"os"
)
type pageData struct {
Title string
Summary string
Body template.HTML
}
const page = `{{.Title}}
{{.Summary}}
{{.Body}} `
func main() {
data := pageData{
Title: ``,
Summary: ``,
// 只有来源可信的片段才进入 template.HTML。
Body: template.HTML(`这是可信的重点内容`),
}
// Parse 会建立模板,执行阶段再把数据写入标准输出。
tmpl, err := template.New("page").Parse(page)
if err != nil {
log.Fatalf("解析模板失败: %v", err)
}
// Execute 的错误必须返回给调用方,不能静默丢掉。
if err := tmpl.Execute(os.Stdout, data); err != nil {
log.Fatalf("执行模板失败: %v", err)
}
}
这段模板的预期对照是:标题中的尖括号会被编码,摘要里的脚本标签会保持为普通文字,Body 中的 才会作为标签输出。这里的“预期”是根据类型和模板上下文推导出的结果,不等同于线上页面截图。
template.HTML 只适合放在可信入口之后
template.HTML 的意义不是“让 Go 少转义一次”,而是向模板声明:这段内容已经满足 HTML 片段的安全前提。官方文档也明确提醒,类型中的内容会原样进入输出,来源必须可信,并且不能包含未闭合标签或不完整注释。

工程上可以把转换动作收口到一个小函数或领域层,而不是在控制器里到处写类型转换。若内容来自 Markdown、富文本编辑器或数据库,先使用项目认可的白名单清洗器处理,再把清洗后的结果作为可信片段传递;未经清洗的原文继续保持 string。
// SafeHTML 表示调用方已经完成来源判断或白名单清洗。
func SafeHTML(cleaned string) template.HTML {
// 这里不负责替调用方清洗;不满足前置条件时应返回普通 string。
return template.HTML(cleaned)
}
不要写出下面这种“任何字符串都能显示标签”的快捷函数:
// 不要把不可信输入直接转换成 template.HTML。
func UnsafeHTML(input string) template.HTML {
return template.HTML(input)
}
如果调用方能把用户昵称、评论或外部接口字段直接传给这个函数,模板的安全边界就被绕开了。类型名称虽然变化了,数据来源并没有因此变可信。
属性和 URL 位置要单独判断
同一个值放在正文和 href 中,处理规则不同。正文中的普通字符串主要需要 HTML 转义;URL 位置还要判断协议和属性上下文。来自用户的链接通常应先解析、限制协议和记录拒绝原因,再交给模板,而不是简单转换成 template.URL。
如果一个值在 URL 上下文中触发了 #ZgotmplZ,这通常说明它不符合允许的 URL 语境。它是需要修正数据来源或协议白名单的信号,不是把值改成另一个 typed content 类型的邀请。
用三组核对项建立可复用的基线
这类模板问题不适合只用“页面看起来正常”衡量。可以固定三组输入、三类上下文和三条输出判断,作为代码评审或回归检查的基线:
| 输入 | 模板位置 | 应关注的结果 |
|---|---|---|
| 普通标题 string | 文本节点 | 尖括号显示为实体,不形成新标签 |
| 普通链接 string | href | 协议和属性字符按 URL/属性上下文处理 |
| 可信片段 template.HTML | 内容区域 | 允许的标签保留,但来源责任转由调用方承担 |
这个基线的好处是能把性能和安全讨论分开:模板是否重复解析、是否复用实例属于工程性能问题;值是否被错误标记为可信,属于数据边界问题。不要用缓存模板或减少一次转义来替代来源治理。
几个容易混淆的边界
- 模板作者可信,不等于 Execute 数据可信:模板文件和传入数据是两条不同的信任链。
- 类型安全不等于内容已经清洗:
template.HTML只携带“按 HTML 解释”的承诺,不会替你检查业务允许的标签。 - 上下文转义不等于任意位置都能插值:缺少引号、标签未闭合或分支落入不同上下文时,解析阶段可能返回错误。
- 模板复用和数据复用是两回事:可复用已经解析的模板实例,但每次执行仍要按请求数据的来源和类型判断。
相关问题
为什么普通 string 里的 HTML 标签没有生效?
这是 html/template 的默认行为。普通值按文本处理,标签会被转义;只有明确表示可信的 template.HTML 才会保留标签。
template.HTML 能不能直接包住用户输入?
不建议。除非输入经过可靠的白名单清洗并且明确属于可信片段,否则应保持为普通 string,让模板负责上下文转义。
text/template 和 html/template 该选哪个?
输出 HTML 时选 html/template;text/template 不提供同等的 HTML 上下文自动转义,不能把它当作 HTML 页面模板的替代品。
官方资料在哪里?
https://pkg.go.dev/html/template 记录了上下文自动转义、typed strings、错误码和安全模型。使用具体版本时,还应结合项目实际 Go 版本阅读对应文档。
把普通文本和可信 HTML 分成两条类型路径,通常比在模板里到处补转义更容易维护。先守住来源边界,再让 html/template 根据上下文完成默认处理,最后用固定的三组输入基线检查输出,模板就能同时满足可读性和安全性。
-
206 收藏
-
240 收藏
-
132 收藏
-
286 收藏
-
151 收藏
-
155 收藏
-
Golang · Go教程 | 46分钟前 | 性能优化 · 并发编程 · 同步 · Go教程 · Go add sync/atomic CompareAndSwap 并发计数器 atomic.Int64 Load471 收藏
-
141 收藏
-
Golang · Go教程 | 48分钟前 | select · Context · 超时控制 · Go教程 · time.NewTimer context.Done Go超时 Go select 多路等待112 收藏
-
164 收藏
-
116 收藏
-
359 收藏
-
467 收藏
-
423 收藏
-
146 收藏
-
105 收藏
-
256 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习