登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go教程

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 文本的静态结构说明图
图1:普通字符串、文本上下文与实体化输出之间的关系说明图,不是运行截图或执行证据。

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 片段的安全前提。官方文档也明确提醒,类型中的内容会原样进入输出,来源必须可信,并且不能包含未闭合标签或不完整注释。

普通字符串、template.HTML、可信数据边界和模板输出之间的静态关系图
图2:可信 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文本节点尖括号显示为实体,不形成新标签
普通链接 stringhref协议和属性字符按 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 根据上下文完成默认处理,最后用固定的三组输入基线检查输出,模板就能同时满足可读性和安全性。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>