- Golang的
html/template
包实现了数据驱动的模板,用于生成可对抗代码注入且安全HTML输出。 -
html/template
包提供了和text/template
包相同的接口,html/template
是对text/template
的二次封装并增加了安全性的处理。
模板渲染存在跨站脚本攻击的风险,本质上是网站将用户的输入不作转义写入到生成的页面中。若用户提交一段浏览器脚本,则会在用户页面中执行,进而产生不可预知的风险。
html/template
会自动开启安全模式将需要编码的数据处理成纯文本,各种不同的转义上下文可以安全的嵌入HTML模板,比如JavaScript、CSS、URI上下文。
作用机制
- 模板文件通常定义为
.tmpl
、.tpl
为后缀,也可使用其他后缀。 - 模板文件必须使用UTF8编码
- 模板文件使用
{{
和}}
作为分界符包裹和标识需要传入的数据 - 传递给模板的数据通过点号
.
来访问,若数据是结构体或哈希则通过{{.FieldName}}
来访问字段。 - 除
{{
和}}
包裹内容外其他内容军不做修改原样输出
分界符
- 模板文件使用
{{
和}}
作为分界符包裹和标识需要传入的数据 - 可使用
template
实例的Delims()
方法修改默认分界符
//正则匹配解析模板
tpl, err := template.ParseGlob("./view/*.html")
if err != nil {
fmt.Printf("create template failed, %v\n", err)
}
//修改定界符
tpl.Delims("{[", "]}")
使用流程
- 定义模板文件
定义模板文件时需按照相关语法规则去编写
- 解析模板文件
使用模板解析方法获取模板对象
func (t *Template) Parse(src string) (*Template, error)
func ParseFiles(filenames ...string) (*Template, error)
func ParseGlob(pattern string) (*Template, error)
也可使用New()
方法创建名为name
的模板实例
func New(name string) *Template
对模板实例调用上面的方法去解析模板字符串或模板文件
- 模板渲染
使用数据填充模板
func (t *Template) Execute(wr io.Writer, data interface{}) error
func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error
例如:
创建入口文件
$ vim main.go
package main
import (
"fmt"
"html/template"
"net/http"
)
func main() {
http.HandleFunc("/", defaultHandler)
err := http.ListenAndServe(":9090", nil)
if err != nil {
fmt.Printf("http server start failed, %v\n", err)
}
}
func defaultHandler(rw http.ResponseWriter, rq *http.Request) {
//定义模板
tplname := "./view/default.html"
//解析模板
tpl, err := template.ParseFiles(tplname)
if err != nil {
fmt.Printf("parse template failed, %v\n", err)
return
}
//渲染模板
data := "admin"
err = tpl.Execute(rw, data)
if err != nil {
fmt.Printf("render template faild, %v\n", err)
return
}
}
创建模板文件
$ vim ./view/default.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
{{.}}
</body>
</html>
测试访问
$ curl -i http://127.0.0.1:9090
模板语法
-
{{.}}
中的点表示当前对象
渲染map
类型
//渲染模板
data := map[string]interface{}{"Id": 1, "Name": "admin"}
err = tpl.Execute(rw, data)
<body>
{{.}}
<p>{{.Id}}</p>
<p>{{.Name}}</p>
</body>
渲染struct
类型
type User struct {
Id int
Name string
}
//渲染模板
data := User{Id: 100, Name: "admin"}
err = tpl.Execute(rw, data)
<body>
{{.}}
<p>{{.Id}}</p>
<p>{{.Name}}</p>
</body>
渲染列表
//渲染模板
list := []string{"manager", "administrator", "operator"}
data := map[string]interface{}{"id": 1, "list": list}
err = tpl.Execute(rw, data)
{{range $index,$item := .list}}
<p>{{$index}} - {{$item}}</p>
{{end}}
模板嵌套
define
{{define "tplname"}}
模板文件中建议为每个模板文件显示地定义模板名称,否则会因模板对象名与模板名不一致,无法解析。
在模板中嵌入其他模板,嵌入的模板可以是单独的文件,也可以是通过define
定义的模板。
$ vim ./view/header.html
{{define "header"}}
<div>header {{$.id}}</div>
{{end}}
template
- 使用
{{template "tplName" .}}
引入其他模板 - 使用
.
点号访问当前数据域 - 使用
$.
访问绝对顶层数据域
//定义模板
layout := "./view/layout.html"
tplname := "./view/default.html"
//解析模板
tpl, err := template.ParseFiles(layout, tplname)
解析模板时被嵌套的模板一定要在后面解析
$ vim ./view/layout.html
{{define "layout"}}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>LAYOUT {{$.id}}</h1>
{{template "header" .}}
<div>{{block "content" .}}{{end}}</div>
</body>
</html>
{{end}}
被嵌套模板无法渲染变量
$ vim ./view/default.html
{{template "layout" .}}
{{define "content"}}
<p>default</p>
{{end}}
block
{{block "name" pipeline}}
T1
{{end}}
block
是定义模板{{define "name"}}T1{{end}}
和执行{{template "name" pipeline}}
的缩写,典型用法是定义一组模板,通过在其中重新定义块模块进行自定义。
定义基础布局父模板
$ vim view/layout.html
{{define "layout"}}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>LAYOUT {{$.id}}</h1>
{{template "header" .}}
<div>{{block "content" .}}{{end}}</div>
</body>
</html>
{{end}}
定义子模板继承自父模板
$ vim ./view/default.html
{{template "layout" .}}
{{define "content"}}
<p>default</p>
{{end}}
使用template.ParseGlob
按正则匹配规则解析模板,再通过ExecuteTemplate
渲染指定的模板。
func defaultHandler(rw http.ResponseWriter, rq *http.Request) {
//正则匹配解析模板
tpl, err := template.ParseGlob("./view/*.html")
if err != nil {
fmt.Printf("create template failed, %v\n", err)
}
//渲染模板
name := "default.html"
list := []string{"manager", "administrator", "operator"}
data := map[string]interface{}{"id": 1, "list": list}
err = tpl.ExecuteTemplate(rw, name, data)
if err != nil {
fmt.Printf("render template faild, %v\n", err)
return
}
}
有疑问加站长微信联系(非本文作者)