gin的Abort()方法使用

gin的Abort()方法使用

食用本篇文章,请先看完Gin的Next()方法使用

简单的Http服务的实现

先来实现一个简单的http服务

package main

import (
    "github.com/gin-gonic/gin"
    "github.com/jinfeijie/blog-demo/post-837/middleware/md1"
    "github.com/jinfeijie/blog-demo/post-837/middleware/md2"
    "github.com/jinfeijie/blog-demo/post-837/middleware/md3"
    "github.com/jinfeijie/blog-demo/post-837/middleware/md4"
    "net/http"
)

func main() {
    router := gin.New()
    router.Use(gin.Recovery(), md1.Md1, md2.Md2, md3.Md3, md4.Md4)

    fmt.Println(math.MaxInt8 / 2)
    router.GET("/", func(ctx *gin.Context) {
        ctx.JSON(http.StatusOK, []int{1, 2, 3})
    })

    if err := http.ListenAndServe(":1080", router); err != nil {
        panic(err.Error())
    }
}

我们在程序中引入了4个中间件,来实现非嵌入式的业务处理。
简化后的中间件代码如下(demo代码中,4个中间件只有数字的差异)

package md1

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

func Md1(ctx *gin.Context) {
    fmt.Println("md1 start")
    ctx.Next()
    fmt.Println("md1 end")
}

运行起来后,请求,可以在日志中看到如下输出:

[GIN-debug] GET    /                         --> main.main.func1 (6 handlers)
md1 start
md2 start
md3 start
md4 start
md4 end
md3 end
md2 end
md1 end

依旧是非常典型的洋葱形式的输出。

使用Abort()的效果

md1 是风控模块

假设中间1是一个风控模块,当检测到请求参数异常时,立即进行拦截,不再继续执行后续操作。
我们可以在A中加入Abort(),让他跳过后续步骤的执行。中间件1的实现代码

func Md1(ctx *gin.Context) {
    fmt.Println("md1 start")
    if ctx.Query("danger") == "1" {
        ctx.Abort()
    }
    ctx.Next()
    fmt.Println("md1 end")
}

此时我们通过风控链接访问到服务,就会触发风控机制,直接被abort。

[GIN-debug] GET    /                         --> main.main.func1 (6 handlers)
md1 start
md1 end

md2是登录验证模块

假设中间2是一个登录验证模块,未登录的用户会在这一层被拦截

func Md2(ctx *gin.Context) {
    fmt.Println("md2 start")
    if ctx.Query("login") == "0" {
        ctx.Abort()
    }
    ctx.Next()
    fmt.Println("md2 end")
}

当一个未登录的用户访问过来,就会被这一层拦截,不再执行后续的代码

[GIN-debug] GET    /                         --> main.main.func1 (6 handlers)
md1 start
md2 start
md2 end
md1 end

Abort()如何实现它的功能

从上一篇Next的文章中我们可以知道中间件的执行过程是 Md1.start -> Md2.start -> Md3.start -> service -> Md3.end -> Md2.end -> Md1.end。当中间件中遇到异常执行了abort时,他会中止执行后续的中间件的逻辑,直接结束掉。流程就是Md1.start -> Md2.start -> Md2.end -> Md1.end
Abort()是如何实现他的这个功能的呢?查看代码后会发现,它的实现非常简单,只有一行代码。

c.index = abortIndex

它的作用就是把中间件的索引位放置到第64位,直接跳过前置的中间件的运行。 (const abortIndex = (1<<7 - 1) / 2 )
Abort()方法对应的IsAborted()就是通过当前索引位是否大于等于63。

更优雅地使用Abort()

从上文日志中可以发现,当使用Abort()时,不会有任何的输出。而一个在线服务就算是报错了也需要一个内容返回。那如何更优雅地给出提示?我们可以调用AbortWithStatusJSON(code int, jsonObj interface{})来输出json内容。如果不满足输出可以,可以自己封装一个Abort()

中间件+Abort()组合有何作用

  1. 风控拦截,在中间件层实现风控拦截,可以避免业务代码与风控代码耦合度过高
  2. 登录验证,在中间件层做完权鉴,通过上下文传递,实现业务代码与权鉴的解耦
  3. 黑名单拦截,在中间件层实现黑名单拦截,可以使业务代码可以更加专注于实现业务
  4. 限流,在中间件层处理限流逻辑,可以提升系统的横向扩展能力

潜在的问题

常规情况下一个系统不会有超过64个中间件。一旦中间件超过64个,使用isAbort()就会出现不在预期的结果。

引用

【1】文章相关代码

发表评论

电子邮件地址不会被公开。 必填项已用*标注

评论审核已启用。您的评论可能需要一段时间后才能被显示。