zl程序教程

您现在的位置是:首页 >  其他

当前栏目

「Go框架」剖析iris中错误码路由的运行机制

2023-02-26 09:48:49 时间

大家好,我是渔夫子。本号新推出「Go工具箱」系列,意在给大家分享使用go语言编写的、实用的、好玩的工具。同时了解其底层的实现原理,以便更深入地了解Go语言。

在iris中,除了能够指定正常的请求路由外,还能根据http的响应错误码指定具体的请求处理函数,以便针对具体的错误做出不同的响应。例如,当响应状态码是400时,该如何处理该请求;当响应状态码是500时,又该如何处理该请求。

本文就iris框架中错误码路由的运行机制做一个深入的剖析。

一、错误码路由Demo

我们先来看下,在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请求处理流程

首先,我们看下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)
 }
}

这里的处理逻辑也很清晰了。

  • 首先从上下文池中获取一个上下文ctx
  • 处理正常的请求。包括路由匹配、执行具体的请求处理函数、设置响应码。
  • 释放上下文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文档。