37

Gin框架系列03:换个姿势理解中间件

 4 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzU0NDk0OTY2OA%3D%3D&%3Bmid=2247483986&%3Bidx=1&%3Bsn=42410c1ff52f6e8de7c2eff138891bb5
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

什么是中间件

中间件,英译middleware,顾名思义,放在中间的物件,那么放在谁中间呢?本来,客户端可以直接请求到服务端接口。

YnuQbeA.jpg!web file

现在,中间件横插一脚,它能在请求到达接口之前拦截请求,做一些特殊处理,比如日志记录,故障处理等。这就是今天要讲述的中间件,那么,它在Gin框架中是怎么使用的呢?

Ufii6jY.jpg!web file

如何使用中间件

我们来看一下逢 gin 必调的方法 Default ,方法中有一个变量 engine ,它 UseLoggerRecovery 两个函数,这两个函数就是 gin 框架的日志和故障处理中间件。

func Default() *Engine {
 debugPrintWARNINGDefault()
 engine := New()
 engine.Use(Logger(), Recovery())
 return engine
}

那就很清楚了,使用中间件就是调用 Use 方法就行了呗,问题是现在除了这两个中间件还能去 Use 谁?不如咱先自己写一个中间件吧,这样比较容易理解。

写一个中间件

写啥呢,做产品讲究MVP,那咱就写个最简单的闭环,拦截请求后输出 平也最帅 的日志,产品就可以交付了。

VZZfEvU.png!web file

写之前先研究一下官方的 LoggerRecovery 是怎么写的,好比葫芦画瓢。

func Logger() HandlerFunc {
 return LoggerWithConfig(LoggerConfig{})
}

func Recovery() HandlerFunc {
 return RecoveryWithWriter(DefaultErrorWriter)
}

原来这两个函数都返回了 HandlerFunc 类型,那我们也模仿写一个函数就好了。

func PingYe() gin.HandlerFunc {
 return func(c *gin.Context) {
  c.String(200, "平也最帅")
 }
}

很简单,写完了,可以在 main 函数中 Use 它了。

func main() {
 r := gin.Default()
 r.Use(PingYe())
 r.Run()
}

把项目跑起来,访问 localhsot:8080 看一下,帅呆了,成功渲染数据。

NbERBrr.png!web file

是不是太简单了?这就交差了?我还能打十个啊!?

3iAjuiJ.png!web file

看来我要把毕生所学都交给你了。

延伸阅读

Next

假如我们定义了两个中间件,一个是平也最帅,另一个是在哪里最帅。

func PingYe() gin.HandlerFunc {
 return func(c *gin.Context) {
  c.String(200, "平也最帅")
 }
}

func Where() gin.HandlerFunc {
 return func(c *gin.Context) {
  c.String(200, "在全宇宙")
 }
}

按顺序把它们分别注册到框架当中,这个时候我们猜测它会先输出 平也最帅 再输出 在全宇宙 对吧?对,确实是的。

func main() {
 r := gin.Default()
 r.Use(PingYe(), Where())
 r.Run()
}

但是,如果我在不更改注册顺序的前提下,怎么调换一下顺序,先输出 在全宇宙 再输出 平也最帅 呢?这就用到了大名鼎鼎的 Next 方法。它的作用就是先执行以下一个中间件,执行完了再回来继续执行接下来的逻辑。记得是在中间件中调用哦~

func PingYe() gin.HandlerFunc {
  return func(c *gin.Context) {
    c.Next()
    c.String(200, "平也最帅")
  }
}
QJvQNnF.png!web file

Abort

当然,除了提供 Next 方法外,理论上也应该有个中断操作吧,毕竟拿中间件来做授权验证的话,验证失败后还是希望阻断请求的。所以, Abort 就是我们要找的那个方法。拿上面的例子,在 平也最帅 的下一行调用 Abort 方法后, Where 中间件就不再生效了,于是平也只剩下了单纯的帅气。

func PingYe() gin.HandlerFunc {
 return func(c *gin.Context) {
  c.String(200, "平也最帅")
  c.Abort()
 }
}
VjmE7vz.png!web file

局部中间件

刚才我讲的中间件是会在所有的路由上生效的,有些不需要添加中间件的路由场景就无法适应了。所以,我们需要有能为局部添加中间件的能力。

RBBFNbM.png!web file

我们先实现给某个接口单独加中间件,所以先得定义两个接口 knowunknown ,分别代表认识平也与不认识两个场景,鉴于认识后才知道平也是全宇宙最帅的,所以要绑中间件,不认识就算了。实现方式非常简单,往路由后面的参数拼命加中间件就好了。

r.GET("know", PingYe(), Where())
r.GET("unknown", func(c *gin.Context) {
  c.String(200, "???")
})

除了针对某个接口添加中间件外,还可以针对一组接口添加,同样调用 Use 方法即可。

v1 := r.Group("v1")
v1.Use(PingYe(), Where())
{
  v1.GET("/know", func(c *gin.Context) {
    c.String(200, "know")
  })
  v1.GET("/unknown", func(c *gin.Context) {
    c.String(200, "unknown")
  })
}

HTTP基本认证

基本认证,又称 BasicAuth ,加了基本认证的接口,会让你在访问接口时提供用户名与密码。

UBVrYr3.jpg!web file

对于浏览器用户,为了用户的体验会自动弹出登录框,而在其他场景下是没有的,那在哪里输入账号密码呢?实际上,它是通过头信息传输的,头信息里有一个固定的格式来代表基本认证。

Authorization: Basic <凭证>

凭证部分是是用户名和密码组合的 base64 编码,两者以冒号方式拼接。然鹅, gin 框架的 BaseAuth 中间件早已准备好了一切,它可以短短几行代码就能解析基本认证的信息。

func main() {
 r := gin.Default()
 r.Use(gin.BasicAuth(gin.Accounts{
  "pingye": "123",
 }))
 r.GET("/secrets", func(c *gin.Context) {
  user := c.MustGet(gin.AuthUserKey).(string)
  c.String(200, user+"已登录成功")
 })
 r.Run()
}

示例中的部分代码可能有些同学不太明白,比如 BasicAuth 方法中的参数,因为基本认证需要账号和密码对吧,所以我们可以利用 gin.Accounts 方便的配置好需要验证的账号密码, gin.Accounts 是一个map类型,键代表用户名,值代表密码,当然可以设置不止一个键值对,根据你的喜好自行设置。

代码中还出现了 c.MustGet 方法,这个方法的作用就是一定要取到某个参数,取不到就不干了 panic ,取的是什么呢?就是 gin.AuthUserKey ,在官方中的解释是基本认证中用户凭证的 cookie 名称。

// AuthUserKey is the cookie name for user credential in basic auth.
const AuthUserKey = "user"

EbUFfar.png!web


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK