66.模板详细使用(二)

函数

模板中可以使用函数作为参数。Go的模板引擎函数都是受限的:这些函数可以接受任意多个参数的输入,只能返回一个值,或返回一个值和一个错误。
使用模板函数映射,将一个函数映射为一个键(一般设置为函数名字)。
比如我们可以写这样一个函数,把日期时间类型的变量,格式化为指定的格式(“2006-01-02”)

func formatDate(t time.Time) string {
    layout := "2006-01-02"
    return t.Format(layout)
}

然后,我们定义一个函数Joeltemplate7(writer http.ResponseWriter, request *http.Request),在函数内创建一个变量名为 funcMap 的 FuncMap 结构。这个结构将名字 fdate 映射到 formatDate 函数。
接着,使用 template.New 函数创建一个模板,名为 ./JoelTemplate/sayHelloDate.html,再以串联的方式调用模板的 Funcs 方法,并将前面创建的 funcMap 传递给模板。
这两步使得 funcMap 和模板绑定完成。最后对模板文件进行语法分析,调用 ExecuteTemplate 方法,并将 ResponseWriter 以及当前时间传递给程序。

func Joeltemplate7(writer http.ResponseWriter, request *http.Request) {

    funcMap := template.FuncMap{"fdate":formatDate}
    t := template.New("./JoelTemplate/sayHelloDate.html").Funcs(funcMap)
    t, _ = t.ParseFiles("./JoelTemplate/sayHelloDate.html")
    t.ExecuteTemplate(writer,"sayHelloDate.html" ,time.Now())

}

模板文件 sayHelloDate.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Go Web Programming</title>
</head>
<body>
管道:{{. | fdate}}
<br>
普通:{{fdate .}}
</body>
</html>

在模板文件中,使用了2种显示方式,一种是管道、一种是普通函数的方式。2种方式执行结果是相同的。


2种方式的执行结果相同
上下文感知

go语言的模板引擎可以根据内容所处的上下文改变其显示的内容。可以正确的转义显示内容。比如:如果模板显示的是HTML格式的内容,那么模板将对其实施HTML转义;如果模板显示的是javascript格式的内容,那么模板将对其进行javascript转义。此外,模板还可以感知识别出内容中的URL或css样式。
代码示例

func Joeltemplate8(writer http.ResponseWriter, request *http.Request) {
    t, _ := template.ParseFiles("./JoelTemplate/sayHelloContextAware.html")
    content := `I asked:<li>"What's up?'"</li>`
    t.Execute(writer, content)
}

模板文件sayHelloContextAware.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Go Web Programming</title>
</head>
<body>
<div>{{.}}</div>
<div><a href="/{{.}}">path</a> </div>
<div><a href="/?q={{.}}">query</a> </div>
<div><a onclick="f('{{.}}')">Onclick</a> </div>
</body>
</html>

这个content在模板中表现出的转义结果是不同的

<html lang="en"><head>
    <meta charset="UTF-8">
    <title>Go Web Programming</title>
</head>
<body>
<div>I asked:&lt;li&gt;"What's up?'"&lt;/li&gt;</div>
<div><a href="/I%20asked:%3cli%3e%22What%27s%20up?%27%22%3c/li%3e">path</a> </div>
<div><a href="/?q=I%20asked%3a%3cli%3e%22What%27s%20up%3f%27%22%3c%2fli%3e">query</a> </div>
<div><a onclick="f('I asked:\x3cli\x3e\x22What\x27s up?\x27\x22\x3c\/li\x3e')">Onclick</a> </div>

</body></html>

界面效果


不同的转义效果

对传递的值做一些改变,再比较一下运行结果

func Joeltemplate8(writer http.ResponseWriter, request *http.Request) {
    t, _ := template.ParseFiles("./JoelTemplate/sayHelloContextAware.html")
    content := []string{`I asked:<li>"What's up?'"</li>`,`完全是文字`,`http://www.baidu.com/`,`333`,`alert("Love Moon")`}
    t.Execute(writer, content)
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Go Web Programming</title>
</head>
<body>
{{range .}}
<div>{{.}}</div>
<div><a href="{{.}}">path</a></div>
<div><a href="/?q={{.}}">query</a></div>
<div><buton onclick="alert('{{.}}')">Onclick</buton></div>
<hr>
{{end}}
</body>
</html>
<html lang="en"><head>
    <meta charset="UTF-8">
    <title>Go Web Programming</title>
</head>
<body>

<div>I asked:&lt;li&gt;"What's up?'"&lt;/li&gt;</div>
<div><a href="#ZgotmplZ">path</a></div>
<div><a href="/?q=I%20asked%3a%3cli%3e%22What%27s%20up%3f%27%22%3c%2fli%3e">query</a></div>
<div><buton onclick="alert('I asked:\x3cli\x3e\x22What\x27s up?\x27\x22\x3c\/li\x3e')">Onclick</buton></div>
<hr>

<div>完全是文字</div>
<div><a href="%e5%ae%8c%e5%85%a8%e6%98%af%e6%96%87%e5%ad%97">path</a></div>
<div><a href="/?q=%e5%ae%8c%e5%85%a8%e6%98%af%e6%96%87%e5%ad%97">query</a></div>
<div><buton onclick="alert('完全是文字')">Onclick</buton></div>
<hr>

<div>http://www.baidu.com/</div>
<div><a href="http://www.baidu.com/">path</a></div>
<div><a href="/?q=http%3a%2f%2fwww.baidu.com%2f">query</a></div>
<div><buton onclick="alert('http:\/\/www.baidu.com\/')">Onclick</buton></div>
<hr>

<div>333</div>
<div><a href="333">path</a></div>
<div><a href="/?q=333">query</a></div>
<div><buton onclick="alert('333')">Onclick</buton></div>
<hr>

<div>alert("Love Moon")</div>
<div><a href="alert%28%22Love%20Moon%22%29">path</a></div>
<div><a href="/?q=alert%28%22Love%20Moon%22%29">query</a></div>
<div><buton onclick="alert('alert(\x22Love Moon\x22)')">Onclick</buton></div>
<hr>

</body></html>
各种内容的差异比较,尤其是URL的那个部分差异明显

这个上下文感知,能够让你以原有的形式,在各种上下文中转义显示出来。更可以来防御XSS(cross-site scripting,跨站脚本)攻击。

防御XSS攻击

XSS攻击是由于服务器将攻击者存储的数据原原本本的显示给其他用户所致的。例如一些网站带有用户回复功能,而回复的内容带有<script>标签的代码。界面展示这些回复内容的时候,即使对 <script> 标签也是原原本本的显示给用户,这使得用户在毫不知情的情况下,被执行了 javascript 脚本。这样的脚本往往是恶意代码。
预防这样的情况,常见的方法就是在显示或者存储用户传入的数据之前,对数据进行转义。
给一个XSS攻击的代码例子
form表单模板文件sayHelloForm.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Go Web Programming</title>
</head>
<body>
<form action="/xss/" method="post">
    Comment:<input name="comment" type="text">
    <hr>
    <button id="submit" type="submit">Submit</button>
</form>
</body>
</html>

表单页面模板加载的代码

func JoelFormSend(writer http.ResponseWriter, request *http.Request) {
    t, _ := template.ParseFiles("./JoelTemplate/sayHelloForm.html")
    t.Execute(writer, nil)
}

显示数据的页面模板sayHelloXSS.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Go Web Programming</title>
</head>
<body>
<div>{{.}}</div>
</body>
</html>

接收数据绑定模板的代码

func Joeltemplate9(writer http.ResponseWriter, request *http.Request) {
    vlu:= request.FormValue("comment")
    t, _ := template.ParseFiles("./JoelTemplate/sayHelloXSS.html")
    t.Execute(writer, vlu)
}

我们在界面输入 <script>alert('hah')</script>


输入效果

提交后显示结果为下图:


提交效果
不对HTML转义的方法

在某些情况下,我们需要按照用户原始输入来输出,这个时候,可以在Execute的时候,使用 template.HTML来告诉处理器,不对HTML进行转义。

func Joeltemplate9(writer http.ResponseWriter, request *http.Request) {
    vlu:= request.FormValue("comment")
    writer.Header().Set("X-XSS-Protection", "0")
    t, _ := template.ParseFiles("./JoelTemplate/sayHelloXSS.html")
    t.Execute(writer, template.HTML(vlu))
}

这几行代码中, writer.Header().Set("X-XSS-Protection", "0")是针对一些本来就具有防跨站脚本攻击功能的浏览器,进行关闭内置XSS防御功能。从而实现原始输出。
我们现在可以借助这个方法来看XSS攻击效果。


XSS攻击,弹出一个提示框,javascript脚本被执行了
模板嵌套

模板嵌套是构建丰富页面形态的主要手段了。通常,在一个页面里会有很多个布局部分组成,这些布局中有些是在多个页面甚至是整个站点中都被反复使用的。如果每个页面的这些部分都是独立写的,当这些公共部分需要修改的时候,就会需要改动所有使用到这些内容的页面。这个时候,使用模板嵌套就可以很好的解决这个问题了。
模板,允许把每一个可以逻辑上可独立存在的布局单独编写成一个模板文件,并且让多个其他模板嵌套调用。
先看个简单的例子
我们知道一个页面一般由头部、菜单、主体、底部组成,我们可以称它们为head、menu、body、foot。我们构建一个index页面模板,再分别构建head、menu、body、foot模板。并且在index中嵌套其他的这几个模板。
tmplIndex.html

{{ define "index2" }}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Go Web Programming{{.Title}}</title>
</head>
<body>
index自己的内容
{{ template "head" .Head}}
{{ template "menus" .Menu}}
{{ template "body" .Body }}
{{ template "foot" .Foot}}

</body>
</html>
{{end}}

tmplPubHead.html

{{define "head"}}
<div>头部</div>
<div>Banner</div>
{{end}}

tmplPubMenus.html

{{define "menus"}}
<div>主菜单</div>
<div>子菜单</div>
{{end}}

tmplPubBody.html

{{define "body"}}
<div>主体1</div>
<div>主体2{{.}}</div>
<div>主体3</div>
{{end}}

tmplPubFoot.html

{{define "foot"}}
<div>底部</div>
{{end}}

在模板调用加载的handler函数中,需要注意,所有需要加载的模板都必须写进代码里。
这里为了给各个模板传递指定的值进去,绑定的参数为map[string]string格式。

//嵌套模板
func processQtHandler(writer http.ResponseWriter, request *http.Request) {
    t, _ := template.ParseFiles("./JoelTempWeb/tmplIndex.html","./JoelTempWeb/tmplPubHead.html","./JoelTempWeb/tmplPubMenus.html","./JoelTempWeb/tmplPubBody.html","./JoelTempWeb/tmplPubFoot.html",)
    t.ExecuteTemplate(writer, "index2", map[string]string{"Title":"This is Title","Head":"This is Head","Menu":"This is menu", "Body":"This is Body", "Foot":"This is Foot"})
}

在main中添加此函数的监听路径,我们可以看到以下运行结果。

map[string]string的值已经加载绑定到指定位置了

如果看不清楚的话,我们来修改一下模板
tmplIndex.html

{{ define "index2" }}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{{.Title}}</title>
</head>
<body>
当前页面是index页
{{ template "head" .Head}}
{{ template "menus" .Menu}}
{{ template "body" .Body }}
{{ template "foot" .Foot}}

</body>
</html>
{{end}}

tmplPubHead.html

{{define "head"}}
<div>{{.}}</div>
{{end}}

tmplPubMenus.html

{{define "menus"}}
<div>{{.}}</div>
{{end}}

tmplPubBody.html

{{define "body"}}
<div>{{.}}</div>
{{end}}

tmplPubFoot.html

{{define "foot"}}
<div>{{.}}</div>
{{end}}

这次看清楚了吗?在index页面里,{{ template "head" .Head}}的 "head" 对应的是 tmplPubHead.html 中的 {{define "head"}} ,.Head 对应的是 map[string]string 中的 “Head”。界面对应位置显示的就是 Head 健的 值 “This is Head”。其他布局也是同样。
由此,我们可以知道,在 handler 中把页面所需要加载的数据,都获取到,并且按照模板布局分别给入绑定就可以了。


很清晰的显示了嵌套的模板和绑定的数据

那么如果,是临时要在index中增加一个动作,来显示一些已经存在的数据,是不是必须要再添加一个模板文件,并且修改go代码中加载模板的那部分呢?
这里,模板给我们一个便利的方法,如果是要显示已经存在的数据(例如Title),只需要在index中使用 block 直接定义一个模板就可以了。
下面我们在index中增加以下模板

{{ block "fly" .Title }}
<div>自由的飞{{.}}</div>
{{end}}

增加这些代码后的index模板文件变为这样

{{ define "index2" }}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{{.Title}}</title>
</head>
<body>
当前页面是index页
{{ template "head" .Head}}
{{ template "menus" .Menu}}
{{ template "body" .Body }}
{{ template "foot" .Foot}}
{{ block "fly" .Title }}
<div>自由的飞{{.}}</div>
{{end}}
</body>
</html>
{{end}}

然后我们不需要修改go代码,直接运行看看结果


新增block已经显示出了Title
动态切换模板

当我们需要根据业务随时切换模板的时候,最好还是能够让程序自动执行。这里可以采用两个相同的 define 来实现这一目标。
首先构建两个用来切换的模板
tmplDtRed.html

{{define "content"}}
<h1 style="color: #f00;">Hello Moon</h1>
{{end}}

tmplDtBlue.html

{{define "content"}}
<h1 style="color: #00f;">Hello Moon</h1>
{{end}}

然后构建一个调用他们的主模板
tmplDt.html

{{define "Dt"}}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Go Web Programming</title>
</head>
<body>
{{template "content"}}
</body>
</html>
{{end}}

两个模板的 define 都是 "content",但是模板文件名不同。
在go代码中,我们采用随机的方式,来判断应该调用哪个嵌套模板

//动态切换模板
func processDtHandler(writer http.ResponseWriter, request *http.Request) {
    rand.Seed(time.Now().Unix())
    var t *template.Template    //创建一个模板
    if rand.Intn(10) > 5 {
        t, _ = template.ParseFiles("./JoelTempWeb/tmplDt.html","./JoelTempWeb/tmplDtRed.html",)
    }else {
        t, _ = template.ParseFiles("./JoelTempWeb/tmplDt.html","./JoelTempWeb/tmplDtBlue.html",)
    }
    t.ExecuteTemplate(writer, "Dt", "")
}

我们根据 rand.Intn(10) > 5 的值来随机的使用嵌套模板。
运行程序后,多刷新几次,就会发现页面文字的颜色会发生变换。


蓝色

红色

当然实际项目中,是很少 rand.Intn(10) > 5 这样实现的,应该是你具体的业务逻辑判断,来决定采用哪个模板。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,723评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,003评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,512评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,825评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,874评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,841评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,812评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,582评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,033评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,309评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,450评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,158评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,789评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,409评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,609评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,440评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,357评论 2 352

推荐阅读更多精彩内容

  • 之前积累了XSS 有一段时间,因为目前开始了一件有趣的工程,需要整合非常多的知识,其中Web 安全这一块出现最多的...
    刀背藏身阅读 9,056评论 0 16
  • 概要 64学时 3.5学分 章节安排 电子商务网站概况 HTML5+CSS3 JavaScript Node 电子...
    阿啊阿吖丁阅读 9,179评论 0 3
  • 前后端分离的Restful架构大行其道,传统的模板技术已经不多见了。实际上只是渲染的地方由后端转移到了前端,模板的...
    人世间阅读 54,720评论 5 44
  • 不知道开头要怎么说明,就随意写写今天悟到的一些想法吧,想到什么写什么(其实现在有快要按耐不住的浮躁,很想打人啦他奶...
    fairy_3f58阅读 156评论 0 1
  • 今天是公历7月24日,农历六月十二,水晶的黄种子年,宇宙的龟之月,黄色收成之周第7天,也是今年水晶黄种子年的最后一...
    蓝夜写玛雅阅读 1,390评论 0 1