Go 踩坑之实现 html-template 不转义 HTML 标签
实实在在踩了个 GO 的坑。
需求很简单:把用户提交的带换行、加粗标签的评论内容,通过 html-template 渲染到页面上。
结果运行后发现,页面上直接显示了<strong>好评</strong>这种原始标签,完全没有被解析成加粗样式——这就是 html-template 默认的HTML 标签转义机制在搞鬼。
今天就把这些踩坑经验分享出来,帮大家少走弯路。
为什么 html-template 会转义 HTML 标签?
一开始会疑惑为什么 html-template 要“多此一举”转义标签。
其实这是框架的安全设计,目的是防止XSS攻击。
比如用户恶意提交<script>alert('攻击')</script>这样的内容,如果直接渲染执行,可能会窃取用户信息。
但实际开发中,我们确实有合法渲染HTML标签的场景,比如用户评论的富文本、后台编辑的带格式内容等。
这时候就需要手动关闭指定内容的转义功能。
方法 1:使用 template.HTML 类型
最直接——使用 template.HTML 类型转换
这是最常用的方法,核心是把要渲染的字符串转换成 template.HTML 类型。
html-template 会识别这种类型,不对其进行转义。
package main
import (
"html/template"
"net/http"
)
func main() {
// 定义带HTML标签的内容
comment := "<strong>这个功能太好用了!</strong><br/>必须好评~"
// 转换为template.HTML类型,避免转义
unsafeComment := template.HTML(comment)
// 定义模板内容
tplStr := `
<!DOCTYPE html>
<html>
<body>
<h3>用户评论</h3>
{{.}}
</body>
</html>
`
// 解析模板
tpl, err := template.New("commentTpl").Parse(tplStr)
if err != nil {
panic(err)
}
// 启动HTTP服务,渲染模板
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
err := tpl.Execute(w, unsafeComment)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
})
panic(http.ListenAndServe(":8080", nil))
}运行后访问localhost:8080,就能看到“这个功能太好用了!”被加粗,并且有换行效果。
需要注意的是,这种方法要确保内容是可信的,比如是后台管理员编辑的内容,避免直接渲染未知用户提交的内容,防止 XSS 风险。
方法 2:通过 FuncMap 自定义转义函数
更灵活——通过 FuncMap 自定义转义函数
如果需要在模板中动态控制是否转义,或者有多个内容需要处理,用 FuncMap 自定义函数更合适。
我们可以定义一个“不转义”函数,在模板中按需调用。
package main
import (
"html/template"
"net/http"
)
func main() {
// 定义模板内容,调用自定义的unescape函数
tplStr := `
<!DOCTYPE html>
<html>
<body>
<h3>可信内容(不转义)</h3>
{{unescape .TrustedContent}}
<h3>不可信内容(默认转义)</h3>
{{.UntrustedContent}}
</body>
</html>
`
// 创建FuncMap,注册unescape函数
funcMap := template.FuncMap{
"unescape": func(s string) template.HTML {
// 核心逻辑:将字符串转为template.HTML类型
return template.HTML(s)
},
}
// 解析模板时关联FuncMap
tpl, err := template.New("contentTpl").Funcs(funcMap).Parse(tplStr)
if err != nil {
panic(err)
}
// 准备渲染数据:可信内容和不可信内容
data := map[string]string{
"TrustedContent": "<h4>官方公告:系统将于24点维护</h4>",
"UntrustedContent": "<script>alert('恶意代码')</script>",
}
// 启动服务
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
err := tpl.Execute(w, data)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
})
panic(http.ListenAndServe(":8080", nil))
}这个示例中,可信的官方公告会正常渲染为标题样式,而恶意代码会被转义成纯文本显示。
这种方法的优势在于灵活性高,能在模板中精准控制转义范围。
方法 3:使用 text/template
最谨慎——使用 text/template(不推荐轻易用)
还有一种极端情况:如果整个模板都不需要转义(比如渲染纯HTML静态页面),可以用 text/template 替代 html-template。
因为 text/template 本身就没有自动转义功能。
package main
import (
"net/http"
"text/template" // 注意这里用的是text/template
)
func main() {
tplStr := `
<!DOCTYPE html>
<html>
<body>
{{.}}
</body>
</html>
`
// 用text/template解析
tpl, err := template.New("textTpl").Parse(tplStr)
if err != nil {
panic(err)
}
htmlContent := "<div style='color:red'>这是红色文本</div>"
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
err := tpl.Execute(w, htmlContent)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
})
panic(http.ListenAndServe(":8080", nil))
}重点提醒:这种方法会完全关闭转义功能,安全性极低。
除非你能 100% 确保渲染的内容绝对可信,否则强烈不推荐使用,避免留下 XSS 漏洞。
常见问题
Q1.用了template.HTML还是转义了?
我第一次尝试时就遇到过这个问题,后来发现是因为在模板中重复处理了变量。
比如先把变量转成template.HTML,又在模板里用{{. | html}}强制转义,相当于画蛇添足。
解决方法:检查模板语法,去掉多余的转义过滤器。
Q2.FuncMap 注册后模板调用提示“函数未定义”?
这是新手常犯的错误,原因是注册FuncMap的顺序不对。
必须先调用Funcs(funcMap),再调用Parse或ParseFiles。
如果先解析模板再注册函数,模板就找不到对应的函数了。
Q3.如何平衡渲染需求和安全性?
核心原则:“最小权限”。能局部不转义就不全局不转义,能过滤标签就不直接渲染原始内容。
比如用户提交的评论,可以用第三方库(如github.com/microcosm-cc/bluemonday)过滤危险标签(script、iframe等),只保留strong、br等安全标签,再用方法1或2渲染。
总结
处理 html-template 的转义问题:
首选方法1( template.HTML 类型转换)和方法2( FuncMap 自定义函数),这两种方法既能满足需求又能保证安全。
方法3( text/template )尽量少用,除非有特殊场景且能把控安全风险。
最后:关闭转义时一定要确保内容可信,必要时结合标签过滤库做二次处理,别为了图方便留下安全隐患。
如果还有其他踩坑经历,欢迎在评论区交流~
版权声明
未经授权,禁止转载本文章。
如需转载请保留原文链接并注明出处。即视为默认获得授权。
未保留原文链接未注明出处或删除链接将视为侵权,必追究法律责任!
本文原文链接: https://fiveyoboy.com/articles/go- template-html-code/
备用原文链接: https://blog.fiveyoboy.com/articles/go- template-html-code/