文章目录

Go 模板:text/template 与 html/template

发布于 2026-04-02 13:32:08 · 浏览 6 次 · 评论 0 条

Go 模板:text/template 与 html/template

Go 语言内置了两个模板包:text/templatehtml/template。它们语法几乎一致,但用途和安全机制完全不同。选择错误的包可能导致严重的安全漏洞或功能失效


区分使用场景

text/template 用于生成任意纯文本内容,比如配置文件、日志、邮件正文等。它不做任何特殊转义,直接输出数据。

html/template 专为生成 HTML 设计。它会自动对变量内容进行 HTML 转义(escaping),防止跨站脚本攻击(XSS)。例如,若变量包含 <script>html/template 会将其转为 &lt;script&gt;,确保浏览器不会执行恶意代码。

永远不要用 text/template 生成 HTML。即使当前数据“看起来安全”,未来数据源变化也可能引入风险。


基础语法:两者通用

两个包共享相同的模板语法。以下步骤适用于任一包:

  1. 导入对应包:

    import "text/template"
    // 或
    import "html/template"
  2. 定义模板字符串,使用双花括号 {{}} 包裹指令:

    const tmpl = `Hello, {{.Name}}! You have {{.Count}} messages.`
  3. 解析模板

    t := template.Must(template.New("example").Parse(tmpl))
  4. 准备数据结构(通常为 struct 或 map):

    data := struct {
        Name  string
        Count int
    }{
        Name:  "Alice",
        Count: 5,
    }
  5. 执行模板并输出到 io.Writer(如 os.Stdouthttp.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 &lt;b&gt;Important&lt;/b&gt; 自动 HTML 转义

如果确实需要在 HTML 中渲染原始 HTML(如富文本编辑器输出),必须显式标记为“安全”:

  1. 定义类型 template.HTML(来自 html/template 包):

    import "html/template"
    
    data := struct {
        TrustedHTML template.HTML
    }{
        TrustedHTML: "<b>Bold text</b>",
    }
  2. 在模板中直接使用

    const tmpl = `{{.TrustedHTML}}`

此时 html/template 会跳过转义,直接输出 <b>Bold text</b>仅对完全可信的内容使用此方法


常见陷阱与规避方法

  1. 混用包导致编译错误
    text/templatehtml/templateTemplate 类型不兼容。不能将 *html/template.Template 赋值给 *text/template.Template 变量。确保全程使用同一包

  2. 误以为 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}}; ...`))
  3. 模板嵌套时路径错误
    在嵌套模板或 range 循环中,. 会改变指向。使用 $` 引用根对象: ```go const tmpl = ` {{range .Items}} - {{.Name}} (Owner: {{$.Owner}}) {{end}}


实战:生成安全的 HTML 页面

创建一个 Web 服务,展示用户提交的评论(已过滤 HTML 标签):

  1. 定义数据结构

    type PageData struct {
        Title   string
        Comments []Comment
    }
    
    type Comment struct {
        Author string
        Text   template.HTML // 已净化的 HTML
    }
  2. 编写模板

    <!-- comments.html -->
    <h1>{{.Title}}</h1>
    <ul>
    {{range .Comments}}
      <li><strong>{{.Author}}:</strong> {{.Text}}</li>
    {{end}}
    </ul>
  3. 加载并执行模板

    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)
    }
  4. 启动服务器

    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)
}

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文