摘要
在上一篇文章中,我们已经可以实现一个性能较高,且支持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:如果有其他的问题,也可以在公众号找到作者。并且,所有文章第一时间会在公众号更新,欢迎来找作者玩~