责任链模式
责任链模式:在处理核心业务前后,可能会有很多道自定义的工序
,每道工序
间存在线性依赖关系。简单粗暴的,是所有代码揉成一团
责任链模式使得每道工序
可以自由拼接,传递顺序明确,便于扩展
典型的应用是 http 请求处理, github 上有一个项目使用了该模式,网址如下:
https://github.com/gin-gonic/gin#using-middleware
下面先给一个感性的例子,再详细分析下其实现原理
gin 使用例子展示
这里引用 bilibili/discovery 中使用 gin 的例子:
(摘录部分代码,增加注释、删除干扰代码等)
func Init(...) {
engine := gin.New() // 实例化 gin
engine.Use(loggerHandler, recoverHandler) // 注册中间件
innerRouter(engine) // 注册 HTTP 请求处理函数
go func() {
if err := engine.Run(c.HTTPServer.Addr); err != nil { // 开始监听端口,提供 HTTP 服务
panic(err)
}
}()
}
// 注册 HTTP 请求处理函数
func innerRouter(e *gin.Engine) {
group := e.Group("/discovery")
group.POST("/register", register)
group.POST("/renew", renew)
}
// 工序1:打印日志,包括 耗时、错误号、IP信息等
func loggerHandler(c *gin.Context) {
// Start timer
start := time.Now()
path := c.Request.URL.Path
raw := c.Request.URL.RawQuery
method := c.Request.Method
// Process request
c.Next()
// Stop timer
end := time.Now()
latency := end.Sub(start)
statusCode := c.Writer.Status()
ecode := c.GetInt(contextErrCode)
clientIP := c.ClientIP()
if raw != "" {
path = path + "?" + raw
}
log.Infof("METHOD:%s | PATH:%s | CODE:%d | IP:%s | TIME:%d | ECODE:%d", method, path, statusCode, clientIP, latency/time.Millisecond, ecode)
}
// 工序2:异常捕获,打印异常堆栈信息、并应答 500 错误号
func recoverHandler(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
const size = 64 << 10
buf := make([]byte, size)
buf = buf[:runtime.Stack(buf, false)]
httprequest, _ := httputil.DumpRequest(c.Request, false)
pnc := fmt.Sprintf("[Recovery] %s panic recovered:\n%s\n%s\n%s", time.Now().Format("2006-01-02 15:04:05"), string(httprequest), err, buf)
fmt.Fprintf(os.Stderr, pnc)
log.Error(pnc)
c.AbortWithStatus(500)
}
}()
c.Next()
}
粗看这段代码,刚开始我是想不通的,loggerHandler 、recoverHandler 、与具体的业务逻辑 register 、renew ,都是独立的函数,如何做到让 loggerHandler 能监测到耗时、 recoverHandler 做到异常捕获呢?
看了下 gin 代码即可理解,其核心在于 c.Next() 方法
实现原理分析
上述代码,1 个 HTTP 请求进来,实际执行过程,函数调用栈如下:
函数调用栈 ----->
+----------------+ +----------------+ +----------------+ +----------------+ +----------------+
| 1 | | 2 | | 3 | | 4 | | 5 |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | register | | | | |
| | | | +----------------+ | | | |
| | | | | c.Next | | | | |
| | +----------------+ +----------------+ +----------------+ | |
| | | recoverHandler | | recoverHandler | | recoverHandler | | |
| | +----------------+ +----------------+ +----------------+ | |
| | | c.Next | | c.Next | | c.Next | | |
+----------------+ +----------------+ +----------------+ +----------------+ +----------------+
| loggerHandler | | loggerHandler | | loggerHandler | | loggerHandler | | loggerHandler |
+----------------+ +----------------+ +----------------+ +----------------+ +----------------+
| c.Next | | c.Next | | c.Next | | c.Next | | c.Next |
+----------------+ +----------------+ +----------------+ +----------------+ +----------------+
有上图可以看出:
- 业务逻辑 register 在 loggerHandler 函数内的 c.Next() 函数内执行,因此 loggerHandler 可以计算耗时
- 业务逻辑 register 在 recoverHandler 函数内的 c.Next() 函数内执行,因此 recoverHandler 可以捕获异常
因此不难猜测:
- engine.Use 、 engine.Group.POST 等方法的作用,就是注册
句柄
时,顺便把这些句柄组织成函数链(责任链)
gin 代码分析
1. 组织责任链
组织责任链
相关代码均在 https://github.com/gin-gonic/gin/blob/master/routergroup.go
摘录主要相关代码如下,并添加注释:
-
gin.Engine ,组合嵌入 RouterGroup,因此具有 RouterGroup 的所有属性
type Engine struct { RouterGroup }
-
RouterGroup.Use ,把 middleware (
责任
)按序保存到 Handlers 字段// Use adds middleware to the group, see example code in GitHub. func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes { group.Handlers = append(group.Handlers, middleware...) return group.returnObj() }
-
RouterGroup.POST ,把业务逻辑加到 middleware 链(
责任链
)尾部,并正式注册路由(group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) IRoutes { return group.handle("POST", relativePath, handlers) } func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes { absolutePath := group.calculateAbsolutePath(relativePath) handlers = group.combineHandlers(handlers) // 将业务句柄 handlers 加到 middleware 链尾部 group.engine.addRoute(httpMethod, absolutePath, handlers) // 这里才是真正注册句柄(该句柄为 middleware 链 + 业务句柄 handlers ) return group.returnObj() } func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain { finalSize := len(group.Handlers) + len(handlers) if finalSize >= int(abortIndex) { panic("too many handlers") } mergedHandlers := make(HandlersChain, finalSize) copy(mergedHandlers, group.Handlers) copy(mergedHandlers[len(group.Handlers):], handlers) // 将业务句柄 handlers 加到 middleware 链尾部 return mergedHandlers }
-
RouterGroup.Group ,与
责任链模式
无关代码,仅为代码阅读好看些,用于保存 URL 请求的公共部分func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup { return &RouterGroup{ Handlers: group.combineHandlers(handlers), basePath: group.calculateAbsolutePath(relativePath), // 保存 URL 请求公共部分 engine: group.engine, } }
2. 执行
执行责任链
相关代码均在: https://github.com/gin-gonic/gin/blob/master/gin.go
摘录主要相关代码如下,并添加注释:
-
Engine.addRoute ,按 URL 请求路径,维护树结构,方便查找
责任链
。细节可不深究,与本文无关func (engine *Engine) addRoute(method, path string, handlers HandlersChain) { assert1(path[0] == '/', "path must begin with '/'") assert1(method != "", "HTTP method can not be empty") assert1(len(handlers) > 0, "there must be at least one handler") debugPrintRoute(method, path, handlers) root := engine.trees.get(method) if root == nil { root = new(node) root.fullPath = "/" engine.trees = append(engine.trees, methodTree{method: method, root: root}) } root.addRoute(path, handlers) }
-
Engine.handleHTTPRequest ,执行
责任链
,看 2 行中文注释即可,细节可不深究,与本文无关
func (engine *Engine) handleHTTPRequest(c *Context) {
httpMethod := c.Request.Method
rPath := c.Request.URL.Path
unescape := false
if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 {
rPath = c.Request.URL.RawPath
unescape = engine.UnescapePathValues
}
rPath = cleanPath(rPath)
// Find root of the tree for the given HTTP method
t := engine.trees
for i, tl := 0, len(t); i < tl; i++ {
if t[i].method != httpMethod {
continue
}
root := t[i].root
// Find route in tree
value := root.getValue(rPath, c.Params, unescape) // 根据 URL 请求路径,获取`责任链`
if value.handlers != nil {
c.handlers = value.handlers
c.Params = value.params
c.fullPath = value.fullPath
c.Next() // 开始执行`责任链`
c.writermem.WriteHeaderNow()
return
}
if httpMethod != "CONNECT" && rPath != "/" {
if value.tsr && engine.RedirectTrailingSlash {
redirectTrailingSlash(c)
return
}
if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {
return
}
}
break
}
if engine.HandleMethodNotAllowed {
for _, tree := range engine.trees {
if tree.method == httpMethod {
continue
}
if value := tree.root.getValue(rPath, nil, unescape); value.handlers != nil {
c.handlers = engine.allNoMethod
serveError(c, http.StatusMethodNotAllowed, default405Body)
return
}
}
}
c.handlers = engine.allNoRoute
serveError(c, http.StatusNotFound, default404Body)
}
以上