《Go Web 编程》之第5章 内容展示
《Go Web 编程》之第5章 内容展示
- 第5章 内容展示
- 5.1 模板引擎
- 5.2 Go的模板引擎
- 5.2.1 模板进行语法分析
- 5.2.2 执行模板
- 5.3 动作
- 5.3.1 条件动作
- 5.3.2 迭代动作
- 5.3.3 设置动作
- 5.3.4 包含动作
- 5.4 参数、变量和管道
- 5.5 函数
- 5.6 上下文感知
- 5.6.1 防御XSS攻击
- 5.6.2 不对HTML进行转义
- 5.7 嵌套模板
- 5.8 通过块动作定义默认模板
第5章 内容展示
5.1 模板引擎
模板引擎通过将数据和模板组合在一起生成HTML。
- 无逻辑模板引擎(logic-less template engine),只进行占位符的字符串替换,分离表现和逻辑,计算交给处理器完成。
- 嵌入逻辑的模板引擎(embedded logic template engine),编程语言嵌入模板中,功能强大,逻辑分布于不同处理器间,代码难维护。
5.2 Go的模板引擎
模板引擎通过模板文件列表和传入的动态数据,生成HTML,写入ResponseWriter。
模板引擎两个步骤:
(1)模板文件列表或者字符串进行语法分析,创建模板结构;
(2)模板引擎结合模板结构和传入动态数据,开始执行,生成HTML,传递给ResponseWriter。
tmpl.html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Go Web Programming</title>
</head>
<body>
{{ . }}
</body>
</html>
package main
import (
"html/template"
"net/http"
)
func process(w http.ResponseWriter, r *http.Request) {
t, _ := template.ParseFiles("tmpl.html")
t.Execute(w, "Hello World!")
}
func main() {
server := http.Server{
Addr: "127.0.0.1:8080",
}
http.HandleFunc("/process", process)
server.ListenAndServe()
}
5.2.1 模板进行语法分析
//可接收多个文件,返回一个模板集合(名称与第一个文件名相同)
t, _ := template.ParseFiles("tmpl.html")
//等价于
t := template.New("tmpl.html")
t, _ := t.ParseFiles("tmpl.html")
//匹配给定模式的所有文件进行语法分析
t, _ := template.ParseGlob("*.html")
//非nil错误,产生panic
t := template.Must(template.ParseFiles("tmpl.html"))
t, _ := template.ParseFiles("tmpl.html")
//等价于
t := template.New("tmpl.html")
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, _ := t.Parse(tmpl)
5.2.2 执行模板
t, _ := template.ParseFiles("t1.html", "t2.html")
//默认执行t1.html模板
t.Execute(w, "Hello World!")
//执行t2.html模板
t.ExecuteTemplate(w, "t2.html", "Hello World!")
5.3 动作
{{和}}包围的命令。
- 条件动作
- 迭代动作
- 设置动作
- 包含动作
- 定义动作
点.
也是动作,代表传递的数据。
5.3.1 条件动作
{{ if arg }}
some content
{{ end }}
{{ if arg }}
some content
{{ else }}
other content
{{ end }}
tmpl.html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Go Web Programming</title>
</head>
<body>
{{ if . }}
Number is greater than 5!
{{ else }}
Number is 5 or less!
{{ end }}
</body>
</html>
package main
import (
"html/template"
"math/rand"
"net/http"
"time"
)
func process(w http.ResponseWriter, r *http.Request) {
t, _ := template.ParseFiles("tmpl.html")
rand.Seed(time.Now().Unix())
t.Execute(w, rand.Intn(10) > 5)
}
func main() {
server := http.Server{
Addr: "127.0.0.1:8080",
}
http.HandleFunc("/process", process)
server.ListenAndServe()
}
5.3.2 迭代动作
数组、切片、映射或者通道进行迭代,内部{{ . }}代表迭代元素。
{{ range array }}
Dot is set to the element {{ . }}
{{ end }}
tmpl.html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Go Web Programming</title>
</head>
<body>
<ul>
{{ range . }}
<li>{{ . }}</li>
{{ else }}
<li>Nothing to show</li>
{{ end }}
</ul>
</body>
</html>
package main
import (
"html/template"
"net/http"
)
func process(w http.ResponseWriter, r *http.Request) {
t, _ := template.ParseFiles("tmpl.html")
daysOfWeek := []string{"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"}
t.Execute(w, daysOfWeek)
}
func main() {
server := http.Server{
Addr: "127.0.0.1:8080",
}
http.HandleFunc("/process", process)
server.ListenAndServe()
}
5.3.3 设置动作
指定范围内为点(.)设置值。
{{ with arg }}
Dot is set to arg
{{ end }}
tmpl.html
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Go Web Programming</title>
</head>
<body>
<div>The dot is {{ . }}</div>
<div>
{{ with "world"}}
Now the dot is set to {{ . }}
{{ end }}
{{ with ""}}
Now the dot is set to {{ . }}
{{ else }}
The dot is still {{ . }}
{{ end }}
</div>
<div>The dot is {{ . }} again</div>
</body>
</html>
package main
import (
"html/template"
"net/http"
)
func process(w http.ResponseWriter, r *http.Request) {
t, _ := template.ParseFiles("tmpl.html")
t.Execute(w, "hello")
}
func main() {
server := http.Server{
Addr: "127.0.0.1:8080",
}
http.HandleFunc("/process", process)
server.ListenAndServe()
}
5.3.4 包含动作
嵌套模板。
{{ template "name" }}
t1.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=9">
<title>Go Web Programming</title>
</head>
<body>
<div> This is t1.html before</div>
<div>This is the value of the dot in t1.html - [{{ . }}]</div>
<hr/>
{{ template "t2.html" . }}
<hr/>
<div> This is t1.html after</div>
</body>
</html>
t2.html
<div style="background-color: yellow;">
This is t2.html<br/>
This is the value of the dot in t2.html - [{{ . }}]
</div>
package main
import (
"html/template"
"net/http"
)
func process(w http.ResponseWriter, r *http.Request) {
t, _ := template.ParseFiles("t1.html", "t2.html")
t.Execute(w, "Hello World!")
}
func main() {
server := http.Server{
Addr: "127.0.0.1:8080",
}
http.HandleFunc("/process", process)
server.ListenAndServe()
}
5.4 参数、变量和管道
参数(argument)是模板中值。
布尔、整数、字符串;
结构、结构字段、数组;
变量、方法(只返回一个值或者值+error)、函数;
处理器向模板引擎传递的数据,点(.)。
arg是参数。
{{ if arg }}
some content
{{ end }}
动作中设置变量,以$开头。
$variable := value
变量实现迭代。
{{ range $key, $value := . }}
The key is {{ $key }} and the value is {{ $value }}
{{ end }}
模板中管道(pipeline)是多个有序串联的参数、函数和方法。
{{ p1 | p2 | p3 }}
<!DOCTYPE html>
<html>
<head>
<title>Go Web Programming</title>
</head>
<body>
{{ 12.3456 | printf "%.2f" }}
</body>
</html>
5.5 函数
模板引擎函数可输入多个参数,只返回一个值或者值+error
自定义模板函数:
(1)创建FuncMap映射,键为函数名(自定义字符串),值为实际定义的函数;
(2)FuncMap与模板绑定。
tmpl.html
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Go Web Programming</title>
</head>
<body>
<div>The date/time is {{ . | fdate }}</div>
<div>The date/time is {{ fdate . }}</div>
</body>
</html>
package main
import (
"html/template"
"net/http"
"time"
)
func formatDate(t time.Time) string {
layout := "2006-01-02"
return t.Format(layout)
}
func process(w http.ResponseWriter, r *http.Request) {
funcMap := template.FuncMap{"fdate": formatDate}
t := template.New("tmpl.html").Funcs(funcMap)
t, _ = t.ParseFiles("tmpl.html")
t.Execute(w, time.Now())
}
func main() {
server := http.Server{
Addr: "127.0.0.1:8080",
}
http.HandleFunc("/process", process)
server.ListenAndServe()
}
5.6 上下文感知
模板以上下文感知(context-aware)的方式显示内容,根据内容所处的上下文改变其显示的内容。
tmpl.html
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; 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>
package main
import (
"html/template"
"net/http"
)
func process(w http.ResponseWriter, r *http.Request) {
t, _ := template.ParseFiles("tmpl.html")
content := `I asked: <i>"What's up?"</i>`
t.Execute(w, content)
}
func main() {
server := http.Server{
Addr: "127.0.0.1:8080",
}
http.HandleFunc("/process", process)
server.ListenAndServe()
}
curl -i 127.0.0.1:8080/process
HTTP/1.1 200 OK
Date: Mon, 12 Sep 2022 13:52:10 GMT
Content-Length: 519
Content-Type: text/html; charset=utf-8
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Go Web Programming</title>
</head>
<body>
<div>I asked: <i>"What's up?"</i></div>
<div><a href="/I%20asked:%20%3ci%3e%22What%27s%20up?%22%3c/i%3e">Path</a></div>
<div><a href="/?q=I%20asked%3a%20%3ci%3e%22What%27s%20up%3f%22%3c%2fi%3e">Query</a></div>
<div><a onclick="f('I asked: \u003ci\u003e\u0022What\u0027s up?\u0022\u003c\/i\u003e')">Onclick</a></div>
</body>
</html>
5.6.1 防御XSS攻击
持久性XSS漏洞(persistent XSS vulnerability),常见XSS攻击方式,由于服务器将攻击者存储的数据原原本本地显示给其它用户所致。比如攻击者数据含<script>
标签,预防方法是对用户数据进行转义存储或显示。
form.html
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Go Web Programming</title>
</head>
<body>
<form action="/process" method="post">
Comment: <input name="comment" type="text" size="50">
<hr/>
<button id="submit">Submit</button>
</form>
</body>
</html>
tmpl.html
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Go Web Programming</title>
</head>
<body>
<div>{{ . }}</div>
</body>
</html>
package main
import (
"net/http"
"html/template"
)
func process(w http.ResponseWriter, r *http.Request) {
t, _ := template.ParseFiles("tmpl.html")
t.Execute(w, r.FormValue("comment"))
}
func form(w http.ResponseWriter, r *http.Request) {
t, _ := template.ParseFiles("form.html")
t.Execute(w, nil)
}
func main() {
server := http.Server{
Addr: "127.0.0.1:8080",
}
http.HandleFunc("/process", process)
http.HandleFunc("/", form)
server.ListenAndServe()
}
http://127.0.0.1:8080/form
文本框输入<script>alert('Pwnd!');<script>,点击Submint按钮
网页源码:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Go Web Programming</title>
</head>
<body>
<div><script>alert('Pwnd!');<script></div>
</body>
</html>
5.6.2 不对HTML进行转义
package main
import (
"net/http"
"html/template"
)
func process(w http.ResponseWriter, r *http.Request) {
//关闭浏览器内置的XSS防御功能
w.Header().Set("X-XSS-Protection", "0")
t, _ := template.ParseFiles("tmpl.html")
//不转换HTML
t.Execute(w, template.HTML(r.FormValue("comment")))
}
func form(w http.ResponseWriter, r *http.Request) {
t, _ := template.ParseFiles("form.html")
t.Execute(w, nil)
}
func main() {
server := http.Server{
Addr: "127.0.0.1:8080",
}
http.HandleFunc("/process", process)
http.HandleFunc("/", form)
server.ListenAndServe()
}
5.7 嵌套模板
布局(layout),Web设计中可在多个页面重复使用的固定模式。头部菜单,提供服务器状态、版权声明、联系方式等附件信息的尾部栏,导航栏,多级菜单等布局可通过嵌套模板实现。
- 一个模板文件里定义多个模板
tmpl.html
{{ define "layout" }}
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Go Web Programming</title>
</head>
<body>
{{ template "content" }}
</body>
</html>
{{ end }}
{{ define "content" }}
Hello World!
{{ end }}
package main
import (
"html/template"
"net/http"
)
func process(w http.ResponseWriter, r *http.Request) {
t, _ := template.ParseFiles("layout.html")
//t.ExecuteTemplate(w, "layout", "")
t.ExecuteTemplate(w, "layout", nil)
}
func main() {
server := http.Server{
Addr: "127.0.0.1:8080",
}
http.HandleFunc("/process", process)
server.ListenAndServe()
}
curl -i http://127.0.0.1:8080/process
- 不同模板文件定义同名模板
red_hello.html
{{ define "content" }}
<h1 style="color: red;">Hello World!</h1>
{{ end }}
blue_hello.html
{{ define "content" }}
<h1 style="color: blue;">Hello World!</h1>
{{ end }}
layout.html
{{ define "layout" }}
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Go Web Programming</title>
</head>
<body>
{{ template "content" }}
</body>
</html>
{{ end }}
package main
import (
"html/template"
"math/rand"
"net/http"
"time"
)
func process(w http.ResponseWriter, r *http.Request) {
rand.Seed(time.Now().Unix())
var t *template.Template
if rand.Intn(10) > 5 {
t, _ = template.ParseFiles("layout.html", "red_hello.html")
} else {
t, _ = template.ParseFiles("layout.html", "blue_hello.html")
}
t.ExecuteTemplate(w, "layout", "")
}
func main() {
server := http.Server{
Addr: "127.0.0.1:8080",
}
http.HandleFunc("/process", process)
server.ListenAndServe()
}
- 定义默认模板布局
layout.html
{{ define "layout" }}
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Go Web Programming</title>
</head>
<body>
{{ template "content" }}
</body>
</html>
{{ end }}
{{ define "content" }}
<h1 style="color: blue;">Hello World!</h1>
{{ end }}
red_hello.html
{{ define "content" }}
<h1 style="color: red;">Hello World!</h1>
{{ end }}
package main
import (
"html/template"
"math/rand"
"net/http"
"time"
)
func process(w http.ResponseWriter, r *http.Request) {
rand.Seed(time.Now().Unix())
var t *template.Template
if rand.Intn(10) > 5 {
t, _ = template.ParseFiles("layout.html", "red_hello.html")
} else {
t, _ = template.ParseFiles("layout.html")
}
t.ExecuteTemplate(w, "layout", "")
}
func main() {
server := http.Server{
Addr: "127.0.0.1:8080",
}
http.HandleFunc("/process", process)
server.ListenAndServe()
}
5.8 通过块动作定义默认模板
块动作(block action),定义模板并且立即使用。
{{ block arg }}
Dot is set to arg
{{ end }}
red_hello.html
{{ define "content" }}
<h1 style="color: red;">Hello World!</h1>
{{ end }}
layout.html
{{ define "layout" }}
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Go Web Programming</title>
</head>
<body>
{{ black "content" . }}
<h1 style="blue: red;">Hello World!</h1>
{{ end }}
</body>
</html>
{{ end }}
package main
import (
"html/template"
"math/rand"
"net/http"
"time"
)
func process(w http.ResponseWriter, r *http.Request) {
rand.Seed(time.Now().Unix())
var t *template.Template
if rand.Intn(10) > 5 {
t, _ = template.ParseFiles("layout.html", "red_hello.html")
} else {
t, _ = template.ParseFiles("layout.html")
}
t.ExecuteTemplate(w, "layout", "")
}
func main() {
server := http.Server{
Addr: "127.0.0.1:8080",
}
http.HandleFunc("/process", process)
server.ListenAndServe()
}