[Go] Template 使用简介
Golang 提供了两个标准库用来处理模板 text/template 和 html/template。我们使用 html/template 格式化 html 字符。
模板引擎
模板引擎很多,Python 的 jinja,nodejs 的 jade 等都很好。所谓模板引擎,则将模板和数据进行渲染的输出格式化后的字符程序。对于 Go,执行这个流程大概需要三步。
- 创建模板对象
- 加载模板字串
- 执行渲染模板
其中最后一步就是把加载的字符和数据进行格式化。其过程可以总结下图:
warming up
Go 提供的标准库 html/template 提供了很多处理模板的接口。我们的项目结构为:
├── main.go
└── templates
├── index.html
└── layout.html
templates 文件夹有两个文件,分别为模板文件。 layout.html 文件如下:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>layout</title>
</head>
<body>
<h3>This is layout</h3>
template data: {{ . }}
</body>
</html>
我们可以使用 ParseFiles 方法加载模板,该方法会返回一个模板对象和错误,接下来就可以使用模板对象执行模板,注入数据对象。Go 的提供了一些模板标签,称之为 action,. 也是一种 action,更多的action 稍后解释。
func templateHandler(w http.ResponseWriter, r *http.Request) {
t, _ := template.ParseFiles("templates/layout.html")
fmt.Println(t.Name())
t.Execute(w, "Hello world")
}
我们打印了 t 模板对象的 Name 方法,实际上,每一个模板,都有一个名字,如果不显示指定这个名字,Go 将会把文件名(包括扩展名当成名字)本例则是 layout.html。访问之后可以看见返回的 html 字串:
$ curl -i http://127.0.0.1:8000/
HTTP/1.1 200 OK
Date: Fri, 09 Dec 2016 09:04:36 GMT
Content-Length: 223
Content-Type: text/html; charset=utf-8 <!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>layout</title>
</head>
<body>
<h3>This is layout</h3>
template data: Hello world
</body>
</html>
Go 不仅可以解析模板文件,也可以直接解析模板字串,这就是标准的处理,新建 -> 加载 -> 执行 三部曲:
func templateHandler(w http.ResponseWriter, r *http.Request) {
tmpl := `<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>Go Web Programming</title>
</head>
<body>
{{ . }}
</body>
</html>` t := template.New("layout.html")
t, _ = t.Parse(tmpl)
fmt.Println(t.Name())
t.Execute(w, "Hello World")
}
实际开发中,最终的页面很可能是多个模板文件的嵌套结果。Go 的 ParseFiles 也支持加载多个模板文件,不过模板对象的名字则是第一个模板文件的文件名。
func templateHandler(w http.ResponseWriter, r *http.Request) {
t, _ :=template.ParseFiles("templates/layout.html", "templates/index.html")
fmt.Println(t.Name())
t.Execute(w, "Hello world")
}
可见打印的还是 layout.html 的名字,执行的模板的时候,并没有 index.html 的模板内容。
此外,还有 ParseGlob 方法,可以通过 glob 通配符加载模板。
模板命名与嵌套
模板命名
前文已经提及,模板对象是有名字的,可以在创建模板对象的时候显示命名,也可以让 Go 自动命名。可是涉及到嵌套模板的时候,该如何命名模板呢,毕竟模板文件有好几个?
Go 提供了 ExecuteTemplate 方法,用于执行指定名字的模板。例如加载 layout.html 模板的时候,可以指定 layout.html
func templateHandler(w http.ResponseWriter, r *http.Request) {
t, _ :=template.ParseFiles("templates/layout.html")
fmt.Println(t.Name())
t.ExecuteTemplate(w, "layout", "Hello world")
}
似乎和 Execute 方法没有太大的差别。下面修改一下 layout.html 文件:
{{ define "layout" }} <!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>layout</title>
</head>
<body>
<h3>This is layout</h3>
template data: {{ . }}
</body>
</html> {{ end }}
在模板文件中,使用了 define 这个 action 给模板文件命名了。虽然我们 ParseFiles 方法返回的模板对象 t 的名字还是 layout.html, 但是 ExecuteTemplate 执行的模板却是 html 文件中定义的layout。
不仅可以通过 define 定义模板,还可以通过 template action 引入模板,类似 jinja 的 include 特性。修改 layout.html 和 index.html
{{ define "layout" }}
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>layout</title>
</head>
<body>
<h3>This is layout</h3>
template data: {{ . }} {{ template "index" }}
</body>
</html>
{{ end }}
index.html
{{ define "index" }} <div style="background: yellow">
this is index.html
</div> {{ end }}
Go 的代码也需要修改,使用 ParseFiles 加载需要渲染的模板文件:
func templateHandler(w http.ResponseWriter, r *http.Request) {
t, _ :=template.ParseFiles("templates/layout.html", "templates/index.html")
t.ExecuteTemplate(w, "layout", "Hello world")
}
访问可以看到 index 被 layout 模板 include 了:
$ curl http://127.0.0.1:8000/ <!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>layout</title>
</head>
<body>
<h3>This is layout</h3>
template data: Hello world <div style="background: yellow">
this is index.html
</div>
</body>
</html>
单文件嵌套
总而言之,创建模板对象后和加载多个模板文件,执行模板文件的时候需要指定 base 模板(layout),在 base 模板中可以 include 其他命名的模板。无论点 .,define,template 这些花括号包裹的东西都是 Go 的 action(模板标签)
Action
action 是 Go 模板中用于动态执行一些逻辑和展示数据的形式。大致分为下面几种:
- 条件语句
- 迭代
- 封装
- 引用
我们已经见识了 template 引用的用法,下面么再看看其他的用法
条件判断
条件判断的语法很简单:
{{ if arg }}
some content
{{ end }} {{ if arg }}
some content
{{ else }}
other content
{{ end }}
arg 可以是基本数据结构,也可以是表达式:if-end 包裹的内容为条件为真的时候展示。与 if 语句一样,模板也可以有 else 语句。
func templateHandler(w http.ResponseWriter, r *http.Request) {
t, _ := template.ParseFiles("templates/layout.html")
rand.Seed(time.Now().Unix())
t.ExecuteTemplate(w, "layout", rand.Intn(10) > 5)
}
layout.html 文件
{{ define "layout" }}
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>layout</title>
</head>
<body>
<h3>This is layout</h3>
template data: {{ . }} {{ if . }}
Number is greater than 5!
{{ else }}
Number is 5 or less!
{{ end }}
</body>
</html>
{{ end }}
此时就能看见,当 . 的值为 true 的时候显示 if 的逻辑,否则显示 else 的逻辑。
迭代
对于一些数组,切片或者是 map,可以使用迭代的 action,与 Go 的迭代类似,使用 range 进行处理:
func templateHandler(w http.ResponseWriter, r *http.Request) {
t := template.Must(template.ParseFiles("templates/layout.html"))
daysOfWeek := []string{"Mon", "Tue", "Wed", "Ths", "Fri", "Sat", "Sun"}
t.ExecuteTemplate(w, "layout", daysOfWeek)
}
layout.html 文件
{{ define "layout" }}
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>layout</title>
</head>
<body>
<h3>This is layout</h3>
template data: {{ . }} {{ range . }}
<li>{{ . }}</li>
{{ end }}
</body>
</html>
{{ end }}
可以看见输出了一堆 li 列表。迭代的时候,还可以使用 $ 设置循环变量:
{{ range $key, $value := . }}
<li>key: {{ $key }}, value: {{ $value }}</li>
{{ else }}
empty
{{ end }}
可以看见和迭代切片很像。range 也可以使用 else 语句:
func templateHandler(w http.ResponseWriter, r *http.Request) {
t := template.Must(template.ParseFiles("templates/layout.html"))
daysOfWeek := []string{}
t.ExecuteTemplate(w, "layout", daysOfWeek)
}
layout.html 部分内容
{{ range . }}
<li>{{ . }}</li>
{{ else }}
empty
{{ end }}
当 range 的结构为空的时候,则会执行 else 分支的逻辑。
with封装
with 语言在 Python 中可以开启一个上下文环境。对于 Go 模板,with 语句类似,其含义就是创建一个封闭的作用域,在其范围内,可以使用 . action,而与外面的 . 无关,只与 with 的参数有关:
{{ with arg }}
此时的点 . 就是arg
{{ end }}
{{ define "layout" }}
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>layout</title>
</head>
<body>
<h3>This is layout</h3>
template data: {{ . }} {{ with "world"}}
Now the dot is set to {{ . }}
{{ end }}
</body>
</html>
{{ end }}
访问结果如下:
$ curl http://127.0.0.1:8000/ <!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>layout</title>
</head>
<body>
<h3>This is layout</h3>
template data: [Mon Tue Wed Ths Fri Sat Sun] Now the dot is set to world </body>
</html>
可见 with 语句的 . 与其外面的 . 是两个不相关的对象。with 语句也可以有 else。else 中的 . 则和 with 外面的 . 一样,毕竟只有 with 语句内才有封闭的上下文:
{{ with ""}}
Now the dot is set to {{ . }}
{{ else }}
{{ . }}
{{ end }}
访问效果为:
$ curl http://127.0.0.1:8000/ <!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>layout</title>
</head>
<body>
<h3>This is layout</h3>
template data: [Mon Tue Wed Ths Fri Sat Sun] [Mon Tue Wed Ths Fri Sat Sun] </body>
</html>
引用
我们已经介绍了模板嵌套引用的技巧。引用除了模板的 include,还包括参数的传递。
func templateHandler(w http.ResponseWriter, r *http.Request) {
t := template.Must(template.ParseFiles("templates/layout.html", "templates/index.html"))
daysOfWeek := []string{"Mon", "Tue", "Wed", "Ths", "Fri", "Sat", "Sun"}
t.ExecuteTemplate(w, "layout", daysOfWeek)
}
修改 layout.html, layout 中引用了 index 模板:
{{ define "layout" }}
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>layout</title>
</head>
<body>
<h3>This is layout</h3>
layout template data: ({{ . }}) {{ template "index" }} </body>
</html>
{{ end }}
index.html模板的内容也打印了 .:
{{ define "index" }} <div style="background: yellow">
this is index.html ({{ . }})
</div> {{ end }}
访问的效果如下,index.html 中的点并没有数据。
$ curl http://127.0.0.1:8000/ <!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>layout</title>
</head>
<body>
<h3>This is layout</h3>
layout template data: ([Mon Tue Wed Ths Fri Sat Sun]) <div style="background: yellow">
this is index.html ()
</div> </body>
</html>
我们可以修改引用语句 {{ template "index" . }},把参数传给子模板,再次访问,就能看见 index.html 模板也有数据啦。
<div style="background: yellow">
this is index.html ([Mon Tue Wed Ths Fri Sat Sun])
</div>
参数,变量和管道
模板的参数可以是go中的基本数据类型,如字串,数字,布尔值,数组切片或者一个结构体。在模板中设置变量可以使用 $variable := value。我们在 range 迭代的过程使用了设置变量的方式。
Go 还有一个特性就是模板的管道函数,熟悉 django 和 jinja 的开发者应该很熟悉这种手法。通过定义函数过滤器,实现模板的一些简单格式化处理。并且通过管道哲学,这样的处理方式可以连成一起。
{{ p1 | p2 | p3 }}
例如 模板内置了一些函数,比如格式化输出:
{{ 12.3456 | printf "%.2f" }}
函数
既然管道符可以成为模板中的过滤器,那么除了内建的函数,能够自定义函数可以扩展模板的功能。幸好 Go 的模板提供了自定义模板函数的功能。
想要创建一个定义函数只需要两步:
- 创建一个 FuncMap 类型的 map,key 是模板函数的名字,value 是其函数的定义。
- 将 FuncMap 注入到模板中。
func templateHandler(w http.ResponseWriter, r *http.Request) {
funcMap := template.FuncMap{"fdate": formDate}
t := template.New("layout").Funcs(funcMap)
t = template.Must(t.ParseFiles("templates/layout.html", "templates/index.html"))
t.ExecuteTemplate(w, "layout", time.Now())
}
然后在模板中使用 {{ . | fdate }},当然也可以不适用管道过滤器,而是使用正常的函数调用形式,{{ fdate . }} 。
注意,函数的注入,必须要在 parseFiles 之前,因为解析模板的时候,需要先把函数编译注入。
智能上下文
上面所介绍的特性,基本上是大多数模板引擎都具有的功能。Go 还提供了一个更有意思的特性。那就是根据上下文显示模板的内容。例如字符的转义,会根据所显示的上下文环境而智能变化。比如同样的 html 标签,在 Js 和 html 环境中,其转义的内容是不一样的:
func templateHandler(w http.ResponseWriter, r *http.Request) {
t, _ := template.ParseFiles("templates/layout.html")
content := `I asked: <i>What's up?</i>`
t.ExecuteTemplate(w, "layout", content)
}
模板文件:
{{ define "layout" }}
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>layout</title>
</head>
<body>
<h3>This is layout</h3>
layout template data: ({{ . }}) <div><a href="/{{ . }}">Path</a></div>
<div><a href="/?q={{ . }}">Query</a></div>
<div><a onclick="f('{{ . }}')">Onclick</a></div> </body>
</html>
{{ end }}
访问结果
layout template data: (I asked: <i>What's up?</i>) <div><a href="/I%20asked:%20%3ci%3eWhat%27s%20up?%3c/i%3e">Path</a></div>
<div><a href="/?q=I%20asked%3a%20%3ci%3eWhat%27s%20up%3f%3c%2fi%3e">Query</a></div>
<div><a onclick="f('I asked: \x3ci\x3eWhat\x27s up?\x3c\/i\x3e')">Onclick</a></div>
可以看见 Go 会自动为我们处理 html 标签的转义。这对 web 安全具有重要作用。避免了一些 XSS 攻击。
XSS安全
安全是一个很大的话题,XSS 安全也包含很多内容,关于模板我们已经介绍了很多内容。XSS 安全就简单介绍一下即可。
XSS 主要分为三种,我们先测试其中一种。即通过提交待 script 标签的内容执行 js。例如下面的 html
layout.html 加一个表单
<form action="/" method="post">
Comment: <input name="comment" type="text">
<hr/>
<button id="submit">Submit</button>
</form>
一个最普通不过的表单。Go 的处理函数为:
func templateHandler(w http.ResponseWriter, r *http.Request) {
t, _ := template.ParseFiles("templates/layout.html")
t.ExecuteTemplate(w, "layout", r.FormValue("comment"))
}
提交一段 js,可以看到 Go 在表达处理的时候,自动帮我们做了 xss 过滤
当然,如果不想转义标签,需要使用 template.HTML 方法包裹:
func templateHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-XSS-Protection", "0")
t, _ := template.ParseFiles("templates/layout.html")
t.ExecuteTemplate(w, "layout", template.HTML(r.FormValue("comment")))
}
开发者尤其要注意 XSS 的安全处理,然而 XSS 原不是这么简单,更多的内容请阅读安全相关的资料。
【补充】
显示 字典 数据
package main
import (
"fmt"
"html/template"
"net/http"
)
func handler(w http.ResponseWriter, r *http.Request) {
t, _ := template.ParseFiles("index.html")
err := t.Execute(w, map[string]string{"Title": "My title", "Body": "Hi this is my body"})
if err != nil {
panic(err)
}
}
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}
index.html
Title is {{.Title}}
遇到的问题
// testtmpl.go
type UserInfo struct {
Name string
} func main() {
t := template.New("My Reporter")
t, err := t.ParseFiles("views/report.html")
if err != nil {
fmt.Println("parse error")
return
} err = t.Execute(os.Stdout, UserInfo{Name: "tonybai"})
if err != nil {
fmt.Println("exec error", err)
}
return
}
执行结果,报错:
go run testtmpl.go
exec error template: My Reporter: "My Reporter" is an incomplete or empty template; defined templates are: "report.html"
看起来似乎 template 对象与模板名字对不上导致的错误啊。修改一下:
t := template.New("report.html")
执行结果:
<html>
<head>
</head>
<body>
Hello, tonybai
</body>
</html>
这回对了,看来 template 的名字在与 ParseFiles 一起使用时不是随意取的,务必要与模板文件名字相同。
ParseFiles 支持解析多个文件,如果是传入多个文件该咋办?godoc 说了,template 名字与第一个文件名相同即可。
参考:
http://www.jianshu.com/p/05671bab2357
[鸟窝]Go 模板嵌套最佳实践
[Tony Bai]近期遇到的3个Golang代码问题
[Go] Template 使用简介的更多相关文章
- template.js文档
参见GitHub:https://github.com/yanhaijing/template.js/ template.js简介: template.js 一款javascript模板引擎,简单,好 ...
- 学习STL-介绍一下STL
从大学时就开始学习C++,到现在近5年的时间了却很少用到STL.现在想想真得是对不起这门语言,也对不起宝贵的五年光阴.我钟爱C++,所以一定要完全搞懂它,理解它.爱一个人的前提是要懂他(她),爱一门语 ...
- [Go] Beego 模板嵌套 使用总结
通过以下文章,掌握了 Go 模板引擎 的基本用法: [Go] Template 使用简介 [Go] 模板嵌套最佳实践 Beego模板语法指南 但在开始学习 Beego 框架的 模板嵌套 模块源码时,有 ...
- Go 收藏
Golang 定位解决分布式系统,服务器应用开发,主要竞争对手是 Java.Python 之类:Rust 定位解决单机安全问题,高性能场景偏系统底层开发,主要竞争对手就是 C 和 C++. Golan ...
- Art-Template模板引擎(原生写法与简洁写法)
模板引擎:把js数据转换成html需要的页面,这就是模板引擎需要做的事 • native原生语法 1. 准备数据 2. 把数据转化成html格式的字符串 使用模板引擎 artT ...
- 使用 Craft CMS 搭建blog模型
原文链接:http://www.supperxin.com/Coding/Details/create-blog-using-craft-cms Craft CMS站点的搭建可以参考这篇:使用Dock ...
- Django框架简介及模板Template,filter
Django框架简介 MVC框架和MTV框架 MVC,全名是Model View Controller,是软件工程中的一种软件架构模式,把软件系统分为三个基本部分:模型(Model).视图(View) ...
- HTML5 <template>标签元素简介
一.HTML5 template元素初面 <template>元素,基本上可以确定是2013年才出现的.干嘛用的呢,顾名思意,就是用来声明是“模板元素”. 目前,我们在HTML中嵌入模板H ...
- C++标准模板库Stand Template Library(STL)简介与STL string类
参考<21天学通C++>第15和16章节,在对宏和模板学习之后,开启对C++实现的标准模板类STL进行简介,同时介绍简单的string类.虽然前面对于vector.deque.list等进 ...
随机推荐
- 主流服务器apache,iis,tomcat,jboss,resion,weblogic,websphere的区别
在互联网高速发展的今天,不同种类的网站大量涌现,每个人都在享受着网络服务带来的便利.而创建自己的个性化网站的门槛不断降低.从事网站架构,这种当年的绝对“”高科技“”绝活.也从it人员的专利“”沦落“” ...
- mongoexport导出csv中文乱码
在用mongoexport导出csv文件时,发现数据库中的中文在excel中都显示为乱码,用notepad打开则正常. 解决办法: 在notepad中,将编码格式改为UTF-8,保存,再用excel打 ...
- springboot日志框架
Spring Boot日志框架Spring Boot支持Java Util Logging,Log4j2,Lockback作为日志框架,如果你使用starters启动器,Spring Boot将使用L ...
- 为什么要使用断路器Hystrix?
为什么需要 Hystrix? 在微服务架构中,我们将业务拆分成一个个的服务,服务与服务之间可以相互调用(RPC).为了保证其高可用,单个服务又必须集群部署.由于网络原因或者自身的原因,服务并不能保证服 ...
- 自定义Web组件
一.Session 1.面向对象基础 面向对象中通过索引的方式访问对象,需要内部实现 __getitem__ .__delitem__.__setitem__方法 +? 1 2 3 4 5 6 7 8 ...
- 08 Go 1.8 Release Notes
Go 1.8 Release Notes Introduction to Go 1.8 Changes to the language Ports Known Issues Tools Assembl ...
- 测试开发之前端——No3.HTML5中的标准属性
HTML5的标准属性 属性 值 描述 accesskey character 规定访问元素的键盘快捷键 class classname 规定元素的类名(用于规定样式表中的类). contentedit ...
- redis配置文件redis.conf翻译、解释以及常用注意事项(持续更新中...)
# Redis configuration file example. #Redis 配置文件的示例 #如何利用配置文件启动Redis # Note that in order to read the ...
- 实操一下<python cookbook>第三版1
这几天没写代码, 练一下代码. 找的书是<python cookbook>第三版的电子书. *这个操作符,运用得好,确实少很多代码,且清晰易懂. p = (4, 5) x, y = p p ...
- ajax和jsonp
ajax和jsonp 前言:ajax和jsonp可以与后台通信,获取数据和信息,但是又不用刷新整个页面,实现页面的局部刷新. 一.ajax 定义:一种发送http请求与后台进行异步通讯的技术. 原理: ...