【GoWeb开发实战】Gin框架_中间件

韩茹

已关注

Golang分布式架构师;资深Android工程师;资深Python工程师;资深Java工程师;ChainDesk区块链全栈工程师,孔壹学院区块链金牌讲师,区块链行业分析师。 2018-12-29 268

中间件middleware

golang的net/http设计的一大特点就是特别容易构建中间件。gin也提供了类似的中间件。需要注意的是中间件只对注册过的路由函数起作用。对于分组路由,嵌套使用中间件,可以限定中间件的作用范围。中间件分为全局中间件,单个路由中间件和群组中间件。

我们之前说过, ContextGin 的核心, 它的构造如下:

type Context struct {
    writermem responseWriter
    Request   *http.Request
    Writer    ResponseWriter

    Params   Params
    handlers HandlersChain
    index    int8

    engine   *Engine
    Keys     map[string]interface{}
    Errors   errorMsgs
    Accepted []string
}

其中 handlers 我们通过源码可以知道就是 []HandlerFunc. 而它的签名正是:

type HandlerFunc func(*Context)

所以中间件和我们普通的 HandlerFunc 没有任何区别对吧, 我们怎么写 HandlerFunc 就可以怎么写一个中间件.

一、全局中间件

先定义一个中间件函数:

func MiddleWare() gin.HandlerFunc {
    return func(c *gin.Context) {
        t := time.Now()
        fmt.Println("before middleware")
        //设置request变量到Context的Key中,通过Get等函数可以取得
        c.Set("request", "client_request")
        //发送request之前
        c.Next()

        //发送requst之后

        // 这个c.Write是ResponseWriter,我们可以获得状态等信息
        status := c.Writer.Status()
        fmt.Println("after middleware,", status)
        t2 := time.Since(t)
        fmt.Println("time:", t2)
    }
}

该函数很简单,只会给c上下文添加一个属性,并赋值。后面的路由处理器,可以根据被中间件装饰后提取其值。需要注意,虽然名为全局中间件,只要注册中间件的过程之前设置的路由,将不会受注册的中间件所影响。只有注册了中间件一下代码的路由函数规则,才会被中间件装饰。

router := gin.Default()

router.Use(MiddleWare())
{
    router.GET("/middleware", func(c *gin.Context) {
        //获取gin上下文中的变量
        request := c.MustGet("request").(string)
        req, _ := c.Get("request")
        fmt.Println("request:",request)
        c.JSON(http.StatusOK, gin.H{
            "middile_request": request,
            "request":         req,
        })
    })
}
router.Run(":8080")

使用router装饰中间件,然后在/middlerware即可读取request的值,注意在router.Use(MiddleWare())代码以上的路由函数,将不会有被中间件装饰的效果。

使用花括号包含被装饰的路由函数只是一个代码规范,即使没有被包含在内的路由函数,只要使用router进行路由,都等于被装饰了。想要区分权限范围,可以使用组返回的对象注册中间件。

运行项目,可以在终端输入命令进行访问,

hanru:~ ruby$ curl http://127.0.0.1:8080/middleware

gin_yunxing36

或者是在浏览器中输入网址进行访问:

gin_yunxing37

然后在服务器端的运行结果如下:

gin_yunxing38

二、Next()方法

我们怎么解决一个请求和一个响应经过我们的中间件呢?神奇的语句出现了, 没错就是 c.Next(),所有中间件都有 RequestResponse 的分水岭, 就是这个 c.Next(),否则没有办法传递中间件。

服务端使用Use方法导入middleware,当请求/middleware来到的时候,会执行MiddleWare(), 并且我们知道在GET注册的时候,同时注册了匿名函数,所有请看Logger函数中存在一个c.Next()的用法,它是取出所有的注册的函数都执行一遍,然后再回到本函数中,所以,本例中相当于是先执行了 c.Next()即注册的匿名函数,然后回到本函数继续执行, 所以本例的Print的输出顺序是: fmt.Println("before middleware")

fmt.Println("request:",request)

fmt.Println("after middleware,", status)

fmt.Println("time:", t2)

如果将c.Next()放在fmt.Println("after middleware,", status)后面,那么fmt.Println("after middleware,", status)和fmt.Println("request:",request)执行的顺序就调换了。所以一切都取决于c.Next()执行的位置。c.Next()的核心代码如下:

// Next should be used only inside middleware.
// It executes the pending handlers in the chain inside the calling handler.
// See example in GitHub.
func (c *Context) Next() {
    c.index++
    for s := int8(len(c.handlers)); c.index < s; c.index++ {
        c.handlers[c.index](c)
    }
}

它其实是执行了后面所有的handlers。

一个请求过来, Gin 会主动调用 c.Next() 一次。因为 handlersslice ,所以后来者中间件会追加到尾部。这样就形成了形如 m1(m2(f())) 的调用链。正如上面数字① ② 标注的一样, 我们会依次执行如下的调用:

m1① -> m2① -> f -> m2② -> m1②

gin-middleware

另外,如果没有注册就使用MustGet方法读取c的值将会抛错,可以使用Get方法取而代之。上面的注册装饰方式,会让所有下面所写的代码都默认使用了router的注册过的中间件。

二、单个路由中间件

当然,gin也提供了针对指定的路由函数进行注册。

router.GET("/before", MiddleWare(), func(c *gin.Context) {
        request := c.MustGet("request").(string)
        c.JSON(http.StatusOK, gin.H{
            "middile_request": request,
        })
    })

把上述代码写在 router.Use(Middleware())之前,同样也能看见/before被装饰了中间件。通过浏览器访问以下地址:

gin_yunxing39

三、中间件实践

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

3.1 简单认证BasicAuth

关于使用gin.BasicAuth() middleware, 可以直接使用一个router group进行处理, 本质和上面的一样。

先定义私有数据:

// 模拟私有数据
var secrets = gin.H{
    "hanru":    gin.H{"email": "hanru@163.com", "phone": "123433"},
    "wangergou": gin.H{"email": "wangergou@example.com", "phone": "666"},
    "ruby":   gin.H{"email": "ruby@guapa.com", "phone": "523443"},
}

然后使用 gin.BasicAuth 中间件,设置授权用户


    authorized := r.Group("/admin", gin.BasicAuth(gin.Accounts{
        "hanru":    "hanru123",
        "wangergou": "1234",
        "ruby":   "hello2",
        "lucy":   "4321",
    }))

最后定义路由:

定义路由
    authorized.GET("/secrets", func(c *gin.Context) {
        // 获取提交的用户名(AuthUserKey)
        user := c.MustGet(gin.AuthUserKey).(string)
        if secret, ok := secrets[user]; ok {
            c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret})
        } else {
            c.JSON(http.StatusOK, gin.H{"user": user, "secret": "NO SECRET :("})
        }
    })

然后启动项目,打开浏览器输入以下网址:http://127.0.0.1:8080/admin/secrets,然后会弹出一个登录框:![gin_yunxing40](http://image.chaindesk.cn/gin_yunxing40.png/mark)

需要输入正确的用户名和密码:

gin_yunxing41

四、总结

/ 1.全局中间件 router.Use(gin.Logger()) router.Use(gin.Recovery())

// 2.单路由的中间件,可以加任意多个 router.GET("/benchmark", MyMiddelware(), benchEndpoint)

// 3.群组路由的中间件 authorized := router.Group("/", MyMiddelware()) // 或者这样用: authorized := router.Group("/") authorized.Use(MyMiddelware()) { ​ authorized.POST("/login", loginEndpoint) }

未经授权禁止转载、改编,转载请注明出处!

本文地址: https://www.chaindesk.com

收藏 0

本文目录

  • • 第1章 【GoWeb开发实战】Gin框架简介

  • • 第2章 【GoWeb开发实战】Gin框架_Router

  • • 第3章 【GoWeb开发实战】Gin框架_Model

  • • 第4章 【GoWeb开发实战】Gin框架_响应

  • • 第5章 【GoWeb开发实战】Gin框架_中间件

  • • 第6章 【GoWeb开发实战】Gin框架_数据库

  • • 第7章 【GoWeb开发实战Gin框架】微博项目实战_项目介绍和项目搭建

  • • 第8章 【GoWeb开发实战Gin框架】微博项目实战_注册用户信息功能

  • • 第9章 【GoWeb开发实战Gin框架】微博项目实战-登录功能

  • • 第10章 【GoWeb开发实战Gin框架】微博项目实战-处理session

  • • 第11章 【GoWeb开发实战Gin框架】微博项目实战-写文章功能

  • • 第12章 【GoWeb开发实战Gin框架】微博项目实战-首页设计

  • • 第13章 【GoWeb开发实战Gin框架】微博项目实战-显示文章详情

  • • 第14章 【GoWeb开发实战Gin框架】微博项目实战-修改文章功能

  • • 第15章 【GoWeb开发实战Gin框架】微博项目实战-删除文章功能

  • • 第16章 【GoWeb开发实战Gin框架】微博项目实战-标签功能

  • • 第17章 【GoWeb开发实战Gin框架】微博项目实战-首页功能扩展

  • • 第18章 【GoWeb开发实战Gin框架】微博项目实战-文件上传和图片展示功能

  • • 第19章 【GoWeb开发实战Gin框架】微博项目实战-关于我

点击查看更多