Golang Gin框架入门

Gin 是 Go语言写的一个 web 框架,它具有运行速度快,分组的路由器,良好的崩溃捕获和错误处理,非常好的支持中间件和 json。

框架介绍

  • Gin 是一个用 Go (Golang) 编写的 HTTP web 框架。 它是一个类似于 martini 但拥有更好性能的 API 框架,由于 httprouter,速度提高了近 40 倍。如果你需要极好的性能,使用 Gin 吧。
  • Golang Gin 文档:文档 | Gin Web Framework (gin-gonic.com)

环境配置

下载Gin包

首先下载安装 gin 包:

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

需要注意的是Gin框架需要Golang 1.13以上版本

有的时候会碰到因为GitHub被墙导致无法获取的情况,

这时候需要改一下Go module 的模块代理,改成国内代理的网站,可以点击下面网址来更改模块代理

七牛云 - Goproxy.cn

测试搭建

创建main.go,输入如下代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package main
import (
	"github.com/gin-gonic/gin"
	"net/http"
)
 
func main() {
	r := gin.Default()
	r.GET("/", func(c *gin.Context) {
		c.String(http.StatusOK, "搭建完成")
	})
	r.Run(":8888")  // 端口号8888
}

运行main.go

1
go run main.go

Gin

然后访问http://127.0.0.1:8888/

gin1

显示在“搭建完成”,则说明Gin环境完成了搭建

路由 (Route)

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

当然也可以使用 context.Any() 这个方法来匹配所有请求.

无参数路由

1
2
3
	r.GET("/", func(c *gin.Context) {
		c.String(http.StatusOK, "Hello,World!")
	})

可以使用 curl + 请求地址来快捷访问服务器

1
curl http://localhost.8888/

返回结果:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
StatusCode        : 200
StatusDescription : OK
Content           : Hello,World!
RawContent        : HTTP/1.1 200 OK
                    Content-Length: 12
                    Content-Type: text/plain; charset=utf-8
                    Date: Sun, 20 Nov 2022 10:04:09 GMT

                    Hello,World!
Forms             : {}
Headers           : {[Content-Length, 12], [Content-Type, text/plain; charset=utf-8], [Date, Sun, 20 Nov 2022 10:04:09
                    GMT]}
Images            : {}
InputFields       : {}
Links             : {}
ParsedHtml        : mshtml.HTMLDocumentClass
RawContentLength  : 12

完整代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package main  
  
import (  
   "fmt"  
   "github.com/gin-gonic/gin"   "net/http")  
  
func main() {  
   r := gin.Default()  
  
   r.GET("/", func(c *gin.Context) {  
      c.String(http.StatusOK, "Hello,World!")  
   })  
  
   r.Run(":8888") // 端口号8888  
}

解析路径参数

有的时候我们需要动态的路由,例如 /user/:name , 通过嗲用不同的URL来串流不同的那么/ 这时候就要获取URL传入的参数和设置接受了

动态路由

1
2
3
4
r.GET("/user/:name", func(c *gin.Context) { //设置URL格式  
   Name := c.Param("name")                    //获取传入的Name  
   c.String(http.StatusOK, "Hello,%s!", Name) //输出  
})

请求服务器:

1
curl http://localhost:8888/user/TEST  

输出结果:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
StatusCode        : 200
StatusDescription : OK
Content           : Hello,TEST!
RawContent        : HTTP/1.1 200 OK
                    Content-Length: 11
                    Content-Type: text/plain; charset=utf-8
                    Date: Sun, 20 Nov 2022 10:12:20 GMT

                    Hello,TEST!
Forms             : {}
Headers           : {[Content-Length, 11], [Content-Type, text/plain; charset=utf-8], [Date, Sun, 20 Nov 2022 10:12:20
                    GMT]}
Images            : {}
InputFields       : {}
Links             : {}
ParsedHtml        : mshtml.HTMLDocumentClass
RawContentLength  : 11

完整代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package main  
  
import (  
   "fmt"  
   "github.com/gin-gonic/gin"   "net/http")  
  
func main() {  
   r := gin.Default()  
     
   r.GET("/user/:name", func(c *gin.Context) { //设置URL格式    
Name := c.Param("name")                    //获取传入的Name    
c.String(http.StatusOK, "Hello,%s!", Name) //输出    
})  
  
   r.Run(":8888") // 端口号8888  
}

获取Query参数

1
2
3
4
5
r.GET("/user", func(c *gin.Context) { //设置URL格式  
   Name := c.Query("name")                               //获取传入的Name  
   role := c.DefaultQuery("role", "Teacher")             //设置参数默认值 ,不存在参数返回默认值,存在参数就设定为参数 
   c.String(http.StatusOK, "Hello,%s - %s!", Name, role) //输出  
})

请求服务器:

1
curl http://localhost:8888/user?name=Tom"&"role=Worker 

需要注意的是, curl中必须给 & 加双引号,否则会被解析成运算符

输出结果:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
StatusCode        : 200
StatusDescription : OK
Content           : Hello,Tom - Worker!
RawContent        : HTTP/1.1 200 OK
                    Content-Length: 19
                    Content-Type: text/plain; charset=utf-8
                    Date: Sun, 20 Nov 2022 10:24:07 GMT

                    Hello,Tom - Worker!
Forms             : {}
Headers           : {[Content-Length, 19], [Content-Type, text/plain; charset=utf-8], [Date, Sun, 20 Nov 2022 10:24:07
                    GMT]}
Images            : {}
InputFields       : {}
ParsedHtml        : mshtml.HTMLDocumentClass
RawContentLength  : 19

不带 role 参数请求:

1
curl http://localhost:8888/user?name=Tom

输出结果:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
StatusCode        : 200
StatusDescription : OK
Content           : Hello,Tom - Teacher!
RawContent        : HTTP/1.1 200 OK
                    Content-Length: 20
                    Content-Type: text/plain; charset=utf-8
                    Date: Sun, 20 Nov 2022 10:24:19 GMT

                    Hello,Tom - Teacher!
Forms             : {}
Headers           : {[Content-Length, 20], [Content-Type, text/plain; charset=utf-8], [Date, Sun, 20 Nov 2022 10:24:19
                    GMT]}
Images            : {}
InputFields       : {}
Links             : {}
ParsedHtml        : mshtml.HTMLDocumentClass
RawContentLength  : 20

这里看到设置的默认值起作用了.

完整代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package main  
  
import (  
   "fmt"  
   "github.com/gin-gonic/gin"   "net/http")  
  
func main() {  
   r := gin.Default()  
  
   r.GET("/user", func(c *gin.Context) { //设置URL格式    
Name := c.Query("name")                               //获取传入的Name    
role := c.DefaultQuery("role", "Teacher")             //设置参数默认值 ,不存在参数返回默认值,存在参数就设定为参数   
c.String(http.StatusOK, "Hello,%s - %s!", Name, role) //输出    
})  
  
   r.Run(":8888") // 端口号8888  
}

获取POST参数

1
2
3
4
5
6
7
r.POST("/form", func(c *gin.Context) {  
   types := c.DefaultPostForm("type", "post")  
   username := c.PostForm("username") //获取POST参数中username 的值  
   password := c.PostForm("password") //设置默认值  
  
   c.String(http.StatusOK, fmt.Sprintf("username:%s,password:%s,type:%s", username, password, types))  
})

请求服务器:

1
CURL -X POST -d "username=TEST&password=123123" "http://localhost:8888/form"

值得注意的是 这条指令在PowerShell里面会报错 报错为:

1
2
3
4
5
6
Invoke-WebRequest : 找不到与参数名称“X”匹配的参数。
所在位置 行:1 字符: 6
+ CURL -X POST -d "username=TEST&password=123123" "http://localhost:888 ...
+      ~~
    + CategoryInfo          : InvalidArgument: (:) [Invoke-WebRequest],ParameterBindingException
    + FullyQualifiedErrorId : NamedParameterNotFound,Microsoft.PowerShell.Commands.InvokeWebRequestCommand

需要将请求改为PowerShell认识的格式:

1
curl -Uri 'http://localhost:8888/form' -Body 'username=TEST&password=123123' -Method 'POST'

或者可以试试转到CMD进行 CURL操作,或者下一个PostMan .

输出结果:

1
username:TEST,password:123123,type:post

PowerShell输出结果:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
StatusCode        : 200
StatusDescription : OK
Content           : username:TEST,password:123123,type:post
RawContent        : HTTP/1.1 200 OK
                    Content-Length: 39
                    Content-Type: text/plain; charset=utf-8
                    Date: Sun, 20 Nov 2022 12:39:09 GMT

                    username:TEST,password:123123,type:post
Forms             : {}
Headers           : {[Content-Length, 39], [Content-Type, text/plain; charset=utf-8], [Date, Sun, 20 Nov 2022 12:39:09
                    GMT]}
Images            : {}
InputFields       : {}
Links             : {}
ParsedHtml        : mshtml.HTMLDocumentClass
RawContentLength  : 39

完整代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package main  
  
import (  
   "fmt"  
   "github.com/gin-gonic/gin"   "net/http")  
  
func main() {  
   r := gin.Default()  
  
   r.POST("/form", func(c *gin.Context) {  
      types := c.DefaultPostForm("type", "post")  
      username := c.PostForm("username") //获取POST参数中username 的值  
      password := c.PostForm("password") //设置默认值  
  
      c.String(http.StatusOK, fmt.Sprintf("username:%s,password:%s,type:%s", username, password, types))  
   })  
  
   r.Run(":8888") // 端口号8888  
}

Query 和 POST 混合参数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
//Query 和 POST 混合  
r.POST("/post", func(c *gin.Context) {  
   id := c.Query("id")        //获取Query传入的ID  
   name := c.PostForm("name") //获取POST传入的Name  
  
   c.JSON(http.StatusOK, gin.H{ //结果返回一个JSON  
      "ID":   id,  
      "Name": name,  
   })  
})

请求服务器:

1
CURL  -Uri 'http://localhost:8888/post?id=001' -Body 'name=TEST' -Method 'POST' 

返回结果:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
StatusCode        : 200
StatusDescription : OK
Content           : {"ID":"001","Name":"TEST"}
RawContent        : HTTP/1.1 200 OK
                    Content-Length: 26
                    Content-Type: application/json; charset=utf-8
                    Date: Mon, 21 Nov 2022 00:20:33 GMT

                    {"ID":"001","Name":"TEST"}
Forms             : {}
Headers           : {[Content-Length, 26], [Content-Type, application/json; charset=utf-8], [Date, Mon, 21 Nov 2022 00:
                    20:33 GMT]}
Images            : {}
InputFields       : {}
Links             : {}
ParsedHtml        : mshtml.HTMLDocumentClass
RawContentLength  : 26

完整代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main  
  
import (  
   "fmt"  
   "github.com/gin-gonic/gin"   "net/http")  
  
func main() {  
   r := gin.Default()  
  
   //Query 和 POST 混合  
   r.POST("/post", func(c *gin.Context) {  
      id := c.Query("id")        //获取Query传入的ID  
      name := c.PostForm("name") //获取POST传入的Name  
  
      c.JSON(http.StatusOK, gin.H{ //结果返回一个JSON  
         "ID":   id,  
         "Name": name,  
      })  
      
   })  
  
   r.Run(":8888") // 端口号8888  
}

Map参数

1
2
3
4
5
6
7
8
//获取MAP参数  
r.POST("/map", func(c *gin.Context) {  
   name := c.QueryMap("name") //创建接收MAP  
  
   c.JSON(http.StatusOK, gin.H{ //返回响应JSON  
      "Name": name,  
   })  
})

请求服务器:

1
CURL  -Uri 'http://localhost:8888/map?name[Jack]=001'  -Method 'POST'

返回响应:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
StatusCode        : 200
StatusDescription : OK
Content           : {"Name":{"Jack":"001"}}
RawContent        : HTTP/1.1 200 OK
                    Content-Length: 23
                    Content-Type: application/json; charset=utf-8
                    Date: Mon, 21 Nov 2022 00:35:36 GMT

                    {"Name":{"Jack":"001"}}
Forms             : {}
Headers           : {[Content-Length, 23], [Content-Type, application/json; charset=utf-8], [Date, Mon, 21 Nov 2022 00:
                    35:36 GMT]}
Images            : {}
InputFields       : {}
Links             : {}
ParsedHtml        : mshtml.HTMLDocumentClass
RawContentLength  : 23

完整代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package main  
  
import (  
   "fmt"  
   "github.com/gin-gonic/gin"   "net/http")  
  
func main() {  
   r := gin.Default()  
  
   //获取MAP参数  
   r.POST("/map", func(c *gin.Context) {  
      name := c.QueryMap("name") //创建接收MAP  
  
      c.JSON(http.StatusOK, gin.H{ //返回响应JSON  
         "Name": name,  
      })  
   })  
  
   r.Run(":8888") // 端口号8888  
}

重定向

1
2
3
4
5
//GET请求重定向  
r.GET("/redirect", func(c *gin.Context) {  
   c.Redirect(http.StatusMovedPermanently, "https://www.baidu.com/")  
  
})

请求服务器:

1
CURL "http://localhost:8888/redirect"

请求结果:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
StatusCode        : 200
StatusDescription : OK
Content           : <html>
                    <head>
                        <script>
                                location.replace(location.href.replace("https://","http://"));
                        </script>
                    </head>
                    <body>
                        <noscript><meta http-equiv="refresh" content="0;url=http://www.baidu.com/"></...
RawContent        : HTTP/1.1 200 OK
                    Connection: keep-alive
                    Accept-Ranges: bytes
                    Content-Length: 227
                    Cache-Control: no-cache
                    Content-Type: text/html
                    Date: Mon, 21 Nov 2022 00:54:26 GMT
                    P3P: CP=" OTI DSP COR IVA OUR...
Forms             : {}
Headers           : {[Connection, keep-alive], [Accept-Ranges, bytes], [Content-Length, 227], [Cache-Control, no-cache]
                    ...}
Images            : {}
InputFields       : {}
Links             : {}
ParsedHtml        : mshtml.HTMLDocumentClass
RawContentLength  : 227

会发现我们跳转到了Baidu

分组路由 (Grouping Routes)

我们可以将拥有共同前缀URL的路由划分为一个分组路由

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
//分组路由  
G := r.Group("/g")  
  
G.POST("/post", func(c *gin.Context) { //POST 请求  
   name := c.PostForm("name")  
  
   c.String(http.StatusOK, fmt.Sprintf("Post Name: %v\n", name))  
})  
  
G.GET("/get", func(c *gin.Context) { //GET 请求  
   c.String(http.StatusOK, fmt.Sprintf("Hello,World!"))  
})

GET 请求服务器:

1
CURL "http://localhost:8888/g/get"

响应结果:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
StatusCode        : 200
StatusDescription : OK
Content           : Hello,World!
RawContent        : HTTP/1.1 200 OK
                    Content-Length: 12
                    Content-Type: text/plain; charset=utf-8
                    Date: Mon, 21 Nov 2022 01:11:33 GMT

                    Hello,World!
Forms             : {}
Headers           : {[Content-Length, 12], [Content-Type, text/plain; charset=utf-8], [Date, Mon, 21 Nov 2022 01:11:33
                    GMT]}
Images            : {}
InputFields       : {}
Links             : {}
ParsedHtml        : mshtml.HTMLDocumentClass
RawContentLength  : 12

POST 请求服务器:

1
CURL -Uri "http://localhost:8888/g/post" -Body "name=POST" -Method "POST"

响应结果:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
StatusCode        : 200
StatusDescription : OK
Content           : Post Name: POST

RawContent        : HTTP/1.1 200 OK
                    Content-Length: 16
                    Content-Type: text/plain; charset=utf-8
                    Date: Mon, 21 Nov 2022 01:16:06 GMT

                    Post Name: POST

Forms             : {}
Headers           : {[Content-Length, 16], [Content-Type, text/plain; charset=utf-8], [Date, Mon, 21 Nov 2022 01:16:06
                    GMT]}
Images            : {}
InputFields       : {}
Links             : {}
ParsedHtml        : mshtml.HTMLDocumentClass
RawContentLength  : 16

文件上传

Golang 的 Gin框架还支持文件上传,文件上传需要 POST 方法.

单文件上传

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//需要注意的是,GET请求和POST请求需要统一,否则无法上传文件
r.POST("/", func(c *gin.Context) {    //接受上传的文件并且保存到本地
   file, err := c.FormFile("upload")  
   if err != nil {  
      c.String(http.StatusBadRequest, "请求失败 Err: %s", err.Error())  
      return  
   }  
  
   //获取文件名  
   fileName := file.Filename  
   fmt.Println("文件名为: ", fileName) //输出文件名  
  
   //将上传的文件保存带本地  
   err = c.SaveUploadedFile(file, fileName)  
   if err != nil {  
      c.String(http.StatusBadRequest, "文件保存失败 Err: %s", err.Error())  
      return  
   }  
  
   c.String(http.StatusOK, "%s uploaded!", file.Filename)  
})  
  
r.LoadHTMLGlob("./index.html")   //加上上传的HTML文件.这里也可以改成直接加载文件夹下的文件
r.GET("/", func(c *gin.Context) {  
   c.HTML(http.StatusOK, "index.html", nil)  
  
})

HTML模板

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<!DOCTYPE html>  
<html lang="en">  
<head>  
    <meta charset="UTF-8">  
    <title>Upload.HTML</title>  
</head>  
<body>  
    <form action="/" method="post" enctype="multipart/form-data">  
        <input name="upload" type="file">  
        <input name="uploadBtn" type="submit" value="上传">  
  
    </form>  
</body>  
</html>

需要注意的是,如果你直接使用 Goland 编译的话就会报错找不到 HMTL 文件,这时候需要你找到你写的 Go 文件目录,手动编译文件,然后运行 . (比如我的Go文件名是GinFlierUpdate ,我需要这样手动编译)

1
go build GinFlierUpdate.go

完整代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package main  
  
import (  
   "fmt"  
   "github.com/gin-gonic/gin"   "net/http")  
  
func main() {  
   r := gin.Default()  
  
   r.POST("/", func(c *gin.Context) {  
      file, err := c.FormFile("upload")  
      if err != nil {  
         c.String(http.StatusBadRequest, "请求失败 Err: %s", err.Error())  
         return  
      }  
  
      //获取文件名  
      fileName := file.Filename  
      fmt.Println("文件名为: ", fileName) //输出文件名  
  
      //将上传的文件保存带本地  
      err = c.SaveUploadedFile(file, fileName)  
      if err != nil {  
         c.String(http.StatusBadRequest, "文件保存失败 Err: %s", err.Error())  
         return  
      }  
  
      c.String(http.StatusOK, "%s uploaded!", file.Filename)  
   })  
  
   r.LoadHTMLGlob("./index.html")  
   r.GET("/", func(c *gin.Context) {  
      c.HTML(http.StatusOK, "index.html", nil)  
  
   })  
  
   r.Run(":8888") // 端口号8888  
}

多文件上传

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
r.POST("/", func(c *gin.Context) {  
  
   form, _ := c.MultipartForm()  
   files, _ := form.File["upload[]"]   //获取文件数组
  
   for _, file := range files {   //遍历保存
      //获取文件名  
      fileName := file.Filename  
      fmt.Println("文件名为: ", fileName) //输出文件名  
  
      //将上传的文件保存带本地  
      err := c.SaveUploadedFile(file, fileName)  
      if err != nil {  
         c.String(http.StatusBadRequest, "文件保存失败 Err: %s", err.Error())  
         return  
      }  
  
      c.String(http.StatusOK, "%s uploaded!\n", file.Filename)  
   }  
  
})

由于 HTML 的 Input标签只能上传一个文件,所以我们要在标签内加上 multiple 属性来保证能上传多个文件

HTML 模板

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<!DOCTYPE html>  
<html lang="en">  
<head>  
    <meta charset="UTF-8">  
    <title>Upload.HTML</title>  
</head>  
<body>  
    <form action="/" method="post" enctype="multipart/form-data">  
        <input multiple name="upload[]" type="file">  
        <input name="uploadBtn" type="submit" value="上传" formaction="" >  
  
    </form>  
</body>  
</html>

完整代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

package main  
  
import (  
   "fmt"  
   "github.com/gin-gonic/gin"   "net/http")  
  
func main() {  
   r := gin.Default()  
 
   r.POST("/", func(c *gin.Context) {  
  
      form, _ := c.MultipartForm()  
      files, _ := form.File["upload[]"]  
  
      for _, file := range files {  
         //获取文件名  
         fileName := file.Filename  
         fmt.Println("文件名为: ", fileName) //输出文件名  
  
         //将上传的文件保存带本地  
         err := c.SaveUploadedFile(file, fileName)  
         if err != nil {  
            c.String(http.StatusBadRequest, "文件保存失败 Err: %s", err.Error())  
            return  
         }  
  
         c.String(http.StatusOK, "%s uploaded!\n", file.Filename)  
      }  
  
   })  
  
   r.LoadHTMLGlob("./index.html")  
   r.GET("/", func(c *gin.Context) {  
      c.HTML(http.StatusOK, "index.html", nil)  
  
   })  
  
   r.Run(":8888") // 端口号8888  
}

中间件 (Middleware)

所谓中间件,就是连接上下级不同功能的函数或者软件,通常进行一些包裹函数的行为,为被包裹函数提供添加一些功能或行为。

JAVA 中的 Filter() 就是起到了中间件的作用.

在 Go 语言中,中间件 Handler 是封装另一个 http.Handler 以对请求进行预处理或后续处理的 http.Handler。它介于 Go Web 服务器与实际的处理程序之间,因此被称为“中间件”。

使用中间件

中间件的使用相当简单,只要对需要中间件的路由使用 Use()方法即可

实例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main  
  
import (  
   "github.com/gin-gonic/gin"  
   "net/http")  
  
func main() {  
   r := gin.Default()  
  
   r.LoadHTMLGlob("html/*")  
  
   adminGroup := r.Group("/")  
   adminGroup.Use(gin.BasicAuth(gin.Accounts{  
      "admin": "123456",  
   }))  
  
   adminGroup.GET("", func(c *gin.Context) {  
      c.HTML(http.StatusOK, "background.html", nil)  
   })  
  
   r.Run(":8888")  
}

HTML

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<!DOCTYPE html>  
<html lang="en">  
<head>  
    <meta charset="UTF-8">  
    <title>网站后台</title>  
</head>  
<body>  
<h>这里是网站后台</h>  
</body>  
</html>

访问测试

1
CURL "http://localhost:8888/"

这里看到直接返回404

1
2
3
4
5
6
7
CURL : 远程服务器返回错误: (401) 未经授权。
所在位置 行:1 字符: 1
+ CURL "http://localhost:8888/"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-WebRequest]WebExce
    ption
    + FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand

这就是中间件起到作用了

全局中间件

这个是在服务启动就开始注册,全局意味着所有API接口都会经过这里。Gin的中间件是通过Use方法设置的,它接收一个可变参数,所以我们同时可以设置多个中间件。

1
2
3
4
5
6
7
// 1.创建路由  
r := gin.Default()  
//默认带Logger(), Recovery()这两个内置中间件  
r:= gin.New()   
//不带任何中间件  
// 注册中间件  
r.Use(MiddleWare())

gin.Default()默认使用了日志写入中间件 Logger和Recovery中间件

这里新建一个自定义中间件middleware,并且注册成为全局中间件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package main  
  
import (  
   "fmt"  
   "github.com/gin-gonic/gin")  
  
func main() {  
   r := gin.Default()  
  
   r.Use(middleware)  
  
   r.GET("/", func(c *gin.Context) {  
      c.Writer.WriteString("GET API 执行")  
   })  
  
   r.Run(":8888")  
}  
  
func middleware(c *gin.Context) {  
   fmt.Println("Middleware执行")  
}

运行发现请求先到达中间件,然后才到路由的

1
2
3
[GIN-debug] Listening and serving HTTP on :8888
Middleware执行
[GIN] 2022/12/03 - 22:53:15 | 200 |            0s |             ::1 | GET      "/"

局部中间件

局部中间件意味着部分接口才会生效,只在局部使用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main  
  
import (  
   "fmt"  
   "github.com/gin-gonic/gin")  
  
func main() {  
   r := gin.Default()  

	//局部中间件
   r.GET("/GET", middleware, func(c *gin.Context) {  
      c.Writer.WriteString("GET API 执行")  
   })  
  
   r.GET("/", func(c *gin.Context) {  
      c.Writer.WriteString("GET API 执行")  
   })  
  
   r.Run(":8888")  
}  
  
func middleware(c *gin.Context) {  
   fmt.Println("Middleware执行")  
}

Gin控制台输出结果:

1
2
3
4
[GIN-debug] Listening and serving HTTP on :8888
[GIN] 2022/12/03 - 22:55:30 | 200 |            0s |             ::1 | GET      "/"
Middleware执行
[GIN] 2022/12/03 - 22:56:25 | 200 |       369.4µs |             ::1 | GET      "/GET"

自定义中间件

https://cloud.tencent.com/developer/article/1585029

数值传递

可以使用gin.Context中的Set()方法来将中间件处理过的请求进行传递,而 Set()通过一个key来存储作何类型的数据,方便下一层获取。

1
func (c *Context) Set(key string, value interface{})

当我们在中间件中通过Set方法设置一些数值,在下一层中间件或HTTP请求处理方法中,可以使用下面列出的方法通过key获取对应数据。

其中,gin.Context的Get方法返回interface{},通过返回exists可以判断key是否存在。

1
func (c *Context) Get(key string) (value interface{}, exists bool)

当我们确定通过Set方法设置对应数据类型的值时,可以使用下面方法获取应数据类型的值。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
func (c *Context) GetBool(key string) (b bool)  
func (c *Context) GetDuration(key string) (d time.Duration)  
func (c *Context) GetFloat64(key string) (f64 float64)  
func (c *Context) GetInt(key string) (i int)  
func (c *Context) GetInt64(key string) (i64 int64)  
func (c *Context) GetString(key string) (s string)  
func (c *Context) GetStringMap(key string) (sm map[string]interface{})  
func (c *Context) GetStringMapString(key string) (sms map[string]string)  
func (c *Context) GetStringMapStringSlice(key string) (smss map[string][]string)  
func (c *Context) GetStringSlice(key string) (ss []string)  
func (c *Context) GetTime(key string) (t time.Time)

示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package main  
  
import (  
   "github.com/gin-gonic/gin"  
   "strconv")  
  
func main() {  
   r := gin.Default()  
  
   r.GET("/", mw, func(c *gin.Context) {  
      Num := c.GetInt("Num")  
      c.Writer.WriteString(strconv.Itoa(Num))  
   })  
  
   r.Run(":8088")  
}  
  
func mw(c *gin.Context) {  
   c.Set("Num", 100)  
}

访问服务器

1
 CURL http://localhost:8088/

返回响应

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
StatusCode        : 200
StatusDescription : OK
Content           : 100
RawContent        : HTTP/1.1 200 OK
                    Content-Length: 3
                    Content-Type: text/plain; charset=utf-8
                    Date: Sat, 10 Dec 2022 10:29:42 GMT

                    100
Forms             : {}
Headers           : {[Content-Length, 3], [Content-Type, text/plain; charset=utf-8], [Date, Sat, 10 Dec 2022 10:29:42 G
                    MT]}
Images            : {}
InputFields       : {}
Links             : {}
ParsedHtml        : mshtml.HTMLDocumentClass
RawContentLength  : 3

拦截请求与后置拦截

拦截请求

当用户请求不合法时,可以使用下面列出的gin.Context的几个方法中断用户请求:

1
2
3
func (c *Context) Abort()  
func (c *Context) AbortWithError(code int, err error) *Error  
func (c *Context) AbortWithStatus(code int)

使用AbortWithStatusJSON()方法,中断用户请求后,则可以返回json格式的数据.

1
func (c *Context) AbortWithStatusJSON(code int, jsonObj interface{})

后置拦截

当然也可以在中间件处理完请求之后,进行拦截,这时候就要使用 gin.Context 中的 Next() 方法来实现后置拦截.

Next 函数会挂起当前所在的函数,然后调用后面的中间件,待后面中间件执行完毕后,再接着执行当前函数。

Next 函数定义如下:

1
func (c *Context) Next()

示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main  
  
import (  
   "fmt"  
   "github.com/gin-gonic/gin"   "net/http")  
  
func main() {  
   router := gin.New()  
  
   mid1 := func(c *gin.Context) {  
      fmt.Println("mid1 start")  
      c.Next()  
      fmt.Println("mid1 end")  
   }  
   mid2 := func(c *gin.Context) {  
      fmt.Println("mid2 start")  
      fmt.Println("mid2 end")  
   }  
   mid3 := func(c *gin.Context) {  
      fmt.Println("mid3 start")  
      fmt.Println("mid3 end")  
   }  
   router.Use(mid1, mid2, mid3)  
   router.GET("/", func(c *gin.Context) {  
      fmt.Println("process get request")  
      c.JSON(http.StatusOK, "hello")  
   })  
   router.Run(":8088")  
}

请求

1
CURL http://localhost:8088/

响应

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
StatusCode        : 200
StatusDescription : OK
Content           : "hello"
RawContent        : HTTP/1.1 200 OK
                    Content-Length: 7
                    Content-Type: application/json; charset=utf-8
                    Date: Sat, 10 Dec 2022 10:46:06 GMT

                    "hello"
Forms             : {}
Headers           : {[Content-Length, 7], [Content-Type, application/json; charset=utf-8], [Date, Sat, 10 Dec 2022 10:4
                    6:06 GMT]}
Images            : {}
InputFields       : {}
Links             : {}
ParsedHtml        : mshtml.HTMLDocumentClass
RawContentLength  : 7

控制台输出

1
2
3
4
5
6
7
8
[GIN-debug] Listening and serving HTTP on :8088
mid1 start
mid2 start
mid2 end
mid3 start
mid3 end
process get request
mid1 end

异常处理

0%