G0第21章 :gin框架介绍、RESTful API、Gin渲染

news2024/11/28 10:54:14

G0第21章 :gin框架

01 内容介绍

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

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)
})

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/572896.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MKS SERVO4257D 闭环步进电机_系列1 产品简介

第1部分 产品概述 1.1 产品介绍 MKS SERVO 28D/35D/42D/57D 系列闭环步进电机是创客基地为满足市场需求而自主研发的一款产品。具备脉冲接口&#xff0c;RS485接口和CAN接口&#xff0c;内置高效FOC矢量算法&#xff0c;采用高精度编码器&#xff0c;通过位置反馈&#xff0c;有…

《深入理解计算机系统》读书笔记1.1-1.5

1.1信息就是位上下文 只由ASCLL字符构成的文件称为文本文件&#xff0c;所有其他文件都称为二进制文件。 系统中的所有的信息都由一串比特表示。区分不同数据对象的唯一方法是读到这些数据对象时的上下文。 1.2程序被其他程序翻译成不同的格式 预编译&#xff0c;编译&#xf…

EasyCVR视频融合平台设备分组共享功能的使用介绍

EasyCVR视频融合平台基于云边端一体化架构&#xff0c;具有强大的数据接入、处理及分发能力&#xff0c;平台支持海量视频汇聚管理&#xff0c;可支持多协议、多类型的设备接入&#xff0c;并能对设备进行分级、分组管理&#xff0c;并支持权限、角色分配&#xff0c;属于功能全…

PFC落石模拟

Landslide/Rockfall simulation 山体滑坡/落石模拟 https://www.youtube.com/watch?vWSa3909qYmI 模拟的目的在于通过导入团块的对象文件产生团块的二进制输出。 具体措施&#xff1a; (i) 使用导入的几何体形成团块模板 (ii) 使用Taghavi(2011)定义的BubblePack算法来生成卵石…

算法|4.归并排序及应用

算法|4.归并排序及应用 1.归并排序算法 题意&#xff1a;归并排序的递归和非递归实现 解题思路&#xff1a; ​ 递归实现&#xff1a; 预处理&#xff1a;数组为空或者长度小于2的直接返回调用子过程子过程终止条件LR分解成[L,mid]&#xff0c;[mid1,R] &#xff0c;子数组…

九章云极DataCanvas公司诚邀您共享AI基础软件前沿技术盛宴

“杭州通用人工智能论坛暨AIIA人工智能产业发展大会”将于2023年5月30日-31日在杭州举办。本次人工智能产业发展大会由中国信息通信研究院、中国人工智能产业发展联盟主办&#xff0c;杭州城西科创大走廊管委会、杭州市经济和信息化局、杭州未来科技城管理委员会、人工智能关键…

企业级信息系统开发——初探JdbcTemplate操作

文章目录 一、创建数据库与表1、创建数据库2、创建用户表3、用户表添加记录 二、打开Spring项目三、添加数据库相关依赖四、创建用户实体类五、创建用户数据访问接口六、创建用户数据访问接口实现类七、创建用户服务类八、创建数据库配置属性文件九、创建Spring配置文件十、创建…

Springboot +spring security,解决跨域问题

一.简介 这篇文章主要是解释什么是跨域&#xff0c;在Spring中如何解决跨域&#xff0c;引入Spring Security后Spring解决跨域的方式失效&#xff0c;Spring Security 如何解决跨域的问题。 二.什么是跨域 跨域的概率&#xff1a; 浏览器不能执行其他网站的脚本&#xff0c…

jsp页面调试

现象: 访问jsp页面, 页面为空, 网络请求显示失败, 控制台打印错误net::ERR_INCOMPLETE_CHUNKED_ENCODING 200 分析: 错误描述&#xff1a;编码模块不完整&#xff0c;返回浏览器的流不完整 可能得原因: 1、网络是否稳定 2、服务器端是否有对响应数据做限制&#xff0c;比如…

【App自动化测试】(十七)遍历测试工具——Android Maxim

目录 1. Android Maxim介绍2. Android Maxim使用方法3.Android Maxim运行命令4.Android Maxim的策略5.实例演示——Windows系统&#xff0c;使用AVD模拟器&#xff0c;系统 Android6.0 1. Android Maxim介绍 Android Maxim是基于遍历规则的高性能Android Monkey&#xff0c;适…

基于SpringBoot+Vue的毕业生信息招聘平台设计与实现

博主介绍&#xff1a; 大家好&#xff0c;我是一名在Java圈混迹十余年的程序员&#xff0c;精通Java编程语言&#xff0c;同时也熟练掌握微信小程序、Python和Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我擅长在JavaWeb、SSH、SSM、SpringBoot等框架下…

Elasticsearch常用接口使用说明以及postman调用调试

查看集群状态 接口url&#xff1a;http://xxxx:9200/_cat 查看所有索引 http://xxxx:9200/_cat/indices?v 创建索引 http://xxxx:9200/test-20230526?pretty 返回值 { "acknowledged": true, "shards_acknowledged": true, "index": &quo…

Opencv-C++笔记 (2) : opencv的矩阵操作

文章目录 创建与初始化1.1 数据类型1.2 基本方法1.3 初始化方法 矩阵加减法矩阵乘法矩阵转置矩阵求逆矩阵非零元素个数矩阵均值与标准差矩阵全局极值及位置GEMM 通用矩阵乘法Transform 对数组每一个元素执行矩阵变换MulTransposed 计算数组和数组的转置的乘积Trace 返回矩阵的迹…

WIN10:Cognos10.2_x32安装

一、Cognos BI Server 10.2 32Bit 二、Cognos Transformer 10.2 三、Cognos Framework Manager 10.2 四、环境 1、如果使用Cognos自带的Tomcat web容器&#xff0c;将E:\common\Cognos\c10\webcontent下的所有文件拷贝到E:\common\Cognos\c10\webapps\p2pd 下面.(一般我们就使…

redis高级篇 缓存双写一致性之更新策略

闲聊 缓存通用查询3部曲 redis 中数据&#xff0c;返回redis 中的数据redis 中没有&#xff0c;查询数据库并返回完成第二部的同时&#xff0c;将数据库查询结果写到redis,redis和数据库数据一致. 谈谈双写一致性的理解 1.如果redis 中有数据&#xff1a;需要和数据库中的相…

什么是可视化开发平台?拥有什么优势?

随着科技的进步和发展&#xff0c;可视化开发平台拥有广阔的市场前景&#xff0c;在提升企业办公企业效率、做好数据管理等方面具有自身的特色和优势。在办公自动化发展的年代&#xff0c;低代码开发平台是助力企业实现提质增效办公效率的得力助手&#xff0c;其可视化、易操作…

Windows操作系统存储管理——实存管理和虚存管理

我是荔园微风&#xff0c;作为一名在IT界整整25年的老兵&#xff0c;今天总结一下Windows操作系统存储管理——实存管理和虚存管理。 存储器管理的对象是主存&#xff08;内存&#xff09;。重点是要知道实存和虚存的管理&#xff0c;而虚存管理重点是逻辑地址和物理地址间的转…

桥梁结构健康监测解决方案

城市桥梁担负着城市的交通和运输网络的重要角色&#xff0c;是城市生命线的重要组成部分。然而&#xff0c;随着时间的推移和日益增长的负荷&#xff0c;桥梁可能会受到各种因素的损害&#xff0c;如自然灾害、疲劳、腐蚀等。因此&#xff0c;桥梁结构健康监测变得至关重要&…

osg给osg::Geometry(自己绘制的几何体)添加纹理(二)

目录 1. 前言 2. 自会集合体贴纹理 2.1. 一张图贴到整个几何体 2.2. 几何体每个面贴不同的图片纹理 3. 说明 1. 前言 前文讲述了如何给osg自带的几何体&#xff0c;如&#xff1a;BOX等&#xff0c;添加纹理&#xff0c;文章参考链接如下&#xff1a; osg给osg::Geometry&…

6步带你弄懂敏捷软件开发管理

敏捷开发是一种项目管理和软件开发的迭代方法&#xff0c;可帮助团队较快地为客户创造价值&#xff0c;同时减少问题。为了获得好处&#xff0c;软件项目团队需要知道如何正确使用敏捷管理方法。 了解敏捷宣言 敏捷宣言阐述了基本的价值观&#xff0c;还详细说明了敏捷团队应…