找回密码
 立即注册
查看: 146|回复: 0

go-gin框架

[复制链接]
发表于 2024-7-15 18:00 | 显示全部楼层 |阅读模式
gin简介
Gin是一个golang的微框架,封装斗劲优雅,API友好,源码注释斗劲明确,已经发布了1.0版本。具有快速灵活,容错便利等特点。其实对于golang而言,web框架的依赖要远比Python,Java之类的要小。自身的net/http足够简单,性能也非常不错。框架更像是一些常用函数或者东西的调集。借助框架开发,不仅可以省去很多常用的封装带来的时间,也有助于团队的编码风格和形成规范。

Gin 包罗以下几个主要的部门:

设计精巧的路由/中间件系统;
简单好用的核心上下文 Context;
附赠东西集(JSON/XML 响应, 数据绑定与校验等).
安装
终端输入:
  1. go get -u http://github.com/gin-gonic/gin
复制代码
使用的时候要导入包:
  1. import ”GitHub - gin-gonic/gin: Gin is a HTTP web framework written in Go (Golang). It features a Martini-like API with much better performance -- up to 40 times faster. If you need smashing performance, get yourself some Gin.”
复制代码
helloworld

新建一个go文件(helloworld.go):
  1. package main
  2. import (
  3.     ”github.com/gin-gonic/gin”
  4.     ”net/http”
  5. )
  6. func main() {
  7.     router := gin.Default()
  8.     router.GET(”/”, func(c *gin.Context) {
  9.         c.String(http.StatusOK, ”Hello World”)
  10.     })
  11.     router.Run(”:8000”)
  12. }
复制代码
使用Gin实现Hello world非常简单,创建一个router(路由),然后执行Run方式即可。

代码布局:

1、router:=gin.Default():这是默认的处事器。使用gin的Default方式创建一个路由Handler;
2、然后通过Http方式绑定路由法则和路由函数。分歧于net/http库的路由函数,gin进行了封装,把request和response都封装到了gin.Context的上下文环境中。
3、最后启动路由的Run方式监听端口。还可以用http.ListenAndServe(”:8080”, router),或者自定义Http处事器配置。
  1. //Run方式
  2. func (engine *Engine) Run(addr ...string) (err error) {
  3.     defer func() { debugPrintError(err) }()
  4.     address := resolveAddress(addr)
  5.     debugPrint(”Listening and serving HTTP on %s\n”, address)
  6.     err = http.ListenAndServe(address, engine)
  7.     return
  8. }
复制代码
路由

处事器

默认处事器

如果不传端标语,默认8080
  1. router.Run()
复制代码
从上面run方式可以看出吗,其内部也是调用的 http.ListenAndServe(),只是在这之前对端口进行了措置
还可以用 http.ListenAndServe(),比如
  1. func main() {
  2.         router := gin.Default()
  3.         router.GET(”/”, func(context *gin.Context) {
  4.                 context.String(http.StatusOK,”Hello World”)
  5.         })
  6.         http.ListenAndServe(”3000”,router)
  7. }
复制代码
http.ListenAndServe()方式内部调用
  1. func ListenAndServe(addr string, handler Handler) error {
  2. //初始化server
  3.         server := &Server{Addr: addr, Handler: handler}
  4.         return server.ListenAndServe()
  5. }
复制代码
因此我们也可以自定义HTTP处事器的配置:
  1. func main() {
  2.         router := gin.Default()
  3.         s := &http.Server{
  4.                 Addr: ”:3000”,
  5.                 Handler:router,
  6.                 ReadTimeout: 10 * time.Second,
  7.                 WriteTimeout: 10 * time.Second,
  8.                 MaxHeaderBytes: 1 << 20,
  9.         }
  10.         s.ListenAndServe()
  11. }
复制代码
当然Server还有很多其他配置,这里纷歧一列举
路由

基本路由

gin框架中的基本路由采用的是httprouter
  1. // 创建带有默认中间件的路由:
  2. // 日志与恢复中间件
  3. router := gin.Default()
  4. //创建不带中间件的路由:
  5. //r := gin.New()
  6. router.GET(”/someGet”, getting)
  7. router.POST(”/somePost”, posting)
  8. router.PUT(”/somePut”, putting)
  9. router.DELETE(”/someDelete”, deleting)
  10. router.PATCH(”/somePatch”, patching)
  11. router.HEAD(”/someHead”, head)
  12. router.OPTIONS(”/someOptions”, options)
复制代码
路由参数
gin的路由来自httprouter库。因此httprouter具有的功能,gin也具有,不外gin不撑持路由正则表达式。
api参数

api参数通过Context的Param方式来获取
  1. router.GET(”/user/:name”, func(c *gin.Context) {
  2.     name := c.Param(”name”)
  3.     c.String(http.StatusOK, name)
  4. })
复制代码
运行后浏览器输入:http://127.0.0.1:3000/user/hanru运行

冒号:加上一个参数名组成路由参数。可以使用c.Params的方式读取其值。当然这个值是字串string。诸如/user/hanru,和/user/hello都可以匹配,而/user/和/user/hanru/不会被匹配。
  1. router.GET(”/user/:name/*action”, func(c *gin.Context) {
  2.     name := c.Param(”name”)
  3.     action := c.Param(”action”)
  4.     message := name + ” is ” + action
  5.     c.String(http.StatusOK, message)
  6. })
复制代码
浏览器中输入:http://127.0.0.1:3000/user/hanru/send

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

对于参数的措置,经常会呈现参数不存在的情况,对于是否提供默认值,gin也考虑了,而且给出了一个优雅的方案,使用c.DefaultQuery方式读取参数,此中当参数不存在的时候,提供一个默认值。使用Query方式读取正常参数,当参数不存在的时候,返回空字串。
  1. func main() {
  2.     router := gin.Default()
  3.     router.GET(”/welcome”, func(c *gin.Context) {
  4.         name := c.DefaultQuery(”name”, ”Guest”) //可设置默认值:”“
  5.         //nickname := c.Query(”nickname”) // 是 c.Request.URL.Query().Get(”nickname”) 的简写
  6.         c.String(http.StatusOK, fmt.Sprintf(”Hello %s ”, name))
  7.     })
  8.     router.Run(”:3000”)
  9. }
复制代码
表单参数
常见的格式就有四种。例如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 方式获取:
  1. func main() {
  2.     router := gin.Default()
  3.     //form
  4.     router.POST(”/form”, func(c *gin.Context) {
  5.         type1 := c.DefaultPostForm(”type”, ”alert”) //可设置默认值
  6.         username := c.PostForm(”username”)
  7.         password := c.PostForm(”password”)
  8.         //hobbys := c.PostFormMap(”hobby”)
  9.         //hobbys := c.QueryArray(”hobby”)
  10.         hobbys := c.PostFormArray(”hobby”)
  11.         c.String(http.StatusOK, fmt.Sprintf(”type is %s, username is %s, password is %s,hobby is %v”, type1, username, password,hobbys))
  12.     })
  13.     router.Run(”:9527”)
  14. }
复制代码
文件上传

上传单个文件

multipart/form-data用于文件上传。gin文件上传也很便利,和原生的net/http方式类似,分歧在于gin把原生的request封装到c.Request中了。
  1. func main() {
  2.     router := gin.Default()
  3.     // Set a lower memory limit for multipart forms (default is 32 MiB)
  4.     // router.MaxMultipartMemory = 8 << 20  // 8 MiB
  5.     router.POST(”/upload”, func(c *gin.Context) {
  6.         // 单个文件
  7.         file, _ := c.FormFile(”file”)
  8.         log.Println(file.Filename)
  9.         // Upload the file to specific dst.
  10.         c.SaveUploadedFile(file, file.Filename)
  11.         /*
  12.         也可以直接使用io操作,拷贝文件数据。
  13.         out, err := os.Create(filename)
  14.         defer out.Close()
  15.         _, err = io.Copy(out, file)
  16.         */
  17.         c.String(http.StatusOK, fmt.Sprintf(”&#39;%s&#39; uploaded!”, file.Filename))
  18.     })
  19.     router.Run(”:8080”)
  20. }
复制代码
使用c.Request.FormFile解析客户端文件name属性。如果不传文件,则会抛错,因此需要措置这个错误。此处我们略写了错误措置。一种是直接用c.SaveUploadedFile()保留文件。另一种方式是使用os的操作,把文件数据复制到硬盘上。

上传多个文件
所谓多个文件,无非就是多一次遍历文件,然后一次copy数据存储即可。
  1. package main
  2. import (
  3.     ”http://github.com/gin-gonic/gin”
  4.     ”net/http”
  5.     ”fmt”
  6. )
  7. func main() {
  8.     router := gin.Default()
  9.     // Set a lower memory limit for multipart forms (default is 32 MiB)
  10.     router.MaxMultipartMemory = 8 << 20 // 8 MiB
  11.     //router.Static(”/”, ”./public”)
  12.     router.POST(”/upload”, func(c *gin.Context) {
  13.         // Multipart form
  14.         form, err := c.MultipartForm()
  15.         if err != nil {
  16.             c.String(http.StatusBadRequest, fmt.Sprintf(”get form err: %s”, err.Error()))
  17.             return
  18.         }
  19.         files := form.File[”files”]
  20.         for _, file := range files {
  21.             if err := c.SaveUploadedFile(file, file.Filename); err != nil {
  22.                 c.String(http.StatusBadRequest, fmt.Sprintf(”upload file err: %s”, err.Error()))
  23.                 return
  24.             }
  25.         }
  26.         c.String(http.StatusOK, fmt.Sprintf(”Uploaded successfully %d files ”, len(files)))
  27.     })
  28.     router.Run(”:8080”)
  29. }
复制代码
Model

数据解析绑定

模型绑定可以将请求体绑定给一个类型。目前Gin撑持JSON、XML、YAML和尺度表单值的绑定。简单来说,,就是按照Body数据类型,将数据赋值到指定的布局体变量中 (类似于序列化和反序列化) 。
Gin提供了两套绑定方式:
Must bind
方式:Bind,BindJSON,BindXML,BindQuery,BindYAML
行为:这些方式使用MustBindWith。如果存在绑定错误,则用c终止请求,使用c.AbortWithError (400) .SetType (ErrorTypeBind)即可。将响应状态代码设置为400,Content-Type header设置为text/plain;charset = utf - 8。请注意,如果在此之后设置响应代码,将会受到警告:[GIN-debug][WARNING] Headers were already written. Wanted to override status code 400 with 422将导致已经编写了警告[GIN-debug][warning]标头。如果想更好地控制行为,可以考虑使用ShouldBind等效方式。
Should bind
方式:ShouldBind,ShouldBindJSON,ShouldBindXML,ShouldBindQuery,ShouldBindYAML
行为:这些方式使用ShouldBindWith。如果存在绑定错误,则返回错误,开发人员有责任适当地措置请求和错误
注意,使用绑定方式时,Gin 会按照请求头中 Content-Type 来自动判断需要解析的类型。如果你明确绑定的类型,你可以不用自动揣度,而用 BindWith 方式。 你也可以指定某字段是必需的。如果一个字段被 binding:”required” 修饰而值倒是空的,请求会掉败并返回错误。
JSON绑定

JSON的绑定,其实就是将request中的Body中的数据按照JSON格式进行解析,解析后存储到布局体对象中。
  1. package main
  2. import (
  3.     ”github.com/gin-gonic/gin”
  4.     ”net/http”
  5. )
  6. type Login struct {
  7.     User     string `form:”username” json:”user” uri:”user” xml:”user”  binding:”required”`
  8.     Password string `form:”password” json:”password” uri:”password” xml:”password” binding:”required”`
  9. }
  10. func main() {
  11.     router := gin.Default()
  12.     //1.binding JSON
  13.     // Example for binding JSON ({”user”: ”hanru”, ”password”: ”hanru123”})
  14.     router.POST(”/loginJSON”, func(c *gin.Context) {
  15.         var json Login
  16.         //其实就是将request中的Body中的数据按照JSON格式解析到json变量中
  17.         if err := c.ShouldBindJSON(&json); err != nil {
  18.             c.JSON(http.StatusBadRequest, gin.H{”error”: err.Error()})
  19.             return
  20.         }
  21.         if json.User != ”hanru” || json.Password != ”hanru123” {
  22.             c.JSON(http.StatusUnauthorized, gin.H{”status”: ”unauthorized”})
  23.             return
  24.         }
  25.         c.JSON(http.StatusOK, gin.H{”status”: ”you are logged in”})
  26.     })
  27.     router.Run(”:8080”)
  28. }
复制代码
前面我们使用c.String返回响应,顾名思义则返回string类型。content-type是plain或者text。调用c.JSON则返回json数据。此中gin.H封装了生成json的方式,是一个强大的东西。使用golang可以像动态语言一样写字面量的json,对于嵌套json的实现,嵌套gin.H即可。

Form表单
其实本质是将c中的request中的body数据解析到form中。首先我们先看一下绑定普通表单的例子:
  1. // 3. Form 绑定普通表单的例子
  2.     // Example for binding a HTML form (user=hanru&password=hanru123)
  3.     router.POST(”/loginForm”, func(c *gin.Context) {
  4.         var form Login
  5.         //方式一:对于FORM数据直接使用Bind函数, 默认使用使用form格式解析,if c.Bind(&form) == nil
  6.         // 按照请求头中 content-type 自动揣度.
  7.         if err := c.Bind(&form); err != nil {
  8.             c.JSON(http.StatusBadRequest, gin.H{”error”: err.Error()})
  9.             return
  10.         }
  11.         if form.User != ”hanru” || form.Password != ”hanru123” {
  12.             c.JSON(http.StatusUnauthorized, gin.H{”status”: ”unauthorized”})
  13.             return
  14.         }
  15.         c.JSON(http.StatusOK, gin.H{”status”: ”you are logged in”})
  16.     })
复制代码
方式二
  1. router.POST(”/login”, func(c *gin.Context) {
  2.         var form Login
  3.         //方式二: 使用BindWith函数,如果你明确知道数据的类型
  4.         // 你可以显式声明来绑定多媒体表单:
  5.         // c.BindWith(&form, binding.Form)
  6.         // 或者使用自动揣度:
  7.         if c.BindWith(&form, binding.Form) == nil {
  8.             if form.User == ”user” && form.Password == ”password” {
  9.                 c.JSON(200, gin.H{”status”: ”you are logged in ..... ”})
  10.             } else {
  11.                 c.JSON(401, gin.H{”status”: ”unauthorized”})
  12.             }
  13.         }
  14.     })
复制代码
Uri绑定
  1. // 5.URI
  2.     router.GET(”/:user/:password”, func(c *gin.Context) {
  3.         var login Login
  4.         if err := c.ShouldBindUri(&login); err != nil {
  5.             c.JSON(400, gin.H{”msg”: err})
  6.             return
  7.         }
  8.         c.JSON(200, gin.H{”username”: login.User, ”password”: login.Password})
  9.     })
复制代码
响应

既然请求可以使用分歧的content-type,响应也如此。凡是响应会有html,text,plain,json和xml等。 gin提供了很优雅的衬着方式。
JSON/XML/YAML衬着
  1. package main
  2. import (
  3.     ”http://github.com/gin-gonic/gin”
  4.     ”net/http”
  5.     ”github.com/gin-gonic/gin/testdata/protoexample”
  6. )
  7. func main() {
  8.     r := gin.Default()
  9.     // gin.H is a shortcut for map[string]interface{}
  10.     r.GET(”/someJSON”, func(c *gin.Context) {
  11.         c.JSON(http.StatusOK, gin.H{”message”: ”hey”, ”status”: http.StatusOK})
  12.     })
  13.     r.GET(”/moreJSON”, func(c *gin.Context) {
  14.         // You also can use a struct
  15.         var msg struct {
  16.             Name    string `json:”user”`
  17.             Message string
  18.             Number  int
  19.         }
  20.         msg.Name = ”hanru”
  21.         msg.Message = ”hey”
  22.         msg.Number = 123
  23.         // 注意 msg.Name 变成了 ”user” 字段
  24.         // 以下方式城市输出 :   {”user”: ”hanru”, ”Message”: ”hey”, ”Number”: 123}
  25.         c.JSON(http.StatusOK, msg)
  26.     })
  27.     r.GET(”/someXML”, func(c *gin.Context) {
  28.         c.XML(http.StatusOK, gin.H{”user”:”hanru”,”message”: ”hey”, ”status”: http.StatusOK})
  29.     })
  30.     r.GET(”/someYAML”, func(c *gin.Context) {
  31.         c.YAML(http.StatusOK, gin.H{”message”: ”hey”, ”status”: http.StatusOK})
  32.     })
  33.     r.GET(”/someProtoBuf”, func(c *gin.Context) {
  34.         reps := []int64{int64(1), int64(2)}
  35.         label := ”test”
  36.         // The specific definition of protobuf is written in the testdata/protoexample file.
  37.         data := &protoexample.Test{
  38.             Label: &label,
  39.             Reps:  reps,
  40.         }
  41.         // Note that data becomes binary data in the response
  42.         // Will output protoexample.Test protobuf serialized data
  43.         c.ProtoBuf(http.StatusOK, data)
  44.     })
  45.     // Listen and serve on 0.0.0.0:8080
  46.     r.Run(”:8080”)
  47. }
复制代码
HTML模板衬着

gin撑持加载HTML模板, 然后按照模板参数进行配置并返回相应的数据。
先要使用 LoadHTMLGlob()或者LoadHTMLFiles()方式来加载模板文件
  1. func main() {
  2.     router := gin.Default()
  3.     //加载模板
  4.     router.LoadHTMLGlob(”templates/*”)
  5.     //router.LoadHTMLFiles(”templates/template1.html”, ”templates/template2.html”)
  6.     //定义路由
  7.     router.GET(”/index”, func(c *gin.Context) {
  8.         //按照完整文件名衬着模板,并传递参数
  9.         c.HTML(http.StatusOK, ”index.tmpl”, gin.H{
  10.             ”title”: ”Main website”,
  11.         })
  12.     })
  13.     router.Run(”:8080”)
  14. }
复制代码
创建一个目录:templates,然后在该目录下创建一个模板文件:
templates/index.tmpl
  1. <html>
  2.     <h1>
  3.         {{ .title }}
  4.     </h1>
  5. </html>
复制代码
运行项目,打开浏览器输入地址:http://127.0.0.1:8080/index
分歧文件夹下模板名字可以不异,此时需要 LoadHTMLGlob() 加载两层模板路径。
  1. router.LoadHTMLGlob(”templates/**/*”)
  2.     router.GET(”/posts/index”, func(c *gin.Context) {
  3.         c.HTML(http.StatusOK, ”posts/index.tmpl”, gin.H{
  4.             ”title”: ”Posts”,
  5.         })
  6.         c.HTML(http.StatusOK, ”users/index.tmpl”, gin.H{
  7.             ”title”: ”Users”,
  8.         })
  9.     })
复制代码
gin也可以使用自定义的模板引擎,如下
  1. import ”html/template”
  2. func main() {
  3.     router := gin.Default()
  4.     html := template.Must(template.ParseFiles(”file1”, ”file2”))
  5.     router.SetHTMLTemplate(html)
  6.     router.Run(”:8080”)
  7. }
复制代码
文件响应

静态文件处事

可以向客户端展示当地的一些文件信息,例如显示某路径下地文件。处事端代码是:
  1. package main
  2. import (
  3.     ”http://github.com/gin-gonic/gin”
  4.     ”net/http”
  5. )
  6. func main() {
  7.     router := gin.Default()
  8.     // 下面测试静态文件处事
  9.     // 显示当前文件夹下的所有文件/或者指定文件
  10.     router.StaticFS(”/showDir”, http.Dir(”.”))
  11.     router.StaticFS(”/files”, http.Dir(”/bin”))
  12.     //Static提供给定文件系统根目录中的文件。
  13.     //router.Static(”/files”, ”/bin”)
  14.     router.StaticFile(”/image”, ”./assets/miao.jpg”)
  15.     router.Run(”:8080”)
  16. }
复制代码
重定向
  1. package main
  2. import (
  3.     ”GitHub - gin-gonic/gin: Gin is a HTTP web framework written in Go (Golang). It features a Martini-like API with much better performance -- up to 40 times faster. If you need smashing performance, get yourself some Gin.”
  4.     ”net/http”
  5. )
  6. func main() {
  7.     r := gin.Default()
  8.     r.GET(”/redirect”, func(c *gin.Context) {
  9.         //撑持内部和外部的重定向
  10.         c.Redirect(http.StatusMovedPermanently, ”百度一下,你就知道”)
  11.     })
  12.     r.Run(”:8080”)
  13. }
复制代码
同步异步

goroutine 机制可以便利地实现异步措置。当在中间件或措置法式中启动新的Goroutines时,你不应该在原始上下文使用它,你必需使用只读的副本。
  1. import (
  2.     ”time”
  3.     ”http://github.com/gin-gonic/gin”
  4.     ”log”
  5. )
  6. func main() {
  7.     r := gin.Default()
  8.     //1. 异步
  9.     r.GET(”/long_async”, func(c *gin.Context) {
  10.         // goroutine 中只能使用只读的上下文 c.Copy()
  11.         cCp := c.Copy()
  12.         go func() {
  13.             time.Sleep(5 * time.Second)
  14.             // 注意使用只读上下文
  15.             log.Println(”Done! in path ” + cCp.Request.URL.Path)
  16.         }()
  17.     })
  18.     //2. 同步
  19.     r.GET(”/long_sync”, func(c *gin.Context) {
  20.         time.Sleep(5 * time.Second)
  21.         // 注意可以使用原始上下文
  22.         log.Println(”Done! in path ” + c.Request.URL.Path)
  23.     })
  24.     // Listen and serve on 0.0.0.0:8080
  25.     r.Run(”:8080”)
  26. }
复制代码
中间件middleware
golang的net/http设计的一大特点就是出格容易构建中间件。gin也提供了类似的中间件。需要注意的是中间件只对注册过的路由函数起感化。对于分组路由,嵌套使用中间件,可以限定中间件的感化范围。中间件分为全局中间件,单个路由中间件和群组中间件。

我们之前说过, Context 是 Gin 的核心, 它的构造如下:
  1. type Context struct {
  2.     writermem responseWriter
  3.     Request   *http.Request
  4.     Writer    ResponseWriter
  5.     Params   Params
  6.     handlers HandlersChain
  7.     index    int8
  8.     engine   *Engine
  9.     Keys     map[string]interface{}
  10.     Errors   errorMsgs
  11.     Accepted []string
  12. }
复制代码
此中handlers我们通过源码可以知道就是[]HandlerFunc. 而它的签名正是:
  1. type HandlerFunc func(*Context)
复制代码
所以中间件和我们普通的 HandlerFunc 没有任何区别对吧, 我们怎么写 HandlerFunc 就可以怎么写一个中间件.
全局中间件

定义一个中间件函数:
  1. func MiddleWare() gin.HandlerFunc {
  2.     return func(c *gin.Context) {
  3.         t := time.Now()
  4.         fmt.Println(”before middleware”)
  5.         //设置request变量到Context的Key中,通过Get等函数可以取得
  6.         c.Set(”request”, ”client_request”)
  7.         //发送request之前
  8.         c.Next()
  9.         //发送requst之后
  10.         // 这个c.Write是ResponseWriter,我们可以获得状态等信息
  11.         status := c.Writer.Status()
  12.         fmt.Println(”after middleware,”, status)
  13.         t2 := time.Since(t)
  14.         fmt.Println(”time:”, t2)
  15.     }
  16. }
复制代码
该函数很简单,只会给c上下文添加一个属性,并赋值。后面的路由措置器,可以按照被中间件装饰后提取其值。需要注意,虽然名为全局中间件,只要注册中间件的过程之前设置的路由,将不会受注册的中间件所影响。只有注册了中间件以下代码的路由函数法则,才会被中间件装饰。
  1. func main() {
  2.         router := gin.Default()
  3.         router.Use(MiddleWare())
  4.         {
  5.                 router.GET(”/middleware”, func(c *gin.Context) {
  6.                         //获取用gin上下文中的变量
  7.                         request := c.MustGet(”request”).(string)
  8.                         req, _ := c.Get(”request”)
  9.                         fmt.Println(”request:”, request)
  10.                         c.JSON(http.StatusOK, gin.H{
  11.                                 ”middle_request”: request,
  12.                                 ”request”:        req,
  13.                         })
  14.                 })
  15.         }
  16.         router.Run(”:3000”)
  17. }
复制代码
使用router装饰中间件,然后在/middlerware即可读取request的值,注意在router.Use(MiddleWare())代码以上的路由函数,将不会有被中间件装饰的效果。
使用花括号包含被装饰的路由函数只是一个代码规范,即使没有被包含在内的路由函数,只要使用router进行路由,都等于被装饰了。想要区分权限范围,可以使用组返回的对象注册中间件。
Next()方式
怎么解决一个请求和一个响应颠末我们的中间件呢?神奇的语句呈现了, 没错就是 c.Next(),所有中间件都有 Request 和 Response 的分水岭, 就是这个 c.Next(),否则没有法子传递中间件。

如果将c.Next()放在fmt.Println(”after middleware,”, status)后面,那么fmt.Println(”after middleware,”, status)和fmt.Println(”request:”,request)执行的挨次就调换了。c.Next()执行的位置。c.Next()的核心代码如下:
  1. func (c *Context) Next() {
  2.     c.index++
  3.     // for循环,执行完后买呢所有的handlers
  4.     for s := int8(len(c.handlers)); c.index < s; c.index++ {
  5.         c.handlers[c.index](c)
  6.     }
  7. }
复制代码
它其实是执行了后面所有的handlers。

一个请求过来, Gin 会主动调用 c.Next() 一次。因为 handlers 是 slice ,所以后来者中间件会追加到尾部。这样就形成了形如 m1(m2(f())) 的调用链。正如上面数字① ② 标注的一样, 我们会依次执行如下的调用:
  1. m1① -> m2① -> f -> m2② -> m1②
复制代码
此外,如果没有注册就使用MustGet方式读取c的值将会抛错,可以使用Get方式取而代之。上面的注册装饰方式,会让所有下面所写的代码都默认使用了router的注册过的中间件。
单个路由中间件

gin也提供了针对指定的路由函数进行注册。
  1. router.GET(”/before”, MiddleWare(), func(c *gin.Context) {
  2.         request := c.MustGet(”request”).(string)
  3.         c.JSON(http.StatusOK, gin.H{
  4.             ”middile_request”: request,
  5.         })
  6.     })
复制代码
把上述代码写在router.Use(Middleware())之前,同样也能看见/before被装饰了中间件。

中间件实践
中间件最大的感化,莫过于用于一些记录log,错误handler,还有就是对部门接口的鉴权。下面就实现一个简易的鉴权中间件。

简单认证BasicAuth
关于使用gin.BasicAuth() middleware, 可以直接使用一个router group进行措置, 本质和上面的一样。

定义私有数据:
  1. // 模拟私有数据
  2. var secrets = gin.H{
  3.     ”hanru”:    gin.H{”email”: ”hanru@163.com”, ”phone”: ”123433”},
  4.     ”wangergou”: gin.H{”email”: ”wangergou@example.com”, ”phone”: ”666”},
  5.     ”ruby”:   gin.H{”email”: ”ruby@guapa.com”, ”phone”: ”523443”},
  6. }
复制代码
然后使用 gin.BasicAuth 中间件,设置授权用户
  1.    authorized := r.Group(”/admin”, gin.BasicAuth(gin.Accounts{
  2.         ”hanru”:    ”hanru123”,
  3.         ”wangergou”: ”1234”,
  4.         ”ruby”:   ”hello2”,
  5.         ”lucy”:   ”4321”,
  6.     }))
复制代码
最后定义路由:
  1.     authorized.GET(”/secrets”, func(c *gin.Context) {
  2.         // 获取提交的用户名(AuthUserKey)
  3.         user := c.MustGet(gin.AuthUserKey).(string)
  4.         if secret, ok := secrets[user]; ok {
  5.             c.JSON(http.StatusOK, gin.H{”user”: user, ”secret”: secret})
  6.         } else {
  7.             c.JSON(http.StatusOK, gin.H{”user”: user, ”secret”: ”NO SECRET :(”})
  8.         }
  9.     })
复制代码
然后启动项目,打开浏览器输入以下网址:http://127.0.0.1:8080/admin/secrets

总结
全局中间件router.Use(gin.Logger()) router.Use(gin.Recovery())
单路由的中间件,可以加任意多个router.GET(”/benchmark”, MyMiddelware(), benchEndpoint)
群组路由的中间件 authorized := router.Group(”/”, MyMiddelware()) 或者这样用:authorized := router.Group(”/”) authorized.Use(MyMiddelware()) { authorized.POST(”/login”, loginEndpoint) }
————————————————
原文作者:ronin223
原文链接:https://blog.csdn.net/m0_56653501/article/details/123777361
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2025-1-22 18:09 , Processed in 0.113775 second(s), 27 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表