命令记不住

登陆

Gin 框架基础 (一)

Gin框架基础

1.Gin初识

Web 编程主要涉及这几个方面:

  • web路由 (uri解析/ restful设计)
  • 中间件 (业务和非业务分离)
  • validator验证 (业务代码的验证和检验)
  • db/orm/sql builder (结构体映射,序列化等)

Gin是用Golang开发的一个微框架,类似Martini的API,重点是小巧、易用、性能好很多,也因为httprouter 的性能提高了40倍。

gin框架基本示例
  • gin

  • server

  • 创建路由对象

  • 请求相关

    • 请求参数

      • param --> /news/:year
      • QueryString --> /news/2020?page=1
      • form表单
      • Json格式的
      • 上传文件
    • 参数绑定

      • ShouldBind()
  • 路由注册

    • GET/POST/PUT/DELETE/PATCH/OPTION/HEAD
    • ANY
    • NOROUTE
  • 路由组 router.Group

  • 返回响应

    • HTML --> c.HTML(状态码, 模板名, 数据)
    • JSON --> c.JSON(状态码, 支持JSON序列化的数据)
      • gin.H
      • 自定义数据
    • 静态文件处理 --> c.Static(模板文件中用的路径, 静态文件实际存储的目录)
  • 模板继承 --> multitemplate

2.创建GinProject

用 GoLand 新建项目的时候,我们选择 Go Modules(vgo) ,建议添加goproxy,填写我们的项目地址和项目名称,我们命名为 GinHello 。

点击 Create ,此时 Goland 为我们生成了项目目录,包括一个 Go module 文件,go mod 是 Go 官方引入的一个依赖管理工具

GinHello
|
|-go.mod
添加依赖

通过 go mod 文件进行依赖的。

require github.com/gin-gonic/gin v1.4.0

我们把上面的依赖进行添加到 go module 中, GoLand 会自动帮我们进行依赖的下载和管理。

(自定义安装)使用go-get安装Gin

在命令行下执行安装
go module 模式下,下载的依赖信息都会保存在 $GOPATH/pkg/mod/

go get -u github.com/gin-gonic/gin

go module 模式下,下载的依赖信息都会保存在 $GOPATH/pkg/mod/

Hello Gin

go module 模式下,下载的依赖信息都会保存在 $GOPATH/pkg/mod/当完成依赖的添加,就可以开始写代码了。
新建一个 main.go 文件。

package main

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

func main() {
	router := gin.Default()
	router.Run()
}

Gin 只需要两行代码就可以把我们的服务跑起来。
只要点击运行,项目便会启动一个 8080 端口,打开浏览器 localhost:8080 便可以看到页面上提示出 404 page not found ,这是因为根路由上并没有返回任何结果。同时可以在控制台上看到一些打印信息,其中就包括刚刚访问根路由的端口

基本示例

项目已经启动了,那么如何返回一个接口呢?
通过 router 的 Handle 进行配置我们返回的参数

package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {
	router := gin.Default()
	router.GET("/", func(context *gin.Context) {
		context.String(http.StatusOK, "ok")
	})
	router.Run()
}

同样,我们还可以进行 POST , PUT , DELETE 等请求方式

3.Gin Router

3.1 服务器

默认服务器
默认路由直接使用run启动即可

router.Run()

http服务器
除了默认服务器中 router.Run() 的方式外,还可以用 http.ListenAndServe() ,比如

func main() {
router := gin.Default()
http.ListenAndServe(":8080", router)
}

或者自定义HTTP服务器的配置

func main() {
    router := gin.Default()
    s := &http.Server{
    Addr: ":8080",
    Handler: router,
    ReadTimeout: 10 * time.Second,
    WriteTimeout: 10 * time.Second,
    MaxHeaderBytes: 1 << 20,
    }
    s.ListenAndServe()
}
3.2 路由

基本路由
基本路由 gin 框架中采用的路由库是 httprouter

// 创建带有默认中间件的路由:
// 日志与恢复中间件
router := gin.Default()
//创建不带中间件的路由:
//r := gin.New()
router.GET("/someGet", getting)
router.POST("/somePost", posting)
router.PUT("/somePut", putting)
router.DELETE("/someDelete", deleting)
router.PATCH("/somePatch", patching)
router.HEAD("/someHead", head)
router.OPTIONS("/someOptions", options)

Gin 的路由支持 GET , POST , PUT , DELETE , PATCH , HEAD , OPTIONS 请求,同时还有一个 Any函数,可以同时支持以上的所有请求。

router.Any("/some", someHandler)

此外,为没有配置处理函数的路由添加处理程序,默认情况下它返回404代码,下面的代码为没有匹配到路由的请求都返回 views/404.html 页面。

r.NoRoute(func(c *gin.Context) {
c.HTML(http.StatusNotFound, "views/404.html", nil)
})
3.3 路由参数

gin的路由来自 httprouter 库。因此httprouter具有的功能, gin也具有,不过gin不支持路由正则表达式。

3.4 API参数

api 参数通过Context的Param方法来获取。

router.GET("/user/:name", func(c *gin.Context) {
name := c.Param("name")
c.String(http.StatusOK, name)
})

冒号 : 加上一个参数名组成路由参数。可以使用c.Params的方法读取其值。当然这个值是字串string。
诸如 /user/ls ,和 /user/hello 都可以匹配,而 /user/ 和 /user/ls/ 不会被匹配。

router.GET("/user/:name/*action", func(c *gin.Context) {
name := c.Param("name")
action := c.Param("action")
message := name + " is " + action
c.String(http.StatusOK, message)
})

除了 : , gin还提供了 * 号处理理参数, * 号能匹配的规则就更多

3.5 URL参数

url

web提供的服务通常是client和server的交互。其中客户端向服务器发送请求,除了路由参数,其他的参数无非两种,查询字符串query string和报文体body参数。所谓query string,即路由用,用 ? 以后连接的 key1=value2&key2=value2 的形式的参数。当然这个key-value是经过urlencode编码。

URL 参数通过 DefaultQuery 或 Query 方法获取。

对于参数的处理,经常会出现参数不存在的情况,对于是否提供默认值, gin也考虑了,并且给出了一个优雅的方案,使用c.DefaultQuery方法读取参数,其中当参数不不在的时候,提供一个默认值。使用Query方法读取正常参数,当参数不存在的时候,返回空字串。

package main

import (
	"fmt"
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {
	router := gin.Default()
	router.GET("welcome", func(c *gin.Context) {
		name := c.DefaultQuery("name", "Guest")
		c.String(http.StatusOK, fmt.Sprintf("Hello %s ", name))
	})
	router.Run(":9527")
}
3.6 表单参数

http的报文体传输数据就比query string稍微复杂一点,常见的格式就有四种。例如application/json , application/x-www-form-urlencoded , application/xml 和multipart/form-data 。后面一个主要用于图片上传。 json格式的很好理解, urlencode其实也不难,无非就是把query string的内容,放到了body体里,同样也需要urlencode。默认情况下,c.PostFROM解析的是 x-www-form-urlencoded 或 from-data 的参数。

表单参数通过 PostForm 方法获取:

package main

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

func main() {
	router := gin.Default()
	//form
	router.POST("/form", func(c *gin.Context) {
		type1 := c.DefaultPostForm("type", "alert") //可设置默认值
		username := c.PostForm("username")
		password := c.PostForm("password")
		//hobbys := c.PostFormMap("hobby")
		//hobbys := c.QueryArray("hobby")
		hobbys := c.PostFormArray("hobby")
		c.String(http.StatusOK, fmt.Sprintf("type is %s, username is %s,password is %s,hobby is %v", type1, username, password, hobbys))
	})
	router.Run(":9527")
}

Postman进行请求或者提供一个html页面(login.html),来进行post请求

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>
<body>
<form action="http://127.0.0.1:9527/form" method="post"
      enctype="application/x-www-form-urlencoded">
    用户名: <input type="text" name="username">
    <br>
    密&nbsp&nbsp&nbsp码: <input type="password" name="password">
    <br>
    兴&nbsp&nbsp&nbsp趣:
    <input type="checkbox" value="girl" name="hobby">女人
    <input type="checkbox" value="game" name="hobby">游戏
    <input type="checkbox" value="money" name="hobby">金钱
    <br>
    <input type="submit" value="登录">
</form>
</body>
</html>

使用PostForm形式,注意必须要设置Post的type,同时此方法中忽略URL中带的参数,所有的参数需要从Body中获得

3.7 文件上传

Any方式上传单个文件

准备upload.html文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>upload</title>
</head>
<form action="/upload" method="post" enctype="multipart/form-data" >
    <input type="file" name="xx">
    <button type="submit">提交</button>
</form>
</body>
</html>

准备主程序

package main

import (
	"fmt"
	"log"
	"net/http"

	"github.com/gin-gonic/gin"
)

func uploadHandler(c *gin.Context) {
	if c.Request.Method == http.MethodPost {
		//上传文件 发送POST请求
		//1.获取用户上传的文件
		fh, _ := c.FormFile("xx")
		fmt.Println(fh.Filename, fh.Size)
		//2.server端保存
		err := c.SaveUploadedFile(fh, fh.Filename)
		if err != nil {
			log.Fatal("上传失败:", err)
		}
		//3.返回响应
		c.String(http.StatusOK, "上传成功")
	} else {
		//想要访问上传文件的页面
		c.HTML(http.StatusOK, "upload.html", nil)
	}
}
func main() {
	r := gin.Default()
	r.LoadHTMLGlob("templates/*")
	r.Any("/upload", uploadHandler)
	r.Run(":8082")
}
package main

import (
	"fmt"
	"log"
	"net/http"

	"github.com/gin-gonic/gin"
)

func uploadHandler(c *gin.Context) {
	if c.Request.Method == http.MethodPost {
		//上传文件 发送POST请求
		//1.获取用户上传的文件
		fh, _ := c.FormFile("xx")
		fmt.Println(fh.Filename, fh.Size)
		//2.server端保存
		err := c.SaveUploadedFile(fh, fh.Filename)
		if err != nil {
			log.Fatal("上传失败:", err)
		}
		//3.返回响应
		c.String(http.StatusOK, "上传成功")
	} else {
		//想要访问上传文件的页面
		c.HTML(http.StatusOK, "upload.html", nil)
	}
}
func main() {
	r := gin.Default()
	r.LoadHTMLGlob("templates/*")
	r.Any("/upload", uploadHandler)
	r.Run(":8082")
}

login

  • 上传单个文件

前面介绍了基本的发送数据,其中 multipart/form-data 转用于文件上传。 gin文件上传也很方便,和原生的net/http方法类似,不同在于gin把原生的request封装到c.Request中了。
首先我们创建一个go文件, demo06_file.go

package main

import (
	"fmt"
	"log"
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {
	router := gin.Default()
	// Set a lower memory limit for multipart forms (default is 32 MiB)
	// router.MaxMultipartMemory = 8 << 20 // 8 MiB
	router.POST("/upload", func(c *gin.Context) {
		// single file
		file, _ := c.FormFile("file")
		log.Println(file.Filename)
		// Upload the file to specific dst.
		c.SaveUploadedFile(file, file.Filename)
		/*
		   也可以直接使⽤用io操作,拷⻉贝⽂文件数据。
		   out, err := os.Create(filename)
		   defer out.Close()
		   _, err = io.Copy(out, file)
		*/
		c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename))
	})
	router.Run(":8080")
}

使用 c.Request.FormFile 解析客户端文件name属性。如果不传文件,则会抛错,因此需要处理这个错误。此处我们略写了错误处理。一种是直接用c.SaveUploadedFile()保存文件。另一种方式是使用os的操作,把文件数据复制到硬盘上。

然后我们创建一个html页面file.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>⽂文件</title>
</head>
<body>
<form action="http://127.0.0.1:8080/upload" method="post"
      enctype="multipart/form-data">
    头像:
    <input type="file" name="file">
    <br>
    <input type="submit" value="提交">
</form>
</body>
</html>

运行程序后,打开浏览器传递文件

终端命令访问

http  :curl -X POST http://127.0.0.1:8080/upload -F "file=F:\123\x200\001.jpg" -H "Content-Type: multipart/form-data"
  • 上传多个文件

所谓多个文件,无非就是多一次遍历文件,然后一次copy数据存储即可。

package main

import (
	"fmt"
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {
	router := gin.Default()
	// Set a lower memory limit for multipart forms (default is 32 MiB)
	router.MaxMultipartMemory = 8 << 20 // 8 MiB
	//router.Static("/", "./public")
	router.POST("/upload", func(c *gin.Context) {
		// Multipart form
		form, err := c.MultipartForm()
		if err != nil {
			c.String(http.StatusBadRequest, fmt.Sprintf("get form err: %s", err.Error()))
			return
		}
		files := form.File["files"]
		for _, file := range files {
			if err := c.SaveUploadedFile(file, file.Filename); err != nil {
				c.String(http.StatusBadRequest, fmt.Sprintf("upload file err:%s", err.Error()))
				return
			}
		}
		c.String(http.StatusOK, fmt.Sprintf("Uploaded successfully %d files ",
			len(files)))
	})
	router.Run(":8080")
}

然后我们提供一个html页面,当然也可以使用终端命令

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>文件s</title>
</head>
<body>
<h1>上传多个文件</h1>
<form action="http://127.0.0.1:8080/upload" method="post"
      enctype="multipart/form-data">
    Files: <input type="file" name="files" multiple><br><br>
    <input type="submit" value="提交">
</form>
</body>
</html>
3.8路由组

router group是为了方便一部分相同的URL的管理,新建一个go文件(demo08_group.go)

package main
import (
    "github.com/gin-gonic/gin"
    "net/http"
    "fmt"
)
func main() {
    router := gin.Default()
    // Simple group: v1
    v1 := router.Group("/v1")
    {
        v1.GET("/login", loginEndpoint)
        v1.GET("/submit", submitEndpoint)
        v1.POST("/read", readEndpoint)
    }
    // Simple group: v2
    v2 := router.Group("/v2")
    {
        v2.POST("/login", loginEndpoint)
        v2.POST("/submit", submitEndpoint)
        v2.POST("/read", readEndpoint)
    }
    router.Run(":8080")
}
func loginEndpoint(c *gin.Context) {
    name := c.DefaultQuery("name", "Guest") //可设置默认值
    c.String(http.StatusOK, fmt.Sprintf("Hello %s \n", name))
}
func submitEndpoint(c *gin.Context) {
    name := c.DefaultQuery("name", "Guest") //可设置默认值
    c.String(http.StatusOK, fmt.Sprintf("Hello %s \n", name))
}
func readEndpoint(c *gin.Context) {
    name := c.DefaultQuery("name", "Guest") //可设置默认值
    c.String(http.StatusOK, fmt.Sprintf("Hello %s \n", name))
}

运行程序后,可以通过一个html页面访问,也可以通过终端使⽤用命令直接访问,此处我们使用终端:

curl http://127.0.0.1:8080/v1/login?name=ls

路由组也是支持嵌套的,例如:

shopGroup := r.Group("/shop")
{
shopGroup.GET("/index", func(c *gin.Context) {...})
shopGroup.GET("/cart", func(c *gin.Context) {...})
shopGroup.POST("/checkout", func(c *gin.Context) {...})
// 嵌套路路由组
xx := shopGroup.Group("xx") // r.Group(xx)
xx.GET("/oo", func(c *gin.Context) {...})
}

通常我们将路由分组用在划分业务逻辑或划分API版本时。

4.路由原理

Gin框架中的路由使用的是httprouter这个库的原理。
其基本原理就是构造一个路由地址的前缀树。

httprouter源码分析httprouter

5.Gin 请求的数据映射出来

数据解析绑定

模型绑定可以将请求体绑定给一个类型。目前Gin⽀支持JSON、 XML、 YAML和标准表单值的绑定。简单来说,,就是根据Body数据类型,将数据赋值到指定的结构体变量中 (类似于序列化和反序列化)

Gin提供了两套绑定方法:

Mustbind
  • 方法: Bind , BindJSON , BindXML , BindQuery , BindYAML
  • 行为:这些方法使用MustBindWith。如果存在绑定错误,则用c终止请求,使用c.AbortWithError (400).SetType(ErrorTypeBind) 即可。将响应状态代码设置为400,Content-Type header设置为 text/plain;charset=utf-8 。请注意,如果在此之后设置响应代码,将会受到警告: GIN-debug Headers were alreadywritten. Wanted to override status code 400 with 422 将导致已经编写了警告GIN-debug标头。如果想更好地控制行为,可以考虑使用ShouldBind等效方法。
Shouldbind
  • 方法:ShouldBind , ShouldBindJSON , ShouldBindXML , ShouldBindQuery ,ShouldBindYAML
  • 行为:这些方法使用ShouldBindWith。如果存在绑定错误,则返回错误,开发人员有责任适当地处理请求和错误

注意,使用绑定方法时, Gin 会根据请求头中 Content-Type 来自动判断需要解析的类型。如果你明确绑定的类型,你可以不用自动推断,而用 BindWith 方法。 你也可以指定某字段是必需的。如果一个字段被 binding:"required" 修饰而值却是空的,请求会失败并返回错误。

5.1 Json绑定

Json的绑定,其实就是将request中的Body中的数据按照Json格式进行解析,解析后存储到结构体对象中。新建一个go文件, demo09_bind.go:

package main
import (
    "github.com/gin-gonic/gin"
    "net/http"
)
type Login struct {
    User string `form:"username" json:"user" uri:"user" xml:"user" binding:"required"`
    Password string `form:"password" json:"password" uri:"password" xml:"password" binding:"required"`
}
func main() {
    router := gin.Default()
    //1.binding JSON
    // Example for binding JSON ({"user": "ls", "password": "123456"})
    router.POST("/loginJSON", func(c *gin.Context) {
        var json Login
        //其实就是将request中的Body中的数据按照JSON格式解析到json变量量中
        if err := c.ShouldBindJSON(&json); err != nil {
            c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
            return
        }
        if json.User != "ls" || json.Password != "123456" {
            c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
            return
        }
        c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
    })
    router.Run(":8080")
}

前面我们使用c.String返回响应,顾名思义则返回string类型。 content-type是plain或者text。调用c.JSON则返回json数据。其中gin.H封装了生成json的方式,是一个强大的工具。使用golang可以像动态语言一样写字面量的json,对于嵌套json的实现,嵌套gin.H即可。

然后打开终端输⼊入以下命令:

curl -v -X POST http://127.0.0.1:8080/loginJSON -H 'contenttype:application/json' -d '{"user":"ls","password":"123456"}'

假如我们传递的json中只有user数据:

得到一个错误信息:
curl -v -X POST http://127.0.0.1:8080/loginJSON -H 'contenttype:application/json' -d '{"user":"ls"}'
5.2 Form表单

其实本质是将c中的request中的body数据解析到form中。首先我们先看一下绑定普通表单的例子:在之前的代码上继续添加:

// 3. Form 绑定普通表单的例子
// Example for binding a HTML form (user=ls&password=123456)
router.POST("/loginForm", func(c *gin.Context) {
	var form Login
	//方法一:对于FORM数据直接使用Bind函数, 默认使用使用form格式解析,if c.Bind(&form) == nil
	// 根据请求头中 content-type ⾃自动推断.
	if err := c.Bind(&form); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	if form.User != "ls" || form.Password != "123456" {
		c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
		return
	}
	c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
})
router.POST("/login", func(c *gin.Context) {
	var form Login
	//方法二: 使用BindWith函数,如果你明确知道数据的类型
	// 你可以显式声明来绑定多媒体表单:
	// c.BindWith(&form, binding.Form)
	// 或者使用自动推断:
	if c.BindWith(&form, binding.Form) == nil {
		if form.User == "user" && form.Password == "password" {
			c.JSON(200, gin.H{"status": "you are logged in ..... "})
		} else {
			c.JSON(401, gin.H{"status": "unauthorized"})
		}
	}
})
5.3 URI绑定
// URI
router.GET("/:user/:password", func(c *gin.Context) {
	var login Login
	if err := c.ShouldBindUri(&login); err != nil {
		c.JSON(400, gin.H{"msg": err})
		return
	}
	c.JSON(200, gin.H{"username": login.User, "password": login.Password})
})

打开终端输入以下内容

curl -v http://127.0.0.1:8080/ls/123456

6.Gin响应

既然请求可以使用不同的 content-type ,响应也如此。通常响应会有html, text, plain, json和xml等。

Gin提供了很优雅的渲染方法。

6.1 JON/XML/YAML渲染

创建一个go文件(demo10_rendering.go):

package main

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

func main() {
    r := gin.Default()
    // gin.H is a shortcut for map[string]interface{}
    r.GET("/someJSON", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
    })
    r.GET("/moreJSON", func(c *gin.Context) {
        // You also can use a struct
        var msg struct {
            Name    string `json:"user"`
            Message string
            Number  int
        }
        msg.Name = "ls"
        msg.Message = "hey"
        msg.Number = 123
        // 注意 msg.Name 变成了 "user" 字段
        // 以下方式都会输出 : {"user": "ls", "Message": "hey", "Number": 123}
        c.JSON(http.StatusOK, msg)
    })
    r.GET("/someXML", func(c *gin.Context) {
        c.XML(http.StatusOK, gin.H{"user": "ls", "message": "hey", "status": http.StatusOK})
    })
    r.GET("/someYAML", func(c *gin.Context) {
        c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
    })
    r.GET("/someProtoBuf", func(c *gin.Context) {
        reps := []int64{int64(1), int64(2)}
        label := "test"
        // The specific definition of protobuf is written in the testdata/protoexample file.
        data := &protoexample.Test{
            Label: &label,
            Reps:  reps,
        }
        // Note that data becomes binary data in the response
        // Will output protoexample.Test protobuf serialized data
        c.ProtoBuf(http.StatusOK, data)
    })
    // Listen and serve on 0.0.0.0:8080
    r.Run(":8080")
}

打开浏览器输入网址:http://127.0.0.1:8080/moreJSON

{"user":"ls","Message":"hey","Number":123}

user    "ls"
Message "hey"
Number  123
6.2 HTML模板渲染

Gin支持加载HTML模板, 然后根据模板参数进行配置并返回相应的数据。
先要使用 LoadHTMLGlob() 或者 LoadHTMLFiles() 方法来加载模板文件:

package main

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

func main() {
	router := gin.Default()
	//加载模板
	router.LoadHTMLGlob("templates/*")
	//router.LoadHTMLFiles("templates/template1.html", "templates/template2.html")
	//定义路路由
	router.GET("/index", func(c *gin.Context) {
		//根据完整⽂文件名渲染模板,并传递参数
		c.HTML(http.StatusOK, "index.tmpl", gin.H{
			"title": "Main website",
		})
	})
	router.Run(":8080")
}

创建一个目录: templates,然后在该目录下创建一个模板文件:
templates/index.tmpl

<html>
<h1>
{{ .title }}
</h1>
</html>

运行项目,打开浏览器输入地址:http://localhost:8080/index

不同文件夹下模板名字可以相同,此时需要 LoadHTMLGlob() 加载两层模板路径。

router.LoadHTMLGlob("templates/**/*")
router.GET("/posts/index", func(c *gin.Context) {
	c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{
		"title": "Posts",
	})
	c.HTML(http.StatusOK, "users/index.tmpl", gin.H{
		"title": "Users",
	})
})

重启项目后,打开浏览器输入以下网址: http://127.0.0.1:8080/posts/index
Gin也可以使用自定义的模板引擎,如下

import "html/template"

func main() {
	router := gin.Default()
	html := template.Must(template.ParseFiles("file1", "file2"))
	router.SetHTMLTemplate(html)
	router.Run(":8080")
}

自定义模板函数

定义一个不转义相应内容的 safe 模板函数如下

package main

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

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://lianshiclass.com'>x86cloud</a>")
	})
	router.Run(":8080")
}

在 index.tmpl 中使用定义好的 safe 模板函数:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>修改模板引擎的标识符</title>
</head>
<body>
<div>{{ . | safe }}</div>
</body>
</html>

使用模板继承

Gin框架默认都是使用单模板,如果需要使用 block template 功能,可以通过 "github.com/gincontrib/multitemplate" 库实现,具体示例如下:

首先,假设我们项目目录下的templates⽂文件夹下有以下模板文件,其中 home.tmpl 和 index.tmpl 继承了 base.tmpl

然后我们定义一个 loadTemplates 函数如下

func loadTemplates(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 = loadTemplates("./templates")
	r.GET("/index", indexFunc)
	r.GET("/home", homeFunc)
	r.Run()
}
6.3 文件响应

静态文件服务
可以向客户端展示本地的一些文件信息,例如显示某路径下地文件。服务端代码是:

package main

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

func main() {
	router := gin.Default()
	// 下⾯面测试静态⽂文件服务
	// 显示当前⽂文件夹下的所有⽂文件/或者指定⽂文件
	router.StaticFS("/showDir", http.Dir("."))
	router.StaticFS("/files", http.Dir("/bin"))
	//Static提供给定⽂文件系统根⽬目录中的⽂文件。
	//router.Static("/files", "/bin")
	router.StaticFile("/image", "./assets/image.jpg")
	router.Run(":8080")
}

打开浏览器,输入地址:
http://127.0.0.1:8080/showDir

http://127.0.0.1:8080/files

http://127.0.0.1:8080/image

6.4 重定向

http重定向
demo13_redirect.go:

package main

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

func main() {
	r := gin.Default()
	r.GET("/redirect", func(c *gin.Context) {
		//⽀支持内部和外部的重定向
		c.Redirect(http.StatusMovedPermanently, "http://www.baidu.com/")
	})
	r.Run(":8080")
}

打开浏览器输入: http://127.0.0.1:8080/redirect

路由重定向

路由重定向,使用 HandleContext

r.GET("/test", func(c *gin.Context) {
	// 指定重定向的URL
	c.Request.URL.Path = "/test2"
	r.HandleContext(c)
})
r.GET("/test2", func(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{"hello": "world"})
})
6.5 同步异步

goroutine 机制可以方便地实现异步处理。当在中间件或处理程序中启动新的Goroutines时,你不应该在原始上下文使用它,你必须使用只读的副本。

新建一个go文件:

package main

import (
	"github.com/gin-gonic/gin"
	"log"
	"time"
)

func main() {
	r := gin.Default()
	//1. 异步
	r.GET("/long_async", func(c *gin.Context) {
		// goroutine 中只能使用只读的上下文 c.Copy()
		cCp := c.Copy()
		go func() {
			time.Sleep(5 * time.Second)
			// 注意使用只读上下文
			log.Println("Done! in path " + cCp.Request.URL.Path)
		}()
	})
	//2. 同步
	r.GET("/long_sync", func(c *gin.Context) {
		time.Sleep(5 * time.Second)
		// 注意可以使用原始上下文
		log.Println("Done! in path " + c.Request.URL.Path)
	})
	// Listen and serve on 0.0.0.0:8080
	r.Run(":8080")
}

启动程序,打开浏览器并输入网址:
http://127.0.0.1:8080/long_sync
http://127.0.0.1:8080/long_async

推荐阅读