摘要

在上一篇文章中,我们已经可以实现一个性能较高,且支持RESTful风格的路由了。但是,在Web应用的开发中,我们还需要一些可以被扩展的功能。

ControllerHandler

所以在这篇文章中,我们来探究如何更优雅的设计这些中间件。

1 耦合的实现方式

比如我们要实现一个日志记录的功能,我们可以用这种简单粗暴的方式:


如果这样做的话,确实是实现了我们的目标,记录了访问的日志。

但是,这样一点都不优雅。

Handlerrecord函数pathrecord函数

如果这样做,不管我们需要添加什么样的额外功能,都必须得把这个额外的功能和我们的业务逻辑牢牢地绑定到一起,不能实现扩展功能与业务逻辑间的解耦。

2 将记录与实现解耦

既然在上面的实现中,记录日志和业务实现完全的耦合在了一起,那么我们能不能把他们的业务实现解耦开来呢?

来看这段代码:

在这里,我们已经把业务实现和日志记录的耦合给解开了一部分。

record(w,r)record函数record
Handler

那么,有没有办法可以把业务逻辑和扩展功能完全分开,让业务代码里只有业务代码,使代码变得更加整洁呢?我们接着往下看。

3 设计中间件

我们在上一篇文章里面,分析了httprouter这个包的实现。所以我们直接对他动手,修改他的代码,使得这个路由具有扩展性。

3.1 效果

在此之前,我们来看看效果:

/helloHelloGET
main方法

从方法名可以看出,这个方法是在Handle之前增加了一个处理过程。

URL
Hello(w http.ResponseWriter, r *http.Request, _ httprouter.Params)
Handler

下面我们来看看具体的实现过程:

3.2 具体实现

AddBeforeHandle
RouterbeforeHandler
beforeHandlerRouterAddBeforeHandleRouter
httprouter

注意看,router在找到了Handler,准备执行之前,我们添加了这么几行:

AddBeforeHandlebeforeHandlernil

3.3 思考

现在我们已经实现了一个完全解耦的中间件。并且,这个中间件是可以任意配置的。你可以拿来做日志记录,也可以做权限校验等等,而且这些功能还不会对Handler中的业务逻辑造成影响。

FilterAOP
panicHandle

比如:

afterHandler

那么问题来了:现在这样的处理操作,我们仅仅只能在请求前和请求后各自添加一个中间件。如果我们想要添加任意多个中间件,该怎么做呢?

gin

4 Gin的中间件

4.1 使用

众所周知,在阅读源码之前,一定要先看看他是怎么用的:

gin
ginHandleHandle

4.2 源码解释

先从Use方法看起:

groupappendgroup.Handlersgroup.HandlersHandler
Handler

在上一节我们留了一个问题,要怎么实现多个中间件。答案就在这里了,用数组保存。

那么问题又来了:怎么保证调用的顺序呢?

我们继续往下看看路由的注册:

httproutergroup.handle
pathpath
combineHandlers
HandlersChainHandler

也就是说,在这个方法里面,把之前放入group中的中间件,和当前路由的Handler,组合成一个新的数组。

并且,中间件在前面,路由Handler在后面。注意,这个顺序很重要

addRoute

到了这里注册方面的内容已经结束了,我们来看看他是怎么处理各个中间件的调用顺序

ginServeHTTP
*ContextresponseWriter*http.Request
handleHTTPRequest(c)
httprouter请求方法HandlerhandlerContext

这里我们注意看c.Next()方法,他是gin中关于中间件的调用最精妙的部分。我们来看看:

Next()Contexthandler
handlerhandler

我们再来看看日志记录这个中间件:

c.Next()
c.Next()Handler


也就是说,其实中间件的业务逻辑是这样的:

5 写在最后

首先,谢谢你能看到这里。

Handler

在本文中,可能会有很多的疏漏。如果在阅读的过程中,有哪些解释不到位,或者作者的理解出现了一些差错,也请你留言指正。

再次感谢~

PS:如果有其他的问题,也可以在公众号找到作者。并且,所有文章第一时间会在公众号更新,欢迎来找作者玩~