Go html/template

  • 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("{[", "]}")

使用流程

  1. 定义模板文件

定义模板文件时需按照相关语法规则去编写

  1. 解析模板文件

使用模板解析方法获取模板对象

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

对模板实例调用上面的方法去解析模板字符串或模板文件

  1. 模板渲染

使用数据填充模板

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
    }
}
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • html html包提供了用于转义和解转义HTML文本的函数。 func EscapeString EscapeS...
    DevilRoshan阅读 1,262评论 0 0
  • 输入与输出-fmt包 时间与日期-time包 命令行参数解析-flag包 日志-log包 IO操作-os包 IO操...
    思考的山羊阅读 6,472评论 0 5
  • Go语言内置的net/http包十分的优秀,提供了HTTP客户端和服务端的实现。 net/http介绍 Go语言内...
    雪上霜阅读 744评论 0 0
  • 前言 web到目前为止走过了1.0、2.0、移动互联网、本地应用化几个阶段,这使得js变得炙手可热,许多原来在se...
    白昔月阅读 1,792评论 3 6
  • https://juejin.im/post/5c403b98f265da612d1984c9 template包...
    GeminiGirl0604阅读 1,864评论 0 1