G0第21章 :gin框架

01 内容介绍

https://gin-gonic.com/zh-cn/docs/

请添加图片描述
请添加图片描述

web本质

  • Web是基于HTTP协议进行交互的应用网络
  • Web就是通过使用浏览器/APP访问的各种资源

请添加图片描述

请添加图片描述
请添加图片描述

package main

import (
   "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 main

import "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 main

import (
	"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 main

import "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 main

import (
	"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
	}
	// 利用给定数据渲染模板,并将结果写入w
	name := "小王子"
	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 main

import (
	"fmt"
	"html/template"
	"net/http"
)

type UserInfo struct{
	Name string
	Gender string
	Age 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
	}
	// 利用给定数据渲染模板,并将结果写入w
	user := 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 main

import (
	"fmt"
	"html/template"
	"io/ioutil"
	"net/http"
)

type UserInfo struct{
	Name string
	Gender string
	Age 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渲染模版,并将结果写入w
	tmpl.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 main

import (
	"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 main

import (
	"fmt"
	"html/template"
	"io/ioutil"
	"net/http"
)

type UserInfo struct{
	Name string
	Age int
	Gender 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渲染模版,并将结果写入w
	tmpl.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
	//}
	//解析模版之前定义一个自定义函数safe
	t, 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.Default
	r.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 main

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

func main() {
	r := gin.Default()

	//gin.H 是map[string]interface{}的缩写
	r.GET("/someJSON", func(c *gin.Context){
		//方式一:自己拼接JSON
		data := 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 string
			Age int
		}
		msg.Name = "小王子"
		msg.Message = "Hello world!"
		msg.Age = 18
		c.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) {
		// 方式一:自己拼接JSON
		c.XML(http.StatusOK, gin.H{"message": "Hello world!"})
	})
	r.GET("/moreXML", func(c *gin.Context) {
		// 方法二:使用结构体
		type MessageRecord struct {
			Name    string
			Message string
			Age     int
		}
		var msg MessageRecord
		msg.Name = "小王子"
		msg.Message = "Hello world!"
		msg.Age = 18
		c.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)
})
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐