G0第21章 :gin框架介绍、RESTful API、Gin渲染
G0第21章 :gin框架
01 内容介绍
https://gin-gonic.com/zh-cn/docs/
web本质
- Web是基于HTTP协议进行交互的应用网络
- Web就是通过使用浏览器/APP访问的各种资源
package mainimport ("fmt""net/http"
)func sayHello(w http.ResponseWriter, r *http.Request){_, _ = fmt.Fprintln(w, "<h1>Hello Golang!</h1>")
}func main (){http.HandleFunc("/hello",sayHello)err := http.ListenAndServe(":9090",nil)if err != nil {fmt.Println("http server failed, err=", err)return}
}
gin框架初识
Gin是一个用Go语言编写的web框架。
它是一个类似于 martini但拥有更好性能的API框架,
由于使用了 httprouter,速度提高了近40倍。
如果你是性能和高效的追求者,你会爱上Gin
Gin框架介绍
Go世界里最流行的Web框架,Github。上有32K+star。基于httprouter开发的Web框架。中文文档齐全,简单易用的轻量级框架。
package mainimport "github.com/gin-gonic/gin"func main(){r := gin.Default()//返回默认的路由引擎//指定用户使用GET请求访问/hello时,执行sayHello这个函数r.GET("/hello",func(c *gin.Context){c.JSON(200,gin.H{"message":"hello golang!",})})//启动服务r.Run(":9090")
}
以下内容为网页中需要显示的界面
<h1 style='color:orange'>Hello Golang!</h1>
<h2>how are you!</h2>
<img id='i1' src='https://img0.baidu.com/it/u=351616666,1871778470&fm=253&fmt=auto&app=138&f=PNG?w=727&h=500'>
<button id='b1'>点我</button>
<script>
document.getElementById('b1').οnclick=function(){document.getElementById('i1').src='https://img0.baidu.com/it/u=4064084738,3175031420&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=450'
}
</script>
当一个网页上的信息非常大的时候,那么通过http协议发送的消息内容也就非常的多,后端返回json格式的数据,可以在不同的平台应用,比如手机、app、网页
02 Gin框架安装与使用
安装
go get -u github.com/gin-gonic/gin
第一个Gin示例:
package mainimport ("github.com/gin-gonic/gin"
)func main() {// 创建一个默认的路由引擎r := gin.Default()// GET:请求方式;/hello:请求的路径// 当客户端以GET方法请求/hello路径时,会执行后面的匿名函数r.GET("/hello", func(c *gin.Context) {// c.JSON:返回JSON格式的数据c.JSON(200, gin.H{"message": "Hello world!",})})// 启动HTTP服务,默认在0.0.0.0:8080启动服务r.Run()
}
将上面的代码保存并编译执行,然后使用浏览器打开127.0.0.1:8080/hello就能看到一串JSON字符串。
03 RESTful API软件架构的风格
REST与技术无关,代表的是一种软件架构风格,REST是Representatiional State Transfer 的简称,中文翻译为“表征状态转移”或“表现层状态转化”。
简单来说,REST的含义就是客户端与Web服务器之间进行交互的时候,使用HTTP协议中的4个请求方法代表不同的动作。
- GET用来获取资源
- POST用来新建资源
- PUT用来更新资源
- DELETE用来删除资源
只要API程序遵循了REST风格,那就可以称其为RESTful API。目前在前后端分离的架构中,前后端基本都是通过RESTful API来进行交互的。
Gin框架支持开发RESTful API的开发。
package mainimport "github.com/gin-gonic/gin"func main(){r := gin.Default()//返回默认的路由引擎//r.GET("/book", ...)//r.GET("/create_book", ...)//r.GET("/upadte_book", ...)//r.GET("/update_book", ...)//指定用户使用GET请求访问/hello时,执行sayHello这个函数r.GET("/hello",func(c *gin.Context){c.JSON(200,gin.H{"message":"hello golang!",})})r.POST("/hello",func(c *gin.Context){c.JSON(200,gin.H{"message":"hello golang!",})})r.PUT("/hello",func(c *gin.Context){c.JSON(200,gin.H{"message":"hello golang!",})})r.DELETE("/hello",func(c *gin.Context){c.JSON(200,gin.H{"message":"hello golang!",})})//启动服务r.Run(":9090")
}
开发RESTful API的时候我们通常使用Postman来作为客户端的测试工具
04 Gin渲染
html/template 包实现了数据驱动的模版,用于生成可防止代码注入的安全的HTML内容。它提供了和text/template包相同的接口,Go语言中输出HTML的场景都应使用html/template这个包。
1、扫盲
模版与渲染
在一些前后端不分离的Web架构中,我们通常需要再后端将一些数据渲染到HTML文档中,从而实现动态的网页(网页的布局和样式大致一样,但展示的内容并不一样)效果。
我们这里说的模版可以理解为事先定义好的HTML文档文件,模板渲染的作用机制可以简单理解为文本替换操作-使用相应的数据去替换HTML文档中事先准备好的标记。
很多编程语言的Web框架中都使用各种模版引擎,比如Python语言中Flask框架中使用的jinja2模版引擎。
Go语言的模版引擎
Go语言内置了文本模版引擎 text/template 和用于HTML文档的html/template。它们的作用机制可以简单归纳如下:
1、模版文件通常定义为.tmpl和.tpl为后缀(也可以使用其他后缀),必须使用UTF8编码。
2、模版文件中使用{{和}}包裹和标识需要传入的数据。
3、传给模版这样的数据就可以通过点号(.)来访问,如果数据是复杂类型的数据,可以通过{{.FieldName}}来访问它的字段
4、除{{和}}包裹外的内容外,其他内容均不做修改原样输出。
模版引擎的使用
Go语言模版引擎的使用可以分为三部分:定义模版文件、解析末班文件、模版渲染
第一步:定义模版文件
其中,定义模版文件时需要我们按照相关语法规则去编写,后文会详细介绍。
第二步:渲染模版文件
上面定义好了模版文件之后,可以使用下面的常用方法取解析模版文件,得到模版对象:
func (t *Template) Parse (src string) (*Template, error)
func ParseFiles(filename ..string) (*Template, error)
func ParseGlob(pattern string) (*Template, error)
当然也可以用func New(name string) *Template 函数创建一个名为 name 的模版,然后对其调用上面的方法取解析模版字符串或模版文件。
第三步:模版渲染
渲染模版简单来说就是使用数据去填充模版,当然实际上可能会复杂得多
func (t *Template) Execute(wr io.Writer, data interface{}) error
fucn (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error
基本示例
定义模版文件
我们按照Go模版语法定义一个 hello.tmpl 的模版文件,内容如下:
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>Hello</title>
</head>
<body><p>Hello {{.}}</p>
</body>
</html>
解析和渲染模版文件
然后我们创建一个 main.go 文件,在其中写下HTTP server端代码如下:13阿萨德阿萨德
//main.go
package mainimport ("fmt""html/template""net/http"
)// main.go
//遇事不决,写注释
func sayHello(w http.ResponseWriter, r *http.Request) {// 解析指定文件生成模板对象tmpl, err := template.ParseFiles("./hello.tmpl")if err != nil {fmt.Println("create template failed, err:", err)return}// 利用给定数据渲染模板,并将结果写入wname := "小王子"err = tmpl.Execute(w, name)if err != nil {fmt.Println("Execute Failed,err=", err)return}
}
func main() {http.HandleFunc("/", sayHello)err := http.ListenAndServe(":9000", nil)if err != nil {fmt.Println("HTTP server failed,err:", err)return}
}
注意 ./hello.tmpl 是相对路径,因此要将main.go编译出来再运行,不可以在goland中直接go run,否则会找不到这个hello.tmpl文件
name 就相当于 tmpl文件中的 {{.}} 点.
模版语法
{{.}}
模版语法都包含在{{和}} 中间,其中{{.}}中的点表示当前对象。
当我们传入一个结构体对象时,我们可以根据 . 来访问结构体的对应字段。例如:
定义模版
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>Hello</title>
</head>
<body><p>Hello {{.Name}}</p><p>性别: {{.Gender}}</p><p>年龄: {{.Age}}</p>
</body>
</html>
解析及渲染模版文件
package mainimport ("fmt""html/template""net/http"
)type UserInfo struct{Name stringGender stringAge int
}// main.go
//遇事不决,写注释
func sayHello(w http.ResponseWriter, r *http.Request) {// 解析指定文件生成模板对象tmpl, err := template.ParseFiles("./hello.tmpl")if err != nil {fmt.Println("create template failed, err:", err)return}// 利用给定数据渲染模板,并将结果写入wuser := UserInfo{Name: "小王子",Gender: "男",Age: 18,}err = tmpl.Execute(w, user)if err != nil {fmt.Println("Execute Failed,err=", err)return}
}
func main() {http.HandleFunc("/", sayHello)err := http.ListenAndServe(":9000", nil)if err != nil {fmt.Println("HTTP server failed,err:", err)return}
}
同理,当传入的变量是map时,也可以在模版文件中通过 . 根据key来取值
user := map[string]interface{}{"Name": "小王子","Gender": "男","Age": 19,}
注释
{{/* a comment */}}
注释,执行时会忽略。可以多行。注释不能嵌套,并且必须紧贴分界符始止
pipeline
pipeline是指产生数据的操作。比如 {{.}} 、{{.Name}}等。Go的模版语法中支持使用管道符号 | 链接多个命令,用法和unix下的管道类似 : | 前面的命令会将运算结果(或返回值)传递给后一个命令的最后一个位置。
注意 :并不是只有使用了 | 才是pipeline。 Go的模版语法中, pipeline概念是传递数据,只要能产生数据的,都是pipeline。
变量
还可以再模版中声明变量,用来保存传入模版的数据或其他语句产生的结果。
$obj := {{.}}
其中 $obj 是变量的名字,在后续的代码中就可以使用该变量了。
移除空格
有时候我们在使用模版语法的时候会不可避免的引入一下空格或者换行符,这样模版最终渲染出来的内容可能就和我们想的不一样,这个时候可以使用 {{- 语法去除模版内容左侧的所有空白符号,使用 -}} 去除模版内容右侧的所有空白符号。
例如:
<p>Hello {{- .u1.Name -}}</p>
注意:- 要紧挨 {{ 和 }} ,同时与模版之间需要使用空格分割。
条件判断
Go模版语法中的条件判断有以下几种:
{{if pipeline}} T1 {{end}}{{if pipeline}} T1 {{else}} T0 {{end}}{{if pipeline}} T1 {{else if pipeline}} T0 {{end}}
range
Go的模版语法中使用 range 关键字进行遍历,有以下两种写法,其中pipeline的值必须是数组、切片、字典或者通道。
{{range pipeline}} T1 {{end}}
如果pipeline的值其长度为0,不会有任何输出{{range pipeline}} T1 {{else}} T0 {{end}}
如果pipeline的值其长度为0,则会执行T0
with
{{with pipeline}} T1 {{end}}
如果pipeline为empty不产生输出,否则将dot设为pipeline的值并执行T1。不修改外面的dot。
{{with pipeline}} T1 {{else}} T0 {{end}}
如果pipeline为empty,不改变dot并执行T0,否则dot设为pipeline的值并执行T1。
预定义函数
执行模版时,函数从两个函数字典中查找:首先是模版函数字典,然后是全局函数字典。一般不在模版内定义函数,而是使用Funcs方法添加函数到模版里。
预定义的全局函数如下:
and函数返回它的第一个empty参数或者最后一个参数;就是说"and x y"等价于"if x then y else x";所有参数都会执行;
or返回第一个非empty参数或者最后一个参数;亦即"or x y"等价于"if x then x else y";所有参数都会执行;
not返回它的单个参数的布尔值的否定
len返回它的参数的整数类型长度
index执行结果为第一个参数以剩下的参数为索引/键指向的值;如"index x 1 2 3"返回x[1][2][3]的值;每个被索引的主体必须是数组、切片或者字典。
print即fmt.Sprint
printf即fmt.Sprintf
println即fmt.Sprintln
html返回与其参数的文本表示形式等效的转义HTML。这个函数在html/template中不可用。
urlquery以适合嵌入到网址查询中的形式返回其参数的文本表示的转义值。这个函数在html/template中不可用。
js返回与其参数的文本表示形式等效的转义JavaScript。
call执行结果是调用第一个参数的返回值,该参数必须是函数类型,其余参数作为调用该函数的参数;如"call .X.Y 1 2"等价于go语言里的dot.X.Y(1, 2);其中Y是函数类型的字段或者字典的值,或者其他类似情况;call的第一个参数的执行结果必须是函数类型的值(和预定义函数如print明显不同);该函数类型值必须有1到2个返回值,如果有2个则后一个必须是error接口类型;如果有2个返回值的方法返回的error非nil,模板执行会中断并返回给调用模板执行者该错误;
比较函数
布尔函数会将任何类型的零值视为假,其余视为真。
下面是定义为函数的二元比较运算的集合:
eq 如果arg1 == arg2则返回真
ne 如果arg1 != arg2则返回真
lt 如果arg1 < arg2则返回真
le 如果arg1 <= arg2则返回真
gt 如果arg1 > arg2则返回真
ge 如果arg1 >= arg2则返回真
为了简化多参数相等检测,eq(只有eq)可以接受2个或更多个参数,它会将第一个参数和其余参数依次比较,返回下式的结果:
{{eq arg1 arg2 arg3}}
比较函数只适用于基本类型(或重定义的基本类型,如”type Celsius float32”)。但是,整数和浮点数不能互相比较。
自定义函数
Go的模板支持自定义函数。
package mainimport ("fmt""html/template""io/ioutil""net/http"
)type UserInfo struct{Name stringGender stringAge int
}func sayHello (w http.ResponseWriter, r *http.Request){//1、定义模版htmlByte, err := ioutil.ReadFile("./f.tmpl")if err != nil {fmt.Println("Read tmpl,err=", err)return}//***自定义一个模版函数fun := func(arg UserInfo) (string ,error){return "天理" + arg.Name, nil}//2、解析模版---采用链式操作在Parse之前调用Funcs添加自定义的fun函数tmpl, err := template.New("f.tmpl").Funcs(template.FuncMap{"fun": fun,}).Parse(string(htmlByte))if err != nil {fmt.Println("create template failed,err=", err)return}user := UserInfo{Name: "小王子",Gender: "男",Age: 18,}//使用user渲染模版,并将结果写入wtmpl.Execute(w,user)
}func main() {http.HandleFunc("/", sayHello)err := http.ListenAndServe(":9000", nil)if err != nil {fmt.Println("HTTP server failed,err:", err)return}
}
可以在模版文件 hello.tmpl中按照如下方式使用我们自定义的 fun 函数了
{{fun .Name}}
嵌套template
我们可以在template 中嵌套其他的template。这个template 可以是单独的文件,也可以是通过define定义的template
举个例子: t.tmpl 文件内容如下:
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>tmpl test</title>
</head>
<body><h1>测试嵌套template语法</h1><hr>{{template "ul.tmpl"}}<hr>{{template "ol.tmpl"}}
</body>
</html>{{ define "ol.tmpl"}}
<ol><li>吃饭</li><li>睡觉</li><li>打豆豆</li>
</ol>
{{end}}
ul.tmpl 文件内容如下:
<ul><li>注释</li><li>日志</li><li>测试</li>
</ul>
模版继承 block
{{block “name” pipeline}} T1 {{end}}
block 是定义模版 {{define “name”}} T1 {{end}} 和执行{{template “name” pipeline}} 缩写,典型的用法是定义一组根模版,然后通过在其中重新定义块模版进行自定义。
定义一个基础模版 template/base.tmpl ,内容如下:
<!DOCTYPE html>
<html lang="zh-CN">
<head><title>Go Template</title>
</head>
<body>
<div class="container-fluid">{{block ""content . }}{{end}}
</div>
</body>
</html>
然后定义一个 template/index.tmpl ,继承 base.tmpl :
{{template "base.tmpl"}}{{define "content"}}<div>Hello World</div>
{{end}}
然后使用 template.ParseGlob 按照正则匹配规则解析模版文件,然后通过 ExecuteTemplate 渲染指定的模版:
func index(w http.ResponseWriter, r *http.Request){//1、定义模版//2、解析模版t, err := template.ParseGlob("templates/*.tmpl")if err != nil {fmt.Println("ParseGlob failed,err=", err)return}//3、渲染模版err = t.ExecuteTemplate(w,"index.tmpl", nil)if err != nil {fmt.Println("execute failed,err=", err)return}
}
如果我们的模版名称冲突了,例如不同业务线下都定义了一个 index.tmpl 模版,我们可以通过下面两种方法来解决。
- 1、在模版文件开头使用{{define 模版名}} 语句显式的为模版命名
- 2、可以把模版文件存放在template文件夹下面的不同目录中,然后使用
template.ParseGlob("template/**/*.tmpl")
解析模版
例子
base.tmpl
<!DOCTYPE html>
<html lang="zh-CN">
<head><title>模版继承</title><style>* {margin: 0;}.nav {height: 50px;width: 100%;position: fixed;top: 0;background-color: blanchedalmond;}.main {margin-top:50px;}.menu {width: 20%;height: 100%;position: fixed;left: 0;background-color: aqua;}.center {text-align: center;}</style>
</head>
<body><div class="nav"></div>
<div class="main"><div class="menu"></div><div class="content center">{{block "content" .}}{{end}}</div>
</div></body>
</html>
home.tmpl
<!DOCTYPE html>
<html lang="zh-CN">
<head><title>模版继承</title><style>* {margin: 0;}.nav {height: 50px;width: 100%;position: fixed;top: 0;background-color: blanchedalmond;}.main {margin-top:50px;}.menu {width: 20%;height: 100%;position: fixed;left: 0;background-color: aqua;}.center {text-align: center;}</style>
</head>
<body><div class="nav"></div>
<div class="main"><div class="menu"></div><div class="content center"><h1>这是home页面</h1><p>{{.}}</p></div>
</div></body>
</html>
index.tmpl
<!DOCTYPE html>
<html lang="zh-CN">
<head><title>模版继承</title><style>* {margin: 0;}.nav {height: 50px;width: 100%;position: fixed;top: 0;background-color: blanchedalmond;}.main {margin-top:50px;}.menu {width: 20%;height: 100%;position: fixed;left: 0;background-color: aqua;}.center {text-align: center;}</style>
</head>
<body><div class="nav"></div>
<div class="main"><div class="menu"></div><div class="content center"><h1>这是home页面</h1><p>{{.}}</p></div>
</div></body>
</html>
nhome.tmpl
{{/*继承根模板*/}}{{template "base.tmpl"}}
{{/*重新定义块模板*/}}
{{define "content"}}<h1>这是home页面</h1><p>Hello {{ . }}</p>
{{end}}
nindex.tmpl
{{/*继承根模板*/}}{{template "base.tmpl"}}
{{/*重新定义块模板*/}}
{{define "content"}}<h1>这是index页面</h1><p>Hello {{ . }}</p>
{{end}}
main.go
package mainimport ("fmt""html/template""net/http"
)func index(w http.ResponseWriter, r *http.Request){//1、定义模版//2、解析模版t, err := template.ParseFiles("./templates/index.tmpl")if err != nil {fmt.Println("ParseFiles failed,err=", err)return}//3、渲染模版msg := "小王"err = t.Execute(w, msg)if err != nil {fmt.Println("execute failed,err=", err)return}
}func home(w http.ResponseWriter, r *http.Request){//1、定义模版//2、解析模版t, err := template.ParseFiles("./templates/home.tmpl")if err != nil {fmt.Println("ParseFiles failed,err=", err)return}//3、渲染模版msg := "大王"err = t.Execute(w, msg)if err != nil {fmt.Println("execute failed,err=", err)return}
}
func nhome(w http.ResponseWriter, r *http.Request){//1、定义模版//2、解析模版t, err := template.ParseFiles("./templates/base.tmpl", "./templates/nhome.tmpl")if err != nil {fmt.Println("ParseFiles failed,err=", err)return}//3、渲染模版msg := "杨璐羽"err = t.ExecuteTemplate(w, "nhome.tmpl", msg)if err != nil {fmt.Println("execute failed,err=", err)return}
}func nindex(w http.ResponseWriter, r *http.Request){//1、定义模版//2、解析模版t, err := template.ParseFiles("./templates/base.tmpl", "./templates/nindex.tmpl")if err != nil {fmt.Println("ParseFiles failed,err=", err)return}//3、渲染模版msg := "田毅"err = t.ExecuteTemplate(w, "nindex.tmpl",msg)if err != nil {fmt.Println("execute failed,err=", err)return}
}func main() {http.HandleFunc("/home",home)http.HandleFunc("/index",index)http.HandleFunc("/nhome",nhome)http.HandleFunc("/nindex",nindex)err := http.ListenAndServe(":9090", nil)if err != nil {fmt.Println("Listen failed,err=", err)return}
}
修改默认的标识符
Go标准库的模版引擎使用的花括号{{和 }}作为标识,而许多前端框架 (如 vue 和 AngularJS)也使用{{}}作为标识符,所以当我们同时使用Go语言模版引擎和以上前端框架时就会发生冲突,这个时候我们就需要修改标识符,修改前端的或者修改Go语言的。这里演示如何修改go语言模版引擎默认的标识符:
template.New("test").Delims("{[" , "]}").ParseFiles("./t.tmpl")
text/tempalte 与 html/template的区别
后者 针对的是需要返回HTML内容的场景,在模版渲染过程中会对一些有风险的内容进行转义,以此来防范跨站脚本攻击。
例如,我定义下面的模版文件
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>Hello</title>
</head>
<body>{{.}}
</body>
</html>
这个时候传入一段JS代码并使用html/template 去渲染改文件,会在页面上显示出转义后的JS内容。
这就是html/template为我们做的事。 这就是html/template为我们做的事。
但是在某些场景下,我们如果相信用户输入的内容,不想转义的话,可以自行编写一个safe函数,手动返回一个 template.HTML 类型的内容,示例如下:
func xss(w http.ResponseWriter, r *http.Request){tmpl,err := template.New("xss.tmpl").Funcs(template.FuncMap{"safe": func(s string)template.HTML {return template.HTML(s)},}).ParseFiles("./xss.tmpl")if err != nil {fmt.Println("create template failed, err:", err)return}jsStr := `<script>alert('嘿嘿嘿')</script>`err = tmpl.Execute(w, jsStr)if err != nil {fmt.Println(err)}
}
这样我们只需要在模板文件不需要转义的内容后面使用我们定义好的safe函数就可以了。
{{ . | safe }}
示例
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>xss</title>
</head>
<body>
{{ .str1 }}
{{ safe .str2 }}
{{/*or*/}}
{{ .str2 | safe }}
</body>
</html>
package mainimport ("fmt""html/template""io/ioutil""net/http"
)type UserInfo struct{Name stringAge intGender string
}func sayHello (w http.ResponseWriter, r *http.Request){//1、定义模版htmlByte, err := ioutil.ReadFile("./f.tmpl")if err != nil {fmt.Println("Read tmpl,err=", err)return}//***自定义一个模版函数fun := func(arg UserInfo) (string ,error){return "天理" + arg.Name, nil}//2、解析模版---采用链式操作在Parse之前调用Funcs添加自定义的fun函数tmpl, err := template.New("f.tmpl").Funcs(template.FuncMap{"fun": fun,}).Parse(string(htmlByte))if err != nil {fmt.Println("create template failed,err=", err)return}user := UserInfo{Name: "小王子",Gender: "男",Age: 18,}//使用user渲染模版,并将结果写入wtmpl.Execute(w,user)
}
func tmplDemo(w http.ResponseWriter, r *http.Request){//1、定义模版//2、解析模版t, err := template.ParseFiles("./t.tmpl", "./ul.tmpl")if err != nil {fmt.Println("Parse failed,err=", err)return}//3、渲染模版user := UserInfo{Name: "小王子",Age: 18,Gender: "男",}t.Execute(w,user)
}func xss (w http.ResponseWriter, r *http.Request){//1、定义模版//2、解析模版//t, err := template.ParseFiles("./xss.tmpl")//if err != nil {// fmt.Println("Parse Failed,err=", err)// return//}//解析模版之前定义一个自定义函数safet, err := template.New("xss.tmpl").Funcs(template.FuncMap{"safe": func(str string)template.HTML {return template.HTML(str)},}).ParseFiles("./xss.tmpl")//3、渲染模版str1 := "<script>alert(123);</script>"str2 := "<a href='http://liwenzhou.com'>liwenzhou的博客</a>"err = t.Execute(w, map[string]interface{}{"str1": str1,"str2": str2,})if err != nil {fmt.Println("Execute Failed,err=", err)return}
}func main() {http.HandleFunc("/say", sayHello)http.HandleFunc("/tmpl", tmplDemo)http.HandleFunc("/xss",xss)err := http.ListenAndServe(":9000", nil)if err != nil {fmt.Println("HTTP server failed,err:", err)return}
}
2、HTML渲染
首先定义一个存放模版文件的templates文件夹,然后在其内部按照业务分别定义一个posts文件夹和一个users文件夹。
posts/index.html文件的内容如下:
{{define "posts/index.html"}}
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>posts/index</title>
</head>
<body>{{.title}}
</body>
</html>
{{end}}
users/index.html文件的内容如下:
{{define "users/index.html"}}
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>users/index</title>
</head>
<body>{{.title}}
</body>
</html>
{{end}}
Gin框架中使用LoadHTMLGlob()或者LoadHTMLFiles()方法进行HTML模板渲染。
func main() {r := gin.Default()r.LoadHTMLGlob("templates/**/*")//r.LoadHTMLFiles("templates/posts/index.html", "templates/users/index.html")r.GET("/posts/index", func(c *gin.Context) {c.HTML(http.StatusOK, "posts/index.html", gin.H{"title": "posts/index",})})r.GET("users/index", func(c *gin.Context) {c.HTML(http.StatusOK, "users/index.html", gin.H{"title": "users/index",})})r.Run(":8080")
}
3、自定义模板函数
定义一个不转义相应内容的safe模板函数如下:
func main() {router := gin.Default()router.SetFuncMap(template.FuncMap{"safe": func(str string) template.HTML{return template.HTML(str)},})router.LoadHTMLFiles("./index.tmpl")router.GET("/index", func(c *gin.Context) {c.HTML(http.StatusOK, "index.tmpl", "<a href='https://liwenzhou.com'>李文周的博客</a>")})router.Run(":8080")
}
在index.tmpl中使用定义好的safe模板函数:
<!DOCTYPE html>
<html lang="zh-CN">
<head><title>修改模板引擎的标识符</title>
</head>
<body>
<div>{{ . | safe }}</div>
</body>
</html>
4、静态文件处理
静态文件是指 .css .js 文件 图片
当我们渲染的HTML文件中引用了静态文件时,我们只需要按照以下方式在渲染页面前调用 gin.Static 方法即可。
func main(){r := gin.Default()r.Static("/static", "./static")r.LoadHttpGlob("template/**/*")// ...r.Run(":9090")
}
5、使用模版继承
Gin框架默认都是使用单模版,如果需要使用 block template 功能,可以通过 “github.com/gin-contrib/multitemplate” 库实现,具体的示例如下:
首先,假设我们项目目录下的templates文件夹下有以下模版文件,其中 home.tmpl 和 index.tmpl 继承了 base.tmpl:
templates
├── includes
│ ├── home.tmpl
│ └── index.tmpl
├── layouts
│ └── base.tmpl
└── scripts.tmpl
然后我们定义一个loadTemplates函数如下:
func loadTemplate(templatesDir string) multitemplate.Renderer {
r := multitemplate.NewRenderer()
layouts, err := filepath.Glob(templatesDir + "/layouts/*.tmpl")
if err != nil {panic(err.Error())
}
includes,err := filepath.Glob(templatesDir + "/includes/*.tmpl")
if err != nil{panic(err.Error())
}
//为layouts/和includes/目录生成 templates map
for _, include := range includes {layoutCopy := make([]string, len(layouts))copy(layoutCopy, layouts)files := append(layoutCopy, include)r.AddFromFiles(filepath.Base(include), files...)return r
}
}
我们在main函数中
func indexFunc(c *gin.Context) {c.HTML(http.StatusOK, "index.tmpl", nil)
}
func homeFunc(c *gin.Context) {c.HTML(http.StatusOK, "home.tmpl", nil)
}func main () {r := gin.Defaultr.HTMLRender = loadTemplate("./templates")r.GET("/index", indexFunc)r.GET("/home",homeFunc)r.Run()
}
6、补充文件路径处理
关于模版文件和静态文件的路径,我们需要根据项目的要求进行设置。可以使用下面的函数获取当前执行程序的路径。
func getCurentPath() string{if ex, err := os.Executable(); err == nil {return filepath.Dir(ex)}return "./"
}
7、JSON渲染
package mainimport ("github.com/gin-gonic/gin""net/http"
)func main() {r := gin.Default()//gin.H 是map[string]interface{}的缩写r.GET("/someJSON", func(c *gin.Context){//方式一:自己拼接JSONdata := gin.H{"message": "Hello world!","age": 18, "name": "小王子"}c.JSON(http.StatusOK, data)})r.GET("/moreJSON", func(c *gin.Context){//方式二:使用结构体。要灵活使用tag来对结构体字段做定制化操作,比如需要小写的时候var msg struct{Name string `json:"user"` // josn的序列化是通过反射原理,小写不可导出。Message stringAge int}msg.Name = "小王子"msg.Message = "Hello world!"msg.Age = 18c.JSON(http.StatusOK, msg)})r.Run(":8080")
}
8、XML渲染
func main() {r := gin.Default()// gin.H 是map[string]interface{}的缩写r.GET("/someXML", func(c *gin.Context) {// 方式一:自己拼接JSONc.XML(http.StatusOK, gin.H{"message": "Hello world!"})})r.GET("/moreXML", func(c *gin.Context) {// 方法二:使用结构体type MessageRecord struct {Name stringMessage stringAge int}var msg MessageRecordmsg.Name = "小王子"msg.Message = "Hello world!"msg.Age = 18c.XML(http.StatusOK, msg)})r.Run(":8080")
}
9、YMAL 渲染
r.GET("/someYAML", func(c *gin.Context) {c.YAML(http.StatusOK, gin.H{"message": "ok", "status": http.StatusOK})
})
10、protobuf渲染
r.GET("/someProtoBuf", func(c *gin.Context) {reps := []int64{int64(1), int64(2)}label := "test"// protobuf 的具体定义写在 testdata/protoexample 文件中。data := &protoexample.Test{Label: &label,Reps: reps,}// 请注意,数据在响应中变为二进制数据// 将输出被 protoexample.Test protobuf 序列化了的数据c.ProtoBuf(http.StatusOK, data)
})
相关文章:

G0第21章 :gin框架介绍、RESTful API、Gin渲染
G0第21章 :gin框架 01 内容介绍 https://gin-gonic.com/zh-cn/docs/ web本质 Web是基于HTTP协议进行交互的应用网络Web就是通过使用浏览器/APP访问的各种资源 package mainimport ("fmt""net/http" )func sayHello(w http.ResponseWriter, r…...

python list,dict操作
一、list 操作 Python中的列表是一种有序、可变的数据类型,可以存储任意类型的数据。以下是Python中常用的列表操作: 创建列表:使用[]或list()函数创建一个空列表,或者使用[value1, value2, ...]创建一个包含初始值的列表。 访问…...

我有一个页面a,在页面a中调用了一个组件,然后组件中要切换页面a的一块区域,该怎么实现?
你可以在组件中使用路由的编程式导航,通过访问路由实例来切换页面a的对应区域。具体来说,你可以先在页面a中设置一个具有唯一标识的占位符元素,然后在组件中通过路由实例访问这个元素并修改其内容或样式来实现区域切换。路由的编程式导航可以…...

ChatGPT唤醒AI游戏:AIGC持续走深,游戏或成AI最佳抓手
随着人工智能技术的不断发展,AI在游戏行业的应用日益深入。本文将详细探讨ChatGPT在AI游戏领域的应用,以及游戏如何成为AI技术的最佳抓手。让我们一起探讨这个有趣且充满潜力的领域。 一、引言 人工智能在各行各业都取得了巨大的成功,而游戏…...

远程服务和web服务和前端,三方通过socket和websocket进行双向通信传输数据
1. 什么是socket? 在计算机通信领域,socket 被翻译为“套接字”,它是计算机之间进行通信的一种约定或一种方式。通过 socket 这种约定,一台计算机可以接收其他计算机的数据,也可以向其他计算机发送数据。 2. 什么是websocket?…...

Linux 网络基础(2)应用层(http/https协议、请求格式、响应格式、session、cookie、加密传输)
说明:网络基础2讲解的是应用层的典型协议, 通过对于典型协议的理解,来体会数据的网络传输的软件层面的流程与原理。 面试中网络通信相关问题占了很大的比重,而网络通信相关的问题大多都集中在网络基础2这个单元中 下面是应用层的位…...

解决sshfs挂载报错
使用ssh命令和sshfs命令报错 read: Connection reset by peer rootjiangcheng01:~/common/remote# sshfs -o allow_other htrdxxx.xxx.xxx.xxx:/home/htrd /root/common/remote/dev01 read: Connection reset by peer 报错问题排查,追加命令 -o debug -o sshf s_d…...

由于过多的连接错误而被 MySQL服务器 阻止
Caused by: com.mysql.cj.exceptions.CJException: null, message from server: "Host 10.105.***.** is blocked because of many connection errors; unblock with mysqladmin flush-hosts" 这个错误可能表示当您尝试使用 IP 地址为 "10.105.***.**" 的…...

Go语言实现JDBC
Go语言操作数据库 Go语言提供了关于数据库的操作,包下有sql/driver 该包用来定义操作数据库的接口,这保证了无论使用哪种数据库,操作方式都是相同的; 准备工作: 下载驱动 需要在代码所在文件夹下执行相应的命令 go get github.com/go-sql-driver/mys…...

ubuntu修改环境变量的几种方法
ubuntu修改环境变量的几种方法 有多种方法可以修改Ubuntu系统的环境变量,包括: 临时修改环境变量:在终端中使用export命令可以临时修改环境变量。例如,要将PATH环境变量添加到新目录,可以运行以下命令: …...

基于html+css的图展示95
准备项目 项目开发工具 Visual Studio Code 1.44.2 版本: 1.44.2 提交: ff915844119ce9485abfe8aa9076ec76b5300ddd 日期: 2020-04-16T16:36:23.138Z Electron: 7.1.11 Chrome: 78.0.3904.130 Node.js: 12.8.1 V8: 7.8.279.23-electron.0 OS: Windows_NT x64 10.0.19044 项目…...

数据库基础——5.运算符
这篇文章我们来讲一下SQL语句中的运算符操作。 说点题外话:SQL本质上也是一种计算机语言,和C,java一样的,只不过SQL是用来操作数据库的。在C,java中也有运算符,这两种语言中的运算符和数学中的运算符差距不…...

JMeter 性能测试基本过程及示例
jmeter 为性能测试提供了一下特色: 2023年最新出炉性能测试教程,真实企业性能压测全流程项目实战训练大合集!_哔哩哔哩_bilibili2023年最新出炉性能测试教程,真实企业性能压测全流程项目实战训练大合集!共计11条视频&…...

漏洞复现 CVE-2018-2894 weblogic文件上传
vulhub weblogic CVE-2018-2894 1、 搭建好靶场,按提示访问 http://192.168.137.157:7001/console 按照给出的文档,会查看容器的日志,找到管理员用户名/密码为 weblogic / h3VCmK2L,暂时用不到,不需要登录 2、未授权…...

二叉树:填充每个节点的下一个右侧节点指针(java)
leetcode116:填充每个节点的下一个右侧节点指针 leetcode原题链接:题目描述递归解法一递归方法二(效率更高)二叉树专题 leetcode原题链接: 116题:填充每个节点的下一个右侧节点指针 题目描述 给定一个 完美二叉树 &a…...

Android 12.0修改系统默认设备类型的平板电脑类型为设备类型
1.概述 在12.0的系统rom产品开发中,对于产品设备类型都默认为tablet即平板电脑类型,即 product="tablet" 在一些不是平板的项目中,可能需要修改这个类型为device类型 即 product="device",这就需要找到相关设置系统属性的代码,修改系统属性就可以了 2…...

debug研究
debug研究 debug的condition 通常用在for循环里面 for循环中实际使用 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UsmJ93w5-1685344057464)(D:\typora_pic_all\image-20230529145417753.png)] log.info("当前共有{}条数据待处理", vos…...

zabbix监控系统
一、Zabbix概述 1、使用zabbix的原因 作为一个运维,需要会使用监控系统查看服务器状态以及网站流量指标,利用监控系统的数据去了解上线发布的结果,和网站的健康状态。 利用一个优秀的监控软件,我们可以: ●通过一个友好的界面进…...

Python入门学习
一、执行Python(Hello World)程序 对于大多数程序语言,第一个入门编程代码便是 “Hello World!”,以下代码为使用 Python 输出 “Hello World!” 1.1 创建hello.py文件 1.2 编写程序 #!/usr/bin/python…...

自动驾驶嵌入式开发工程师:车载SOC开发修炼秘籍
声明:本文档是博主在开发学习过程中写的笔记,本意是便于以后开发复盘,参考《 ug1144-petalinux-tools-reference-guide》、《ug1085》、黑金Zynq UltraScale MPSoC 5EV开发板资料、英伟达官方资料。大佬勿喷 大佬勿喷 大佬勿喷!&a…...

Linux之搭建环境
文章目录 1 FileZilla软件2 Linux搭建samba文件共享服务器,实现基于Linux和Windows的共享文件服务2.1 smaba的安装与基本应用2.2 samba的账号权限配置2.3 win系统下的文件无法复制到Linux共享文件夹中 1 FileZilla软件 在跟着正点原子教程安装后,出现如下…...

泡利矩阵(一)
〇、厄米矩阵 厄米矩阵(Hermitian Matrix),也称为自共轭矩阵(Self-adjoint Matrix),是线性代数中的一个重要概念。它是指一个复数域上的方阵,其转置矩阵与共轭矩阵相等。 具体来说,…...

通用支付系统设计
支付永远是一个公司的核心领域,因为这是一个有交易属性公司的命脉。那么,支付系统到底长什么样,又是怎么运行交互的呢?抛开带有支付牌照的金融公司的支付架构,下述链路和系统组成基本上符合绝大多数支付场景。其实整体可以看成是…...

metaRTC+ZLMediaKit实现webrtc的推拉流
概述 ZLMediaKit是一个基于C11的高性能运营级流媒体服务框架,是一个支持webrtc SFU的优秀的流媒体服务器系统。 metaRTC新版本支持whip/whep协议,支持whip/whep协议的ZLMediaKit推拉流。 信令通信 ZLMediaKit新版本支持whip和whep协议,支…...

【JavaSE】Java基础语法(八)
文章目录 🍓1. 类和对象🍹🍹1.1 类和对象的关系🍹🍹1.2 类的定义 🍓2. 对象内存图🍹🍹2.1 单个对象内存图🍹🍹2.2 多个对象内存图2.3 多个对象指向相同内存图…...

Java如何配置环境变量
Java如何配置环境变量 0. 前言1. 下载Java2. 配置环境变量2.1新建 Java_Home2.2 编辑Path情况1情况2 3. 验证安装 0. 前言 本节记录如何配置Java环境变量,用自己重装过的系统实操 操作系统:Windows10 专业版 Java版本:jdk1.7.0_07 1. 下载…...

android 12.0SystemUI 状态栏下拉快捷添加截图快捷开关
1.概述 在12.0的系统产品rom定制化开发中,对SystemUI的定制需求也是挺多的,在下拉状态栏中 添加截图快捷开关,也是常有的开发功能,下面就以添加 截图功能为例功能的实现 2.SystemUI 状态栏下拉快捷添加截图快捷开关的核心代码 frameworks/base/packages/SystemUI/res/va…...

【无标题】 Vue 路由库Router 【重点】 - 安装 - 基本使用 - 路由配置 - 路由模式 - 路由传递参数 - 路由内置对象 - 路由守卫
0.0 课程介绍 Vue 路由库Router 【重点】 安装基本使用路由配置路由模式路由传递参数路由内置对象路由守卫 Vue的内置API 【掌握】 ref Vue.set Vue.nextTick Vue.filter Vue.component Vue.use Vue.directive 1.0 Vue的路由Router 【重点】 1.1 路由作用 进行页面…...

RocksDB笔记 -- 整体架构
RocksDB是由Facebook开发的存储引擎, 它最初的目标是用于快速存储, 特别是Flash存储. 一个基于C开发keys-values存储引擎库. 整体架构 RocksDB由这三个基本结构组成: memtable, sstfile 和 logfile. 其中: memtable是一个内存数据结构, 新的写入会插入到memtable中, 同时可选…...

设计模式之单例模式入门介绍
一、设计模式概念 设计模式是被广泛使用的软件开发中的一种解决方案,它提供了一套被验证过的、可重用的设计思想,帮助开发人员更加高效地开发出可维护、易扩展的软件系统。 设计模式可以分为三类:创建型模式、结构型模式和行为型模式。 1.1…...