Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

html/template - 安全的 HTML 模板

html/template 包实现了数据驱动的模板引擎,用于生成可对抗代码注入的安全 HTML 输出。

概述

html/template 包提供了与 text/template 相同的接口,但会自动对 HTML 输出进行转义,防止代码注入攻击。它理解 HTML、CSS、JavaScript 和 URI 上下文,并根据上下文自动添加适当的转义函数。

包导入

import "html/template"

基本使用

// 1. 创建模板
tmpl, err := template.New("name").Parse("Hello, {{.}}!")

// 2. 执行模板
err = tmpl.Execute(os.Stdout, "World")

// 3. 从文件解析
tmpl, err = template.ParseFiles("index.html")

// 4. 执行命名模板
err = tmpl.ExecuteTemplate(w, "index", data)

典型示例

示例 1:基本模板

package main

import (
    "html/template"
    "os"
)

func main() {
    // 创建并解析模板
    tmpl, err := template.New("greet").Parse("Hello, {{.}}!")
    if err != nil {
        panic(err)
    }
    
    // 执行模板
    tmpl.Execute(os.Stdout, "World")
}

运行

$ go run main.go
Hello, World!

示例 2:自动转义防止 XSS

package main

import (
    "html/template"
    "os"
)

func main() {
    // html/template 会自动转义危险内容
    tmpl, _ := template.New("test").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
    
    // 恶意输入
    malicious := "<script>alert('XSS')</script>"
    
    // 执行模板(自动转义)
    tmpl.ExecuteTemplate(os.Stdout, "T", malicious)
}

运行

$ go run main.go
Hello, &lt;script&gt;alert(&#39;XSS&#39;)&lt;/script&gt;!

示例 3:结构体字段访问

package main

import (
    "fmt"
    "html/template"
    "os"
)

type Person struct {
    Name string
    Age  int
    Addr string
}

func main() {
    tmpl := `
<!DOCTYPE html>
<html>
<head><title>{{.Name}}</title></head>
<body>
    <p>Name: {{.Name}}</p>
    <p>Age: {{.Age}}</p>
    <p>Address: {{.Addr}}</p>
</body>
</html>`
    
    t, _ := template.New("person").Parse(tmpl)
    
    person := Person{
        Name: "Alice",
        Age:  25,
        Addr: "<script>alert('XSS')</script>",
    }
    
    t.Execute(os.Stdout, person)
}

运行

$ go run main.go
<!DOCTYPE html>
<html>
<head><title>Alice</title></head>
<body>
    <p>Name: Alice</p>
    <p>Age: 25</p>
    <p>Address: &lt;script&gt;alert(&#39;XSS&#39;)&lt;/script&gt;</p>
</body>
</html>

示例 4:从文件加载模板

// index.html 文件内容:
/*
<!DOCTYPE html>
<html>
<head><title>{{.Title}}</title></head>
<body>
    <h1>{{.Heading}}</h1>
    <p>{{.Content}}</p>
</body>
</html>
*/

package main

import (
    "html/template"
    "net/http"
)

type PageData struct {
    Title   string
    Heading string
    Content string
}

func handler(w http.ResponseWriter, r *http.Request) {
    tmpl, err := template.ParseFiles("index.html")
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    
    data := PageData{
        Title:   "My Page",
        Heading: "Welcome",
        Content: "<script>alert('XSS')</script>",
    }
    
    tmpl.Execute(w, data)
}

func main() {
    http.HandleFunc("/", handler)
    http.ListenAndServe(":8080", nil)
}

一、核心类型(按字母顺序)

ErrorCode 类型

ErrorCode

定义

type ErrorCode string

说明

  • 模板错误代码类型
  • 用于标识不同类型的模板错误

常量

const (
    ErrOK                  ErrorCode = "OK"
    ErrPredefinedEscaper   ErrorCode = "ErrPredefinedEscaper"
    ErrNoSuchTemplate      ErrorCode = "ErrNoSuchTemplate"
    ErrOutputTimeout       ErrorCode = "ErrOutputTimeout"
    ErrBadTemplate         ErrorCode = "ErrBadTemplate"
    ErrBadContext          ErrorCode = "ErrBadContext"
    ErrPartialResult       ErrorCode = "ErrPartialResult"
    ErrUnsafe              ErrorCode = "ErrUnsafe"
    ErrUnknown             ErrorCode = "ErrUnknown"
)

Error 类型

Error

定义

type Error struct {
    Err  error
    Code ErrorCode
}

说明

  • 模板执行错误类型
  • 包含具体错误和错误代码

方法

  • Error() string - 错误信息

示例

package main

import (
    "fmt"
    "html/template"
    "os"
)

func main() {
    // 无效的模板语法
    _, err := template.New("test").Parse("{{.Invalid")
    if err != nil {
        if templateErr, ok := err.(*template.Error); ok {
            fmt.Printf("错误代码:%s\n", templateErr.Code)
            fmt.Printf("错误信息:%s\n", templateErr.Err)
        }
    }
}

FuncMap 类型

FuncMap

定义

type FuncMap map[string]interface{}

说明

  • 模板函数映射表
  • 键为函数名,值为函数

示例

package main

import (
    "fmt"
    "html/template"
    "os"
    "strings"
)

func main() {
    // 自定义函数
    funcMap := template.FuncMap{
        "upper": strings.ToUpper,
        "lower": strings.ToLower,
        "title": strings.Title,
    }
    
    tmpl := `{{.Name | upper}}
{{.Name | lower}}
{{.Name | title}}`
    
    t, _ := template.New("test").Funcs(funcMap).Parse(tmpl)
    
    data := struct{ Name string }{"hello world"}
    t.Execute(os.Stdout, data)
}

运行

$ go run main.go
HELLO WORLD
hello world
Hello World

HTML 类型

HTML

定义

type HTML string

说明

  • 安全的 HTML 字符串类型
  • 不会被自动转义
  • 用于标记已知安全的 HTML 内容

示例

package main

import (
    "html/template"
    "os"
)

func main() {
    tmpl := `{{.SafeHTML}}`
    
    t, _ := template.New("test").Parse(tmpl)
    
    data := struct {
        SafeHTML template.HTML
    }{
        SafeHTML: template.HTML("<b>Bold Text</b>"),
    }
    
    t.Execute(os.Stdout, data)
}

运行

$ go run main.go
<b>Bold Text</b>

HTMLAttr 类型

HTMLAttr

定义

type HTMLAttr string

说明

  • 安全的 HTML 属性类型
  • 用于 HTML 标签的属性名

示例

package main

import (
    "html/template"
    "os"
)

func main() {
    tmpl := `<div {{.Attr}}="value">Content</div>`
    
    t, _ := template.New("test").Parse(tmpl)
    
    data := struct {
        Attr template.HTMLAttr
    }{
        Attr: template.HTMLAttr(`class="highlight"`),
    }
    
    t.Execute(os.Stdout, data)
}

运行

$ go run main.go
<div class="highlight"="value">Content</div>

JS 类型

JS

定义

type JS string

说明

  • 安全的 JavaScript 代码类型
  • 不会被自动转义
  • 用于嵌入已知的安全 JavaScript

示例

package main

import (
    "html/template"
    "os"
)

func main() {
    tmpl := `<script>{{.Code}}</script>`
    
    t, _ := template.New("test").Parse(tmpl)
    
    data := struct {
        Code template.JS
    }{
        Code: template.JS(`alert("Hello");`),
    }
    
    t.Execute(os.Stdout, data)
}

运行

$ go run main.go
<script>alert("Hello");</script>

JSStr 类型

JSStr

定义

type JSStr string

说明

  • 安全的 JavaScript 字符串类型
  • 用于 JavaScript 上下文中的字符串值

示例

package main

import (
    "html/template"
    "os"
)

func main() {
    tmpl := `<script>var msg = {{.Msg}};</script>`
    
    t, _ := template.New("test").Parse(tmpl)
    
    data := struct {
        Msg template.JSStr
    }{
        Msg: template.JSStr(`"Hello, World!"`),
    }
    
    t.Execute(os.Stdout, data)
}

运行

$ go run main.go
<script>var msg = "Hello, World!";</script>

Srcset 类型

Srcset

定义

type Srcset string

说明

  • 安全的 srcset 属性类型
  • 用于 img 标签的 srcset 属性

示例

package main

import (
    "html/template"
    "os"
)

func main() {
    tmpl := `<img srcset="{{.Src}}" alt="image">`
    
    t, _ := template.New("test").Parse(tmpl)
    
    data := struct {
        Src template.Srcset
    }{
        Src: template.Srcset("image-320w.jpg 320w, image-480w.jpg 480w"),
    }
    
    t.Execute(os.Stdout, data)
}

运行

$ go run main.go
<img srcset="image-320w.jpg 320w, image-480w.jpg 480w" alt="image">

Template 类型

Template

定义

type Template struct {
    // 内部字段
}

说明

  • 模板的主要类型
  • 表示一个已解析的模板
  • 可并发安全执行

方法

  • AddParseTree(name, tree) (*Template, error) - 添加解析树
  • Clone() (*Template, error) - 克隆模板
  • DefinedNames() []string - 返回已定义的模板名
  • Delims(left, right) - 设置分隔符
  • Escape() error - 应用转义
  • Execute(wr, data) error - 执行模板
  • ExecuteTemplate(wr, name, data) error - 执行命名模板
  • Funcs(funcMap) - 添加自定义函数
  • Lookup(name) *Template - 查找命名模板
  • Name() string - 返回模板名
  • New(name) - 创建新模板
  • Option(opt) - 设置选项
  • Parse(text) - 解析模板字符串
  • ParseFiles(filenames) - 解析文件
  • ParseGlob(pattern) - 解析匹配的文件

URL 类型

URL

定义

type URL string

说明

  • 安全的 URL 类型
  • 不会被自动转义
  • 用于已知的安全 URL

示例

package main

import (
    "html/template"
    "os"
)

func main() {
    tmpl := `<a href="{{.URL}}">Link</a>`
    
    t, _ := template.New("test").Parse(tmpl)
    
    data := struct {
        URL template.URL
    }{
        URL: template.URL("https://example.com"),
    }
    
    t.Execute(os.Stdout, data)
}

运行

$ go run main.go
<a href="https://example.com">Link</a>

二、核心函数(按字母顺序)

Must - 错误处理辅助函数

**Must(t Template, err error) Template

说明

  • 辅助函数,用于简化错误处理
  • 如果 err 不为 nil,则 panic
  • 否则返回 t
  • 常用于包变量初始化

定义

func Must(t *Template, err error) *Template

示例

package main

import (
    "html/template"
    "os"
)

// 使用 Must 简化初始化
var tmpl = template.Must(template.New("test").Parse("Hello, {{.}}!"))

func main() {
    tmpl.Execute(os.Stdout, "World")
}

运行

$ go run main.go
Hello, World!

New - 创建模板

*New(name string) Template

说明

  • 创建指定名称的新模板
  • 通常与 Parse 或 ParseFiles 链式调用

定义

func New(name string) *Template

示例

package main

import (
    "fmt"
    "html/template"
    "os"
)

func main() {
    // 链式调用
    tmpl := template.New("greet")
    tmpl = template.Must(tmpl.Parse("Hello, {{.}}!"))
    
    // 或一行完成
    tmpl2 := template.Must(template.New("farewell").Parse("Goodbye, {{.}}!"))
    
    tmpl.Execute(os.Stdout, "Alice")
    fmt.Println()
    tmpl2.Execute(os.Stdout, "Alice")
}

运行

$ go run main.go
Hello, Alice!
Goodbye, Alice!

ParseFiles - 解析多个文件

*ParseFiles(filenames …string) (Template, error)

说明

  • 解析一个或多个模板文件
  • 返回的模板名为第一个文件的文件名(不含扩展名)
  • 至少需要一个文件
  • 如果出错返回 nil

定义

func ParseFiles(filenames ...string) (*Template, error)

示例

package main

import (
    "html/template"
    "net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
    // 解析多个文件
    tmpl, err := template.ParseFiles(
        "header.html",
        "content.html",
        "footer.html",
    )
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    
    tmpl.Execute(w, nil)
}

ParseGlob - 解析匹配的文件

*ParseGlob(pattern string) (Template, error)

说明

  • 解析匹配 glob 模式的所有文件
  • 返回的模板名为第一个匹配文件的文件名
  • 至少需要一个匹配文件

定义

func ParseGlob(pattern string) (*Template, error)

示例

package main

import (
    "html/template"
    "net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
    // 解析所有 .html 文件
    tmpl, err := template.ParseGlob("templates/*.html")
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    
    tmpl.Execute(w, nil)
}

三、Template 方法详解

AddParseTree - 添加解析树

**AddParseTree(name string, tree parse.Tree) (Template, error)

说明

  • 为模板添加解析树
  • 用于底层模板操作

Clone - 克隆模板

*Clone() (Template, error)

说明

  • 创建模板的副本
  • 用于创建模板变体

示例

package main

import (
    "html/template"
    "os"
)

func main() {
    base := template.Must(template.New("base").Parse("Base: {{.}}"))
    
    // 克隆并修改
    derived := template.Must(base.Clone())
    derived.Parse(" Derived: {{.}}")
    
    base.Execute(os.Stdout, "Test")
    fmt.Println()
    derived.Execute(os.Stdout, "Test")
}

DefinedNames - 获取已定义的模板名

DefinedNames() []string

说明

  • 返回所有已定义的模板名称
  • 用于检查可用模板

示例

package main

import (
    "fmt"
    "html/template"
)

func main() {
    tmpl := template.Must(template.New("main").Parse(`
        {{define "header"}}Header{{end}}
        {{define "footer"}}Footer{{end}}
    `))
    
    names := tmpl.DefinedNames()
    fmt.Printf("已定义的模板:%v\n", names)
}

运行

$ go run main.go
已定义的模板:[main header footer]

Delims - 设置分隔符

*Delims(left, right string) Template

说明

  • 设置模板动作的分隔符
  • 默认是 {{}}
  • 用于避免与某些语法冲突

示例

package main

import (
    "html/template"
    "os"
)

func main() {
    // 使用 [[ 和 ]] 作为分隔符
    tmpl := template.Must(template.New("test").Delims("[[", "]]").
        Parse("Hello, [[.]]!"))
    
    tmpl.Execute(os.Stdout, "World")
}

运行

$ go run main.go
Hello, World!

Escape - 应用转义

Escape() error

说明

  • 手动应用转义
  • 通常自动调用

Execute - 执行模板

Execute(wr io.Writer, data interface{}) error

说明

  • 将模板应用到数据并输出
  • 如果出错会停止执行
  • 模板可安全并发执行

定义

func (t *Template) Execute(wr io.Writer, data interface{}) error

示例

package main

import (
    "html/template"
    "os"
)

func main() {
    tmpl := template.Must(template.New("test").Parse("Name: {{.Name}}, Age: {{.Age}}"))
    
    data := struct {
        Name string
        Age  int
    }{
        Name: "Alice",
        Age:  25,
    }
    
    tmpl.Execute(os.Stdout, data)
}

运行

$ go run main.go
Name: Alice, Age: 25

ExecuteTemplate - 执行命名模板

ExecuteTemplate(wr io.Writer, name string, data interface{}) error

说明

  • 执行指定名称的模板
  • 用于执行嵌套或定义的模板

定义

func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error

示例

package main

import (
    "html/template"
    "os"
)

func main() {
    tmpl := template.Must(template.New("main").Parse(`
        {{define "greeting"}}Hello, {{.}}!{{end}}
        {{define "farewell"}}Goodbye, {{.}}!{{end}}
    `))
    
    // 执行不同的模板
    tmpl.ExecuteTemplate(os.Stdout, "greeting", "Alice")
    fmt.Println()
    tmpl.ExecuteTemplate(os.Stdout, "farewell", "Alice")
}

运行

$ go run main.go
Hello, Alice!
Goodbye, Alice!

Funcs - 添加自定义函数

*Funcs(funcMap FuncMap) Template

说明

  • 向模板添加自定义函数
  • 必须在 Parse 之前调用
  • 返回模板以便链式调用

定义

func (t *Template) Funcs(funcMap FuncMap) *Template

示例

package main

import (
    "html/template"
    "os"
    "strings"
)

func main() {
    funcMap := template.FuncMap{
        "upper": strings.ToUpper,
        "reverse": func(s string) string {
            runes := []rune(s)
            for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
                runes[i], runes[j] = runes[j], runes[i]
            }
            return string(runes)
        },
    }
    
    tmpl := template.Must(template.New("test").Funcs(funcMap).
        Parse("{{.Name | upper | reverse}}"))
    
    data := struct{ Name string }{"hello"}
    tmpl.Execute(os.Stdout, data)
}

运行

$ go run main.go
OLLEH

Lookup - 查找模板

*Lookup(name string) Template

说明

  • 查找指定名称的模板
  • 如果不存在返回 nil

示例

package main

import (
    "fmt"
    "html/template"
)

func main() {
    tmpl := template.Must(template.New("main").Parse(`
        {{define "header"}}Header{{end}}
    `))
    
    // 查找模板
    if t := tmpl.Lookup("header"); t != nil {
        fmt.Println("找到模板:header")
    }
    
    if t := tmpl.Lookup("footer"); t == nil {
        fmt.Println("模板不存在:footer")
    }
}

运行

$ go run main.go
找到模板:header
模板不存在:footer

Name - 获取模板名

Name() string

说明

  • 返回模板的名称

示例

package main

import (
    "fmt"
    "html/template"
)

func main() {
    tmpl := template.Must(template.New("myTemplate").Parse("Content"))
    fmt.Printf("模板名:%s\n", tmpl.Name())
}

运行

$ go run main.go
模板名:myTemplate

Option - 设置选项

*Option(opt …string) Template

说明

  • 设置模板选项
  • 如 “missingkey=error” 等

示例

package main

import (
    "html/template"
    "os"
)

func main() {
    // 设置缺失键的处理方式
    tmpl := template.Must(template.New("test").
        Option("missingkey=zero").
        Parse("Value: {{.MissingKey}}"))
    
    data := map[string]interface{}{}
    tmpl.Execute(os.Stdout, data)
}

Parse - 解析模板字符串

*Parse(text string) (Template, error)

说明

  • 解析模板字符串
  • 可多次调用以添加模板定义
  • 只有第一次调用可以包含模板定义外的文本

定义

func (t *Template) Parse(text string) (*Template, error)

示例

package main

import (
    "html/template"
    "os"
)

func main() {
    // 链式调用
    tmpl := template.Must(template.New("base").
        Parse("{{define "T1"}}T1 content{{end}}").
        Parse("{{define "T2"}}T2 content{{end}}"))
    
    tmpl.ExecuteTemplate(os.Stdout, "T1", nil)
    fmt.Println()
    tmpl.ExecuteTemplate(os.Stdout, "T2", nil)
}

运行

$ go run main.go
T1 content
T2 content

ParseFiles - 解析文件

*ParseFiles(filenames …string) (Template, error)

说明

  • 解析一个或多个文件
  • 作为 Template 方法时可向现有模板添加定义

示例

package main

import (
    "html/template"
    "os"
)

func main() {
    // 创建基础模板
    tmpl := template.New("base")
    
    // 添加文件定义
    tmpl.ParseFiles("header.html", "footer.html")
    
    tmpl.ExecuteTemplate(os.Stdout, "header", nil)
}

四、使用场景

场景 1:Web 页面渲染

package main

import (
    "html/template"
    "net/http"
)

type PageData struct {
    Title   string
    Content string
    User    string
}

func handler(w http.ResponseWriter, r *http.Request) {
    tmpl := template.Must(template.ParseFiles("page.html"))
    
    data := PageData{
        Title:   "My Page",
        Content: "Welcome to my site!",
        User:    "Alice",
    }
    
    tmpl.Execute(w, data)
}

func main() {
    http.HandleFunc("/", handler)
    http.ListenAndServe(":8080", nil)
}

场景 2:邮件模板

package main

import (
    "bytes"
    "html/template"
)

type EmailData struct {
    Username string
    Link     string
}

func sendEmail(username, email string) {
    tmplStr := `
    <html>
    <body>
        <h1>Hello, {{.Username}}!</h1>
        <p>Click <a href="{{.Link}}">here</a> to activate.</p>
    </body>
    </html>`
    
    tmpl := template.Must(template.New("email").Parse(tmplStr))
    
    data := EmailData{
        Username: username,
        Link:     "https://example.com/activate?token=abc123",
    }
    
    var buf bytes.Buffer
    tmpl.Execute(&buf, data)
    
    // 发送邮件...
}

场景 3:代码生成

package main

import (
    "html/template"
    "os"
)

type APIData struct {
    Name   string
    Params []string
}

func generateAPI(data APIData) {
    tmplStr := `
func {{.Name}}({{range .Params}}{{.}}, {{end}}) error {
    // Implementation here
    return nil
}`
    
    tmpl := template.Must(template.New("api").Parse(tmplStr))
    tmpl.Execute(os.Stdout, data)
}

场景 4:布局模板

// layout.html
/*
{{define "header"}}
<!DOCTYPE html>
<html>
<head><title>{{.Title}}</title></head>
<body>
{{end}}

{{define "footer"}}
</body>
</html>
{{end}}
*/

// content.html
/*
{{template "header" .}}
<h1>{{.Heading}}</h1>
<p>{{.Content}}</p>
{{template "footer" .}}
*/

package main

import (
    "html/template"
    "net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
    tmpl := template.Must(template.ParseFiles("layout.html", "content.html"))
    
    data := struct {
        Title   string
        Heading string
        Content string
    }{
        Title:   "My Page",
        Heading: "Welcome",
        Content: "Hello, World!",
    }
    
    tmpl.ExecuteTemplate(w, "content", data)
}

五、最佳实践

1. 始终使用 html/template 处理 HTML

// 推荐:使用 html/template
import "html/template"
tmpl := template.Must(template.New("test").Parse("{{.}}"))

// 不推荐:使用 text/template 处理 HTML
import "text/template"  // 不会自动转义!

2. 使用 Must 简化初始化

// 推荐:使用 Must
var tmpl = template.Must(template.ParseFiles("index.html"))

// 不推荐:每次都检查错误
tmpl, err := template.ParseFiles("index.html")
if err != nil {
    panic(err)
}

3. 预编译模板

// 推荐:包变量预编译
var templates = template.Must(template.ParseGlob("templates/*.html"))

func handler(w http.ResponseWriter, r *http.Request) {
    templates.ExecuteTemplate(w, "index", data)
}

// 不推荐:每次都解析
func handler(w http.ResponseWriter, r *http.Request) {
    tmpl, _ := template.ParseFiles("index.html")
    tmpl.Execute(w, data)
}

4. 使用自定义类型标记安全内容

// 已知安全的 HTML
data := struct {
    SafeHTML template.HTML
}{
    SafeHTML: template.HTML("<b>Bold</b>"),
}

// 不要直接使用 string
data := struct {
    Content string  // 会被转义
}{
    Content: "<b>Bold</b>",  // 输出 &lt;b&gt;Bold&lt;/b&gt;
}

5. 自定义函数要安全

// 安全的自定义函数
funcMap := template.FuncMap{
    "safeFunc": func(s string) string {
        return strings.ToUpper(s)  // 安全
    },
}

// 不安全的自定义函数
funcMap := template.FuncMap{
    "unsafeFunc": func(s string) template.HTML {
        return template.HTML(s)  // 危险!可能引入 XSS
    },
}

六、快速参考

核心类型

类型说明用途
Template模板类型表示已解析的模板
FuncMap函数映射自定义模板函数
HTML安全 HTML不转义的 HTML 字符串
HTMLAttr安全属性HTML 属性名
JS安全 JSJavaScript 代码
JSStr安全 JS 字符串JS 中的字符串
URL安全 URLURL 地址
Srcset安全 Srcsetimg srcset 属性

核心函数

函数说明示例
New(name)创建模板template.New("test")
Must(t, err)错误处理template.Must(Parse(...))
ParseFiles(…)解析文件template.ParseFiles("a.html")
ParseGlob(pattern)解析匹配文件template.ParseGlob("*.html")

Template 方法

方法说明示例
Parse(text)解析字符串tmpl.Parse("{{.}}")
Execute(w, data)执行模板tmpl.Execute(os.Stdout, data)
ExecuteTemplate(w, name, data)执行命名模板tmpl.ExecuteTemplate(w, "name", data)
Funcs(funcMap)添加函数tmpl.Funcs(funcMap)
Clone()克隆模板tmpl.Clone()
Delims(left, right)设置分隔符tmpl.Delims("[[", "]]")
DefinedNames()获取模板名列表tmpl.DefinedNames()
Lookup(name)查找模板tmpl.Lookup("name")

上下文转义

上下文转义方式示例
HTML 正文HTML 转义< > & ' " → 实体
HTML 属性属性转义' " → 实体
URLURL 编码特殊字符 → %XX
JavaScriptJS 转义引号、换行 → 转义
CSSCSS 转义特殊字符 → 转义

安全类型

类型转义使用场景
string普通文本
template.HTML已知安全的 HTML
template.URL已知安全的 URL
template.JS已知安全的 JS

七、与其他包配合

与 net/http 配合

package main

import (
    "html/template"
    "net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
    tmpl := template.Must(template.ParseFiles("index.html"))
    tmpl.Execute(w, struct{ Name string }{"Alice"})
}

func main() {
    http.HandleFunc("/", handler)
    http.ListenAndServe(":8080", nil)
}

与 bytes 配合

package main

import (
    "bytes"
    "fmt"
    "html/template"
)

func renderTemplate(tmplStr string, data interface{}) (string, error) {
    tmpl := template.Must(template.New("test").Parse(tmplStr))
    
    var buf bytes.Buffer
    err := tmpl.Execute(&buf, data)
    if err != nil {
        return "", err
    }
    
    return buf.String(), nil
}

func main() {
    result, _ := renderTemplate("Hello, {{.Name}}!", struct{ Name string }{"Alice"})
    fmt.Println(result)
}

与 strings 配合

package main

import (
    "html/template"
    "os"
    "strings"
)

func main() {
    funcMap := template.FuncMap{
        "upper": strings.ToUpper,
        "split": strings.Split,
        "join":  strings.Join,
    }
    
    tmpl := template.Must(template.New("test").Funcs(funcMap).
        Parse("{{.Text | upper}}"))
    
    data := struct{ Text string }{"hello"}
    tmpl.Execute(os.Stdout, data)
}

八、注意事项

1. 自动转义是上下文相关的

// HTML 上下文
tmpl := `<div>{{.}}</div>`
// 输出:<div>&lt;script&gt;alert(&#39;XSS&#39;)&lt;/script&gt;</div>

// URL 上下文
tmpl := `<a href="{{.}}">Link</a>`
// 如果 . 是 "javascript:alert('XSS')",输出会被过滤为 "#ZgotmplZ"

2. 不要滥用安全类型

// 危险:直接使用 template.HTML
data := template.HTML(userInput)  // 危险!

// 安全:先验证和清理
if isValidHTML(userInput) {
    data := template.HTML(userInput)
}

3. 模板作者必须是可信的

// html/template 假设模板作者是可信的
// 数据参数是不可信的
// 所以不要让用户上传或修改模板

4. Funcs 必须在 Parse 之前调用

// 正确
tmpl.Funcs(funcMap).Parse("...")

// 错误:panic
tmpl.Parse("...").Funcs(funcMap)

5. 模板可并发执行

// 安全:同一个模板可被多个 goroutine 并发执行
tmpl := template.Must(template.ParseFiles("index.html"))

go func() { tmpl.Execute(os.Stdout, data1) }()
go func() { tmpl.Execute(os.Stdout, data2) }()

九、常见问题

Q1: html/template 和 text/template 有什么区别?

A:

  • html/template 会自动根据上下文转义输出,防止 XSS
  • text/template 不会转义,适合生成纯文本
  • 生成 HTML 时应始终使用 html/template

Q2: 如何禁用自动转义?

A:

  • 使用安全类型(template.HTML、template.URL 等)标记已知安全的内容
  • 不要完全禁用转义,这会带来安全风险

Q3: 如何处理富文本编辑器内容?

A:

  1. 使用 HTML 清理库(如 bluemonday)
  2. 清理后使用 template.HTML 标记
  3. 在模板中输出

最后更新:2026-04-04
Go 版本:Go 1.23+