Go 模板:text/template 与 html/template
Go 语言内置了两个模板包:text/template 和 html/template。它们语法几乎一致,但用途和安全机制完全不同。选择错误的包可能导致严重的安全漏洞或功能失效。
区分使用场景
text/template 用于生成任意纯文本内容,比如配置文件、日志、邮件正文等。它不做任何特殊转义,直接输出数据。
html/template 专为生成 HTML 设计。它会自动对变量内容进行 HTML 转义(escaping),防止跨站脚本攻击(XSS)。例如,若变量包含 <script>,html/template 会将其转为 <script>,确保浏览器不会执行恶意代码。
永远不要用 text/template 生成 HTML。即使当前数据“看起来安全”,未来数据源变化也可能引入风险。
基础语法:两者通用
两个包共享相同的模板语法。以下步骤适用于任一包:
-
导入对应包:
import "text/template" // 或 import "html/template" -
定义模板字符串,使用双花括号
{{}}包裹指令:const tmpl = `Hello, {{.Name}}! You have {{.Count}} messages.` -
解析模板:
t := template.Must(template.New("example").Parse(tmpl)) -
准备数据结构(通常为 struct 或 map):
data := struct { Name string Count int }{ Name: "Alice", Count: 5, } -
执行模板并输出到 io.Writer(如
os.Stdout或http.ResponseWriter):err := t.Execute(os.Stdout, data) if err != nil { log.Fatal(err) }
关键差异:自动转义行为
当模板变量包含特殊字符时,两个包的行为截然不同。
假设数据为:
data := map[string]string{"Content": "<b>Important</b>"}
模板内容为:
{{.Content}}
| 包 | 输出结果 | 说明 |
|---|---|---|
text/template |
<b>Important</b> |
原样输出,无转义 |
html/template |
<b>Important</b> |
自动 HTML 转义 |
如果确实需要在 HTML 中渲染原始 HTML(如富文本编辑器输出),必须显式标记为“安全”:
-
定义类型
template.HTML(来自html/template包):import "html/template" data := struct { TrustedHTML template.HTML }{ TrustedHTML: "<b>Bold text</b>", } -
在模板中直接使用:
const tmpl = `{{.TrustedHTML}}`
此时 html/template 会跳过转义,直接输出 <b>Bold text</b>。仅对完全可信的内容使用此方法。
常见陷阱与规避方法
-
混用包导致编译错误
text/template和html/template的Template类型不兼容。不能将*html/template.Template赋值给*text/template.Template变量。确保全程使用同一包。 -
误以为
html/template能防所有 XSS
它只对 HTML 内容转义,对 JavaScript、CSS 或 URL 上下文无效。例如:<script>var user = "{{.Input}}";</script>若
.Input为"; alert('xss'); ",仍会触发 XSS。避免在<script>标签内直接插入用户数据。应改用 JSON 编码:import "encoding/json" func toJSON(v interface{}) template.JS { a, _ := json.Marshal(v) return template.JS(a) }然后在模板中注册函数并使用:
t := template.Must(template.New("").Funcs(template.FuncMap{ "toJSON": toJSON, }).Parse(`... var user = {{.Input | toJSON}}; ...`)) -
模板嵌套时路径错误
在嵌套模板或 range 循环中,.会改变指向。使用$` 引用根对象: ```go const tmpl = ` {{range .Items}} - {{.Name}} (Owner: {{$.Owner}}) {{end}}
实战:生成安全的 HTML 页面
创建一个 Web 服务,展示用户提交的评论(已过滤 HTML 标签):
-
定义数据结构:
type PageData struct { Title string Comments []Comment } type Comment struct { Author string Text template.HTML // 已净化的 HTML } -
编写模板:
<!-- comments.html --> <h1>{{.Title}}</h1> <ul> {{range .Comments}} <li><strong>{{.Author}}:</strong> {{.Text}}</li> {{end}} </ul> -
加载并执行模板:
import "html/template" func handler(w http.ResponseWriter, r *http.Request) { data := PageData{ Title: "User Comments", Comments: []Comment{ {"Alice", "Great post!"}, {"Bob", "Thanks for sharing.<br>Very helpful."}, // <br> 会被保留 }, } t := template.Must(template.ParseFiles("comments.html")) t.Execute(w, data) } -
启动服务器:
http.HandleFunc("/", handler) http.ListenAndServe(":8080", nil)
访问 http://localhost:8080 将看到安全渲染的 HTML,且换行符 <br> 正常显示。
性能提示
- 预编译模板:避免在每次请求中调用
template.Parse。应在程序启动时解析模板并复用。 - 使用
template.Must:在初始化阶段捕获解析错误,避免运行时 panic。 - 缓存复杂逻辑:若模板包含大量计算,先在 Go 代码中处理好数据,再传入模板。
// 全局变量,程序启动时初始化
var pageTemplate = template.Must(template.ParseFiles("page.html"))
func handler(w http.ResponseWriter, r *http.Request) {
// 直接使用预编译模板
pageTemplate.Execute(w, data)
}
暂无评论,快来抢沙发吧!