1. Gin简单入门

Gin 框架 现在是 github 上 start 最多 Go 语言编写的 Web 框架,相比其他它的几个 start 数量差不多的框架,它更加的轻量,有更好的性能。它使用了httprouter,如果你是性能和高效的追求者, 你会爱上Gin!

1.1. 简单示例

1.1.1. 第一个gin程序

  1. import (
  2. "github.com/gin-gonic/gin"
  3. log "github.com/sirupsen/logrus"
  4. )
  5. func main() {
  6. r := gin.Default() // 创建默认路由引擎
  7. // 配置 location 为 / 时,且请求为GET时的路由处理函数
  8. r.GET("/", func(c *gin.Context) {
  9. // c.JSON() 构造json格式的响应体,在后端应用中非常常见
  10. // 请求和响应的响应信息都封装在了对象 c 中
  11. c.JSON(200, gin.H{
  12. "method" : c.Request.Method,
  13. "location": c.Request.URL.String(),
  14. "message": "Hello world!",
  15. })
  16. })
  17. err := r.Run("0.0.0.0:8080") // 绑定 8080 端口
  18. if err != nil {
  19. log.Fatal("Init gin server failed,err:%s", err.Error())
  20. }
  21. }
  1. [root@duduniao ~]# curl 127.0.0.1:8080
  2. {"location":"/","message":"Hello world!","method":"GET"}

1.1.2. RestFul API

在后端开发中,RestFul API 非常常见,即location就是目标资源,而不同的请求方法代表对这个请求资源的操作方式,如:

请求方法 URL 含义
GET /book/ISBN6379 查询书籍信息
HEADA /book/ISBN6379 查询书籍是否存在
POST /book/ISBN6379 创建书籍记录
PUT /book/ISBN6379 更新书籍信息
DELETE /book/ISBN6379 删除书籍信息
  1. import (
  2. "github.com/gin-gonic/gin"
  3. log "github.com/sirupsen/logrus"
  4. )
  5. func main() {
  6. r := gin.Default()
  7. // GET
  8. r.GET("/book/ISBN6379", func(c *gin.Context) {
  9. c.JSON(200, gin.H{
  10. "method": c.Request.Method,
  11. })
  12. })
  13. // POST
  14. r.POST("/book/ISBN6379", func(c *gin.Context) {
  15. c.JSON(200, gin.H{
  16. "method": c.Request.Method,
  17. })
  18. })
  19. // PUT
  20. r.PUT("/book/ISBN6379", func(c *gin.Context) {
  21. c.JSON(200, gin.H{
  22. "method": c.Request.Method,
  23. })
  24. })
  25. // DELETE
  26. r.DELETE("/book/ISBN6379", func(c *gin.Context) {
  27. c.JSON(200, gin.H{
  28. "method": c.Request.Method,
  29. })
  30. })
  31. if err := r.Run("0.0.0.0:8080"); err != nil {
  32. log.Fatal("Init web failed, err:%s\n", err.Error())
  33. }
  34. }
  1. [root@duduniao ~]# for i in GET POST PUT DELETE;do curl -X $i http://127.0.0.1:8080/book/ISBN6379;echo ;done
  2. {"method":"GET"}
  3. {"method":"POST"}
  4. {"method":"PUT"}
  5. {"method":"DELETE"}

1.2. Gin渲染

所谓渲染即生成响应体,在后端开发中,常用的响应体有 json,html,protobuf,静态文件等。

1.2.1. Json 渲染

  1. func main() {
  2. r := gin.Default()
  3. // 使用 gin.H{} 构造json
  4. r.GET("/", func(c *gin.Context) {
  5. c.JSON(200, gin.H{
  6. "method": c.Request.Method,
  7. "location": c.Request.URL.String(),
  8. "message": "root page",
  9. })
  10. })
  11. // 使用结构体
  12. r.GET("/index", func(c *gin.Context) {
  13. res := struct {
  14. Method string `json:"method"`
  15. Location string `json:"location"`
  16. Message string `json:"message"`
  17. }{c.Request.Method, c.Request.URL.Path, "index page"}
  18. c.JSON(200, &res)
  19. })
  20. err := r.Run("0.0.0.0:8080")
  21. if err != nil {
  22. log.Fatal("Init gin server failed,err:%s", err.Error())
  23. }
  24. }
  1. [root@duduniao ~]# curl http://127.0.0.1:8080
  2. {"location":"/","message":"root page","method":"GET"}
  3. [root@duduniao ~]# curl http://127.0.0.1:8080/index
  4. {"method":"GET","location":"/index","message":"index page"}

1.2.2. YAML渲染

  1. func main() {
  2. r := gin.Default()
  3. // 使用 gin.H{} 构造json
  4. r.GET("/", func(c *gin.Context) {
  5. c.YAML(200, gin.H{
  6. "method": c.Request.Method,
  7. "location": c.Request.URL.String(),
  8. "message": "root page",
  9. })
  10. })
  11. // 使用结构体
  12. r.GET("/index", func(c *gin.Context) {
  13. res := struct {
  14. Method string
  15. Location string
  16. Message string
  17. }{c.Request.Method, c.Request.URL.Path, "index page"}
  18. c.YAML(200, &res)
  19. })
  20. err := r.Run("0.0.0.0:8080")
  21. if err != nil {
  22. log.Fatal("Init gin server failed,err:%s", err.Error())
  23. }
  24. }
  1. [root@duduniao ~]# curl http://127.0.0.1:8080/index
  2. method: GET
  3. location: /index
  4. message: index page
  5. [root@duduniao ~]# curl http://127.0.0.1:8080
  6. location: /
  7. message: root page
  8. method: GET

1.2.3. XML渲染

与json和yaml返回不同,XML不支持返回匿名结构体对象

  1. func main() {
  2. r := gin.Default()
  3. // 使用 gin.H{} 构造json
  4. r.GET("/", func(c *gin.Context) {
  5. c.XML(200, gin.H{
  6. "method": c.Request.Method,
  7. "location": c.Request.URL.String(),
  8. "message": "root page",
  9. })
  10. })
  11. // 使用结构体
  12. r.GET("/index", func(c *gin.Context) {
  13. // 使用匿名结构体会无法显示
  14. type msg struct {
  15. Method string
  16. Location string
  17. Message string
  18. }
  19. var res = msg{c.Request.Method, c.Request.URL.Path, "index page"}
  20. c.XML(200, &res)
  21. })
  22. err := r.Run("0.0.0.0:8080")
  23. if err != nil {
  24. log.Fatal("Init gin server failed,err:%s", err.Error())
  25. }
  26. }
  1. [root@duduniao ~]# curl http://127.0.0.1:8080/index
  2. <msg><Method>GET</Method><Location>/index</Location><Message>index page</Message></msg>
  3. [root@duduniao ~]# curl http://127.0.0.1:8080
  4. <map><method>GET</method><location>/</location><message>root page</message></map>

1.2.4. HTML渲染

在做web开发时,返回渲染的 html 页面常见很常见,一般流程: 在 templates 目录下创建html模板文件 —> 导入html模板文件 —> 执行 c.HTML()渲染

  1. [root@duduniao gin_basic]# tree
  2. .
  3. ├── html.go
  4. └── templates
  5. ├── book
  6. └── index.html
  7. └── user
  8. └── index.html
  1. {{define "user/index.html"}}
  2. <!DOCTYPE html>
  3. <html lang="en">
  4. <body>
  5. <h1>{{.name}}</h1>
  6. <h1>{{.age}}</h1>
  7. </body>
  8. </html>
  9. {{end}}
  1. {{define "book/index.html"}}
  2. <!DOCTYPE html>
  3. <html lang="en">
  4. <body>
  5. <h2>{{.title}}</h2>
  6. </body>
  7. </html>
  8. {{end}}
  1. package main
  2. import (
  3. "github.com/gin-gonic/gin"
  4. log "github.com/sirupsen/logrus"
  5. )
  6. func main() {
  7. r := gin.Default()
  8. r.LoadHTMLGlob("templates/**/*.html")
  9. r.GET("/book/", func(c *gin.Context) {
  10. c.HTML(200, "book/index.html", gin.H{"title": "books"})
  11. })
  12. r.GET("/user/", func(c *gin.Context) {
  13. c.HTML(200, "user/index.html", gin.H{"name":"张三", "age":18})
  14. })
  15. err := r.Run("0.0.0.0:8080")
  16. if err != nil {
  17. log.Fatal("Init gin server failed,err:%s", err.Error())
  18. }
  19. }

1.3. 请求处理

1.3.1. PATH参数处理

所谓PATH参数解析,就是指解析URL的locaction,如从请求 GET /student/201909011208 获取学生的ID 201909011208。PATH中关键词解析是通过占位符来实现的。

  1. func main() {
  2. r := gin.Default()
  3. r.GET("/user/search/:name/:age", func(c *gin.Context) {
  4. name, age := c.Param("name"), c.Param("age")
  5. c.JSON(200, gin.H{
  6. "name": name,
  7. "age": age,
  8. })
  9. })
  10. if err := r.Run("0.0.0.0:80"); err != nil {
  11. log.Fatal("Init web failed,err:%s", err.Error())
  12. }
  13. }
  1. [root@duduniao ~]# curl http://127.0.0.1/user/search/zhangsan/20
  2. {"age":"20","name":"zhangsan"}
  3. [root@duduniao ~]# curl http://127.0.0.1/user/search/张三/20
  4. {"age":"20","name":"张三"}

1.3.2. GET请求参数

在非 RESTAPI 请求中,请求基本都是通过GET加URL参数完成的,比如 GET /user/search?name=zhangsan&age=20 ,因此对请求URL中的参数解析场景很常见,Gin中采用 c.Query(key)c.DefaultQuery(key,default) 获取,如果对于的key不存在,前者返回空字符串,后者范围指定的default。

  1. func main() {
  2. r := gin.Default()
  3. r.GET("/user/search", func(c *gin.Context) {
  4. name := c.Query("name")
  5. age := c.Query("age")
  6. class := c.DefaultQuery("class", "101") // 如果不存在则使用默认值
  7. c.JSON(200, gin.H{
  8. "name": name,
  9. "age": age,
  10. "class": class,
  11. })
  12. })
  13. if err := r.Run("0.0.0.0:80"); err != nil {
  14. log.Fatal("Init web failed,err:%s", err.Error())
  15. }
  16. }
  1. [root@duduniao ~]# curl "http://127.0.0.1/user/search?name=zhangsan&age=20"
  2. {"age":"20","class":"101","name":"zhangsan"}
  3. [root@duduniao ~]# curl "http://127.0.0.1/user/search?name=zhangsan"
  4. {"age":"","class":"101","name":"zhangsan"}

1.3.3. 获取表单参数

在网站中开发中,用户提交注册或者登陆信息一般通过POST表单进行传递,Gin中获取该参数的值是通过 c.PostForm(key) 实现。

  1. func main() {
  2. r := gin.Default()
  3. r.POST("/user/search", func(c *gin.Context) {
  4. name := c.PostForm("name")
  5. age := c.PostForm("age")
  6. class := c.PostForm("class")
  7. c.JSON(200, gin.H{
  8. "name": name,
  9. "age": age,
  10. "class": class,
  11. })
  12. })
  13. if err := r.Run("0.0.0.0:80"); err != nil {
  14. log.Fatal("Init web failed,err:%s", err.Error())
  15. }
  16. }
  1. [root@duduniao ~]# curl -X POST "http://127.0.0.1/user/search" -d "name=zhangsan&age=19"
  2. {"age":"19","class":"","name":"zhangsan"}
  3. [root@duduniao ~]# curl -X POST "http://127.0.0.1/user/search" -d "name=zhangsan&age=19&class=301"
  4. {"age":"19","class":"301","name":"zhangsan"}

1.3.4. 获取json参数

获取GET请求参数和表单参数有快捷方式,如上面两个案例所示,但是json类的请求体需要使用 c.BindJson(&obj) 来取参数。

  1. func main() {
  2. r := gin.Default()
  3. r.POST("/user/search", func(c *gin.Context) {
  4. var user UserInfo
  5. err := c.BindJSON(&user) // 绑定json解析,query,yaml,xml同理
  6. if err != nil {
  7. log.Errorf("Get args failed, err:%s\n", err.Error())
  8. c.JSON(200, gin.H{"err": err.Error()})
  9. return
  10. }
  11. c.JSON(200, gin.H{
  12. "name": user.Name,
  13. "age": user.Age,
  14. "class": user.Class,
  15. })
  16. })
  17. if err := r.Run("0.0.0.0:80"); err != nil {
  18. log.Fatal("Init web failed,err:", err.Error())
  19. }
  20. }
  1. [root@duduniao ~]# curl -X POST -H "Content-Type:application/json" -d '{"name":"zhangsan","age":20}' http://127.0.0.1/user/search
  2. {"age":20,"class":"","name":"zhangsan"}
  3. [root@duduniao ~]# curl -X POST -H "Content-Type:application/json" -d '{"name":"zhangsan","age":20,"class":"312"}' http://127.0.0.1/user/search
  4. {"age":20,"class":"312","name":"zhangsan"}

1.3.5. 参数自动绑定

上面的案例中,都是明确知道是URL传参,或者POST表单,或者Json请求体。但是Gin中还提供了一种自动判断参数类型,并解析到结构体对象中的方法。可以自动根据请求类型、Content-Type 来判断请求数据的类型,解析后绑定到指定的结构体中。需要注意的是,结构体中必须打上 form 的tag,否则GET请求中的参数无法解析成功

  1. type UserInfo struct {
  2. Name string `form:"name" json:"name"`
  3. Age uint8 `form:"age" json:"age"`
  4. Class string `form:"class" json:"class"`
  5. }
  6. func search(c *gin.Context) {
  7. var user UserInfo
  8. err := c.ShouldBind(&user) // 根据 Content-Type 自动进行解析
  9. if err != nil {
  10. log.Errorf("Get args failed, err:%s\n", err.Error())
  11. c.JSON(200, gin.H{"err": err.Error()})
  12. return
  13. }
  14. c.JSON(200, gin.H{
  15. "method": c.Request.Method,
  16. "name": user.Name,
  17. "age": user.Age,
  18. "class": user.Class,
  19. })
  20. }
  21. func main() {
  22. r := gin.Default()
  23. r.POST("/user/search", search)
  24. r.GET("/user/search", search)
  25. if err := r.Run("0.0.0.0:80"); err != nil {
  26. log.Fatal("Init web failed,err:", err.Error())
  27. }
  28. }
  1. [root@duduniao ~]# curl "http://127.0.0.1/user/search?name=zhangsan&age=20&class=301"
  2. {"age":20,"class":"301","method":"GET","name":"zhangsan"}
  3. [root@duduniao ~]# curl -X POST "http://127.0.0.1/user/search" -d "name=zhangsan&age=19&class=301"
  4. {"age":19,"class":"301","method":"POST","name":"zhangsan"}
  5. [root@duduniao ~]# curl -X POST -H "Content-Type:application/json" -d '{"name":"zhangsan","age":20,"class":"312"}' http://127.0.0.1/user/search
  6. {"age":20,"class":"312","method":"POST","name":"zhangsan"}

1.4. 上传的文件处理

1.4.1. 上传单个文件

  1. func main() {
  2. r := gin.Default()
  3. r.POST("/upload/", func(c *gin.Context) {
  4. file, err := c.FormFile("background") // 上传单个文件,明确知晓文件的 key
  5. if err != nil {
  6. c.JSON(400, gin.H{"status": 411, "msg": "recv file failed,err" + err.Error()})
  7. log.Errorf("recv file failed,err:%s\n", err.Error())
  8. return
  9. }
  10. if err := c.SaveUploadedFile(file, file.Filename); err != nil {
  11. c.JSON(400, gin.H{"status": 412, "msg": "save file failed, err" + err.Error()})
  12. log.Errorf("save %s failed,err:%s\n", file.Filename, err.Error())
  13. return
  14. }
  15. log.Infof("recv %s success\n", file.Filename)
  16. c.JSON(200, gin.H{"status": 200, "msg": "revc success"})
  17. })
  18. if err := r.Run("0.0.0.0:80"); err != nil {
  19. log.Fatal("Init web failed,err:", err.Error())
  20. }
  21. }
  1. [root@duduniao 壁纸]# curl -X POST -F 'background=@2020-03-14_22-40-07.jpg' http://127.0.0.1/upload/
  2. {"msg":"revc success","status":200}

1.4.2. 上传多个文件

  1. func main() {
  2. r := gin.Default()
  3. // 设置能接收的缓冲区大小
  4. r.MaxMultipartMemory = 50 * 1024 * 1024
  5. r.POST("/upload/", func(c *gin.Context) {
  6. form, err := c.MultipartForm() // 获取表单信息
  7. if err != nil {
  8. c.JSON(400, gin.H{"status":411,"msg": "recv file failed, err:" + err.Error()})
  9. log.Errorf("get files failed,err:%s\n",err.Error())
  10. return
  11. }
  12. files, ok := form.File["files"] // 获取表单中的文件列表
  13. if !ok {
  14. c.JSON(400, gin.H{"status":412,"msg":"recv file failed, err: no key names files"})
  15. log.Errorf("get files failed,err:%s\n","no key names files")
  16. return
  17. }
  18. // 变量文件对象并写入本地
  19. for index, file := range files {
  20. if err := c.SaveUploadedFile(file, fmt.Sprintf("%d-%s", index, file.Filename)); err != nil {
  21. c.JSON(400, gin.H{"status":412,"msg":"save file failed, err:" + err.Error()})
  22. log.Errorf("save %s failed,err:%s\n",file.Filename, err.Error())
  23. return
  24. }
  25. log.Infof("save %s success\n",file.Filename)
  26. }
  27. log.Infof("recv %s success\n", "file.Filename")
  28. c.JSON(200, gin.H{"status":200,"msg":"revc success"})
  29. })
  30. if err := r.Run("0.0.0.0:80"); err != nil {
  31. log.Fatal("Init web failed,err:", err.Error())
  32. }
  33. }
  1. [root@duduniao 壁纸]# curl -X POST -F 'files=@2020-03-10_10-06-06.jpg' -F 'files=@2020-03-10_10-09-32.jpg' -F 'files=@2020-03-14_22-41-02.jpg' http://127.0.0.1/upload/
  2. {"msg":"revc success","status":200}

1.5. 重定向

在HTTP协议中,重定向分为两种: 301 永久重定向,302 临时重定向。在Gin中,重定向有两种实现的方式,一种是是通过HTTP重定向,指定返回码(301,302)和目标地址即可;另一种是通过修改路由实现,内部进行URL跳转,类似于nginx的rewrite规则。

1.5.1. HTTP重定向

  1. func main() {
  2. r := gin.Default()
  3. r.GET("/book/", func(c *gin.Context) {
  4. // c.Redirect(301, "/books/") // 永久重定向
  5. c.Redirect(302, "/books/") // 临时重定向
  6. })
  7. r.GET("/books/", func(c *gin.Context) {
  8. c.JSON(200, gin.H{
  9. "method" : c.Request.Method,
  10. "location": c.Request.URL.String(),
  11. "message": "Hello world!",
  12. })
  13. })
  14. err := r.Run("0.0.0.0:8080")
  15. if err != nil {
  16. log.Fatal("Init gin server failed,err:%s", err.Error())
  17. }
  18. }
  1. [root@duduniao ~]# curl -Lv http://127.0.0.1:8080/book/
  2. ......
  3. > GET /book/ HTTP/1.1
  4. > Host: 127.0.0.1:8080
  5. > User-Agent: curl/7.58.0
  6. > Accept: */*
  7. >
  8. < HTTP/1.1 302 Found
  9. < Content-Type: text/html; charset=utf-8
  10. < Location: /books/
  11. < Date: Sat, 13 Jun 2020 13:52:54 GMT
  12. < Content-Length: 30
  13. <
  14. ......
  15. > GET /books/ HTTP/1.1
  16. > Host: 127.0.0.1:8080
  17. > User-Agent: curl/7.58.0
  18. > Accept: */*
  19. >
  20. < HTTP/1.1 200 OK
  21. < Content-Type: application/json; charset=utf-8
  22. < Date: Sat, 13 Jun 2020 13:52:54 GMT
  23. < Content-Length: 62
  24. <
  25. * Connection #0 to host 127.0.0.1 left intact
  26. {"location":"/books/","message":"Hello world!","method":"GET"}

1.5.2. 路由重定向

  1. func main() {
  2. r := gin.Default()
  3. r.GET("/book/", func(c *gin.Context) {
  4. c.Request.URL.Path = "/books/"
  5. r.HandleContext(c)
  6. })
  7. r.GET("/books/", func(c *gin.Context) {
  8. c.JSON(200, gin.H{
  9. "method" : c.Request.Method,
  10. "location": c.Request.URL.String(),
  11. "message": "Hello world!",
  12. })
  13. })
  14. err := r.Run("0.0.0.0:8080")
  15. if err != nil {
  16. log.Fatal("Init gin server failed,err:%s", err.Error())
  17. }
  18. }
  1. [root@duduniao ~]# curl -Lv http://127.0.0.1:8080/book/
  2. * Trying 127.0.0.1...
  3. * TCP_NODELAY set
  4. * Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0)
  5. > GET /book/ HTTP/1.1
  6. > Host: 127.0.0.1:8080
  7. > User-Agent: curl/7.58.0
  8. > Accept: */*
  9. >
  10. < HTTP/1.1 200 OK
  11. < Content-Type: application/json; charset=utf-8
  12. < Date: Sat, 13 Jun 2020 13:57:18 GMT
  13. < Content-Length: 62
  14. <
  15. * Connection #0 to host 127.0.0.1 left intact
  16. {"location":"/books/","message":"Hello world!","method":"GET"}

1.6. Gin路由

路由就是请求(请求方法+URL)和处理函数之间的关系绑定,根据业务场景,可以分为普通路由和路由组,普通路由就是一个一个零散的路由,路由组是若干个有关联关系的路由组成,比如对同一个URL的不同方法,同一个API版本的所有路由集合等,路由组习惯性一对{}包裹同组的路由,这只是为了看着清晰,你用不用{}包裹功能上没什么区别。

1.6.1. 普通路由

  1. r.GET("/index", func(c *gin.Context) {...})
  2. r.PUT("/index", func(c *gin.Context) {...})
  3. r.POST("/index", func(c *gin.Context) {...})
  4. r.Any("/index", func(c *gin.Context) {...}) // 匹配任意方法
  5. r.NoRoute(func(c *gin.Context) {...}) // 没有任何匹配项

1.6.2. 路由组

  1. userGroup := r.Group("/user")
  2. {
  3. userGroup.GET("/index", func(c *gin.Context) {...})
  4. userGroup.GET("/login", func(c *gin.Context) {...})
  5. userGroup.POST("/login", func(c *gin.Context) {...})
  6. }
  7. shopGroup := r.Group("/shop")
  8. {
  9. shopGroup.GET("/index", func(c *gin.Context) {...})
  10. shopGroup.GET("/cart", func(c *gin.Context) {...})
  11. shopGroup.POST("/checkout", func(c *gin.Context) {...})
  12. }

1.7. 中间件

Gin框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook)函数。这个钩子函数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等。

1.7.1. 定义中间件

Gin中的中间件必须是一个gin.HandlerFunc类型的函数,因此定义中间件就是定义返回值为 gin.HandlerFunc 的函数。中间件类似于一个装饰器,其中 c.Next() 表示执行被装饰的函数,即路由处理函数。

  1. func timer() gin.HandlerFunc {
  2. return func(c *gin.Context) {
  3. start := time.Now()
  4. c.Next() // 执行
  5. log.Infof("The request [%s %s] const %v\n", c.Request.Method, c.Request.URL.Path, time.Since(start))
  6. }
  7. }

1.7.2. 注册中间件

中间件的注册分为三种类型:

  • 注册全局中间件,即所有的请求都会调用该中间件进行装饰: r.Use(timer())
  • 为某个特定路由注册,支持多个中间件: r.GET("/", timer(), func(c *gin.context) {} )
  • 为路由组注册中间件,可以在定义路由组的时候注册: userGroup := r.Group("/user", timer2())

也可以对定义好的路由组添加中间件: deployGroup.Use(timer2())
全局中间件和路由中间件,执行顺序,参考一下案例:

  1. import (
  2. "fmt"
  3. "github.com/gin-gonic/gin"
  4. log "github.com/sirupsen/logrus"
  5. "time"
  6. )
  7. func response(c *gin.Context) {
  8. time.Sleep(time.Millisecond * 100)
  9. c.JSON(200, gin.H{"message": "ok", "location": c.Request.URL.String()})
  10. }
  11. func timer1() gin.HandlerFunc {
  12. return func(c *gin.Context) {
  13. fmt.Println("timer1 start")
  14. start := time.Now()
  15. c.Next()
  16. log.Infof("[timer1] The request [%s %s] const %v\n", c.Request.Method, c.Request.URL.Path, time.Since(start))
  17. fmt.Println("timer1 stop")
  18. }
  19. }
  20. func timer2() gin.HandlerFunc {
  21. return func(c *gin.Context) {
  22. fmt.Println("timer2 start")
  23. start := time.Now()
  24. c.Next()
  25. log.Infof("[timer2] The request [%s %s] const %v\n", c.Request.Method, c.Request.URL.Path, time.Since(start))
  26. fmt.Println("timer2 stop")
  27. }
  28. }
  29. func main() {
  30. r := gin.Default()
  31. r.Use(timer1()) // 注册全局中间件
  32. r.GET("/index", timer2(), response) // 为特定路由注册中间件
  33. userGroup := r.Group("/user", timer2()) // 为路由组添加中间件
  34. {
  35. userGroup.GET("/index", response)
  36. userGroup.GET("/edit", response)
  37. }
  38. deployGroup := r.Group("/deploy")
  39. deployGroup.Use(timer2()) // 为路由组添加中间件
  40. {
  41. deployGroup.GET("/deployment", response)
  42. deployGroup.GET("/service", response)
  43. deployGroup.GET("/template", response)
  44. }
  45. if err := r.Run("0.0.0.0:80"); err != nil {
  46. log.Fatalf("Init gin failed, err:%s\n", err.Error())
  47. }
  48. }
  1. [root@duduniao ~]# curl http://127.0.0.1/index
  2. {"location":"/index","message":"ok"}
  3. ------
  4. timer1 start
  5. timer2 start
  6. INFO[0009] [timer2] The request [GET /index] const 100.7118ms
  7. timer2 stop
  8. INFO[0009] [timer1] The request [GET /index] const 100.8172ms
  9. timer1 stop
  10. [root@duduniao ~]# curl http://127.0.0.1/user/edit
  11. {"location":"/user/edit","message":"ok"}
  12. ------
  13. timer1 start
  14. timer2 start
  15. INFO[0018] [timer2] The request [GET /user/edit] const 100.5929ms
  16. timer2 stop
  17. INFO[0018] [timer1] The request [GET /user/edit] const 100.6653ms
  18. timer1 stop
  19. [root@duduniao ~]# curl http://127.0.0.1/deploy/service
  20. {"location":"/deploy/service","message":"ok"}
  21. ------
  22. timer1 start
  23. timer2 start
  24. INFO[0026] [timer2] The request [GET /deploy/service] const 100.3718ms
  25. timer2 stop
  26. INFO[0026] [timer1] The request [GET /deploy/service] const 100.4596ms
  27. timer1 stop

1.7.3. 中间件注意事项

  • gin默认中间件

gin.Default()默认使用了Logger和Recovery中间件,其中:

  • Logger中间件将日志写入gin.DefaultWriter,即使配置了GIN_MODE=release。
  • Recovery中间件会recover任何panic。如果有panic的话,会写入500响应码。

如果不想使用上面两个默认的中间件,可以使用gin.New()新建一个没有任何默认中间件的路由。

  • gin中间件中使用goroutine

当在中间件或handler中启动新的goroutine时,不能使用原始的上下文(c *gin.Context),必须使用其只读副本(c.Copy())