大家好,我是渔夫子。本号新推出「Go工具箱」系列,意在给大家分享使用go语言编写的、实用的、好玩的工具。同时了解其底层的实现原理,以便更深入地了解Go语言。
在iris中,除了能够指定正常的请求路由外,还能根据http的响应错误码指定具体的请求处理函数,以便针对具体的错误做出不同的响应。例如,当响应状态码是400时,该如何处理该请求;当响应状态码是500时,又该如何处理该请求。
本文就iris框架中错误码路由的运行机制做一个深入的剖析。
我们先来看下,在iris中是如何给特定的响应状态码指定对应的路由函数的。如下:
app := iris.New()
// 捕获特定的错误码。iris.StatusInternalSErverError=500
app.OnErrorCode(iris.StatusInternalServerError, func(ctx iris.Context) {
ctx.HTML("Message: <b>" + ctx.Values().GetString("message") + "</b>")
})
app.Get("/my500", func(ctx iris.Context) {
ctx.Values().Set("message", "this is the error message")
ctx.StatusCode(500)
})
// http://localhost:8080/my500
app.Listen(":8080")
我们运行服务,然后访问 http://localhost:8080/my500时,会输出 "Message:** this is the error message**"
。我们发现,该路径是先执行 "/my500" 对应的处理函数,然后设置错误码是500,然后再执行到了app.OnErrorCode对应的处理函数中。
接下来我们就分析下iris是如何捕获到请求处理函数中对应的错误码的。
通过app.OnErrorCode可以对指定的错误码进行路由注册。根据上文讲解的iris路由的结构,在routerHandler中,不仅有正常的路由表,而且还有一个专门用于错误处理的路由表字段:errorTrees,如下:
在服务启动前,使用app.OnErrorCode
进行错误码路由注册,如下:
app.OnErrorCode(iris.StatusInternalServerError, func(ctx iris.Context) {
ctx.HTML("Message: <b>" + ctx.Values().GetString("message") + "</b>")
})
以上注册的路由,最终生成的路由树如下:
在iris中错误码路由和正常的路由树是分开在两个字段存储的。下面接着看iris是如何利用这两棵路由树的。
首先,我们看下iris框架对请求的整体处理流程。如下图:
在之前的文章中我们详细讲解过go的常用web框架对http请求的本质都是调用标准库中的net/http包中的Server结构体。具体可参考 通过分析gin、beego源码,读懂web框架对http请求处理流程的本质。
iris框架也不例外,在通过Listen函数启动服务的逻辑中,给Server.Handler指定了router.Router作为对应的请求处理入口。
同时,router.Router又实现了ServeHTTP接口。在Router.ServeHTTP函数中调用Router.mainHandler方法。如下:
func (router *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
router.mainHandler(w, r)
}
而Router.mainHandler方法的实现如下:
func (router *Router) buildMainHandler(cPool *context.Pool, requestHandler RequestHandler) {
router.mainHandler = func(w http.ResponseWriter, r *http.Request) {
ctx := cPool.Acquire(w, r)
router.requestHandler.HandleRequest(ctx)
cPool.Release(ctx)
}
}
这里的处理逻辑也很清晰了。
我们接着看cPool.Release(ctx)这段的逻辑,其实现如下:
func (c *Pool) Release(ctx *Context) {
if !ctx.manualRelease {
ctx.EndRequest()
c.pool.Put(ctx)
}
}
这里有一行是 ctx.EndRequest(),继续看该函数的实现,如下:
func (ctx *Context) EndRequest() {
if !ctx.app.ConfigurationReadOnly().GetDisableAutoFireStatusCode() &&
StatusCodeNotSuccessful(ctx.GetStatusCode()) {
ctx.app.FireErrorCode(ctx)
}
ctx.writer.FlushResponse()
ctx.writer.EndResponse()
}
在该函数中,其中会判断如果当前输出的状态码不是成功状态(即200),那么就执行ctx.app.FireErrorCode(ctx)。咱们再继续看该函数的实现,如下:
func (h *routerHandler) FireErrorCode(ctx *context.Context) {
...
statusCode := ctx.GetStatusCode() // the response's cached one.
...
for i := range h.errorTrees {
t := h.errorTrees[i]
if statusCode != t.statusCode {
continue
}
...
n := t.search(ctx.Path(), ctx.Params())
if n == nil {
// try to take the root's one.
n = t.root.getChild(pathSep)
}
if n != nil {
ctx.SetCurrentRoute(n.Route)
ctx.HandlerIndex(0)
ctx.Do(n.Handlers)
return
}
break
}
ctx.Do(h.errorDefaultHandlers)
}
在该函数中,我们看到会从routerHandler的errorTrees中进行匹配路由,并执行对应的请求处理逻辑。这里正好就是在一开始的时候根据状态码注册的路由。
好了,以上就是咱们几天要介绍的内容,希望对大家理解iris框架有所帮助。
---特别推荐---
特别推荐:一个专注go项目实战、项目中踩坑经验及避坑指南、各种好玩的go工具的公众号,「Go学堂」,专注实用性,非常值得大家关注。点击下方公众号卡片,直接关注。关注送《100个go常见的错误》pdf文档。