iris请求注入流程浅析

目前使用的是iris v1.12.0-alpha2,可以自动解析RequestBody成对象注入到参数中

iris的注入分成两个部分:

  • 依赖源列表准备

全局依赖是通过 mvcApp.Register(dependencies ...interface{}) 注入的,全局依赖在注册的时候只是保存依赖列表,接收到请求以后才进行注入

注册流程:

513602390.png

  • 请求时注入依赖

包括 iris.Context 在内的单个请求范围内的数据,mvcApp.Register() 如果是传入函数的,会在接收请求到请求以后再执行函数进行注入

依赖注入是在接收到请求的时候开始执行的,跟踪依赖注入要从请求处开始看

597581593.png

请求时注入依赖关键代码

  • 收到请求进行处理

core.router.router.go.Router.buildMainHandlerWithFilters()#router.mainHandler


    router.mainHandler = func(w http.ResponseWriter, r *http.Request) {
        ctx := cPool.Acquire(w, r)
        filterExecuted := false
        for _, f := range sortedFilters { // from subdomain, largest path to shortest.
            // fmt.Printf("Sorted filter execution: [%s] [%s]\n", f.Subdomain, f.Path)

            // 匹配路由
            if f.Matcher.Match(ctx) {
                // fmt.Printf("Matched [%s] and execute [%d] handlers [%s]\n\n", ctx.Path(), len(f.Handlers), context.HandlersNames(f.Handlers))
                filterExecuted = true
                // execute the final handlers chain.
                ctx.Do(f.Handlers)
                break // and break on first found.
            }
        }

        // 没有匹配的路由,进
        if !filterExecuted {
            // If not at least one match filter found and executed,
            // then just run the router.
            router.requestHandler.HandleRequest(ctx)
        }

        // 释放iris上下文 
        cPool.Release(ctx)
    }

复制代码
路由匹配会调用默认的

router.api_builder.go.defaultPartyMatcher


func defaultPartyMatcher(ctx *context.Context, p Party) bool {
    subdomain, path := splitSubdomainAndPath(p.GetRelPath())

    // 变量前的路径,eg: /users/{uid} 得到/users
    staticPath := staticPath(path)
    hosts := subdomain != ""
    if p.IsRoot() {
        // ALWAYS executed first when registered
        // through an `Application.UseRouter` call.
        return true
    }
    if hosts {
        // Note(@kataras): do NOT try to implement something like party matcher for each party
        // separately. We will introduce a new problem with subdomain inside a subdomain:
        // they are not by prefix, so parenting calls will not help
        // e.g. admin. and control.admin, control.admin is a sub of the admin.
        if !canHandleSubdomain(ctx, subdomain) {
            return false
        }
    }
    // this is the longest static path.
    return strings.HasPrefix(ctx.Path(), staticPath)
}

复制代码

这里可以看出,仅匹配路径不匹配方法,方法将在后面对请求进行操作的时候判断

匹配到路由之后,iris上下文开始调用中件间

context.context.go.Context.Do

// Do sets the "handlers" as the chain
// and executes the first handler,
// handlers should not be empty.
//
// It's used by the router, developers may use that
// to replace and execute handlers immediately.
func (ctx *Context) Do(handlers Handlers) {
    if len(handlers) == 0 {
        return
    }
    ctx.handlers = handlers
    handlers[0](ctx)
}

复制代码

这个方法比较简单,就是从第一个开始调用中间件,之后就是中间件自行遍历调用

context.context.go.Context.Next

// Next calls the next handler from the handlers chain,
// it should be used inside a middleware.
func (ctx *Context) Next() {
 if ctx.IsStopped() {
  return
 }

 nextIndex := ctx.currentHandlerIndex + 1
 handlers := ctx.handlers

 if n := len(handlers); nextIndex == n {
  atomic.StoreUint32(&ctx.proceeded, 1) // last handler but Next is called.
 } else if nextIndex < n {
  ctx.currentHandlerIndex = nextIndex
  handlers[nextIndex](ctx)

 }
}

复制代码

如果有下一个则执行,没有下一步,那活也干完了,不管是全局中间件还是单个请求中间件都是这两个方法的形式在触发链式调用,从单个请求实际上也是以一个handler形式存在于这个链的底部,即为 Router.buildMainHandlerWithFilters.Handlers

请求上下文调用

core.router.router.go.Router.buildMainHandlerWithFilters#f.handlers


        f.Handlers = append(f.Handlers, func(ctx *context.Context) {

            // 上下文是复用的,数据需要重置
            // set the handler index back to 0 so the route's handlers can be executed as expected.
            ctx.HandlerIndex(0)
            // execute the main request handler, this will fire the found route's handlers
            // or if error the error code's associated handler.
            router.requestHandler.HandleRequest(ctx)
        })

复制代码
router.requestHandler.HandleRequest便是单个请求的起点,iris在这里判断路由是否完全符合,方法是否正确

core.router.handler.go.routerHandler.HandlerRequest


func (h *routerHandler) HandleRequest(ctx *context.Context) {
    method := ctx.Method()
    path := ctx.Path()
    config := h.config // ctx.Application().GetConfigurationReadOnly()
    if !config.GetDisablePathCorrection() {
        ... 禁用路径校正,重定向等代码省略
    }

    // 再次匹配路由,此时的匹配会从最近的party开始,并且会对参数进行判断
    for i := range h.trees {
        t := h.trees[i]
        if method != t.method {
            continue
        }
        if h.hosts && !canHandleSubdomain(ctx, t.subdomain) {
            continue
        }

        // 带参数的路径判断,路由树的遍历
        n := t.search(path, ctx.Params())
        if n != nil {
            ctx.SetCurrentRoute(n.Route)
            ctx.Do(n.Handlers)
            // found
            return
        }
        // not found or method not allowed.
        break
    }

    ... 404 或其他重定向的处理

    ctx.StatusCode(http.StatusNotFound)
}

复制代码

单个请求还是要跑自己的中间件,通常jwt、casbin等会在这里调用

单个请求的中间件链第一个跟最后一个比较特殊,第一个略过看最后一个,依赖的注入和请求结果将在这里进行分发

hero.handler.go.makeHandler#Func2

 return func(ctx *context.Context) {
  inputs := make([]reflect.Value, numIn)

  // bindings是在iris.Application初始化的时候就已经准备好了,handler在注册到iris.Application的时候通过getBindingsForFunc初始化了
  for _, binding := range bindings {
   input, err := binding.Dependency.Handle(ctx, binding.Input)
   if err != nil {
    if err == ErrSeeOther {
     continue
    }

    c.GetErrorHandler(ctx).HandleError(ctx, err)
    
    // return [13 Sep 2020, commented that in order to be able to
    // give end-developer the option not only to handle the error
    // but to skip it if necessary, example:
    // read form, unknown field, continue without StopWith,
    // the binder should bind the method's input argument and continue
    // without errors. See `mvc.TestErrorHandlerContinue` test.]
   }

   // If ~an error status code is set or~ execution has stopped
   // from within the dependency (something went wrong while validating the request),
   // then stop everything and let handler fire that status code.
   if ctx.IsStopped() {
    return
   }

   inputs[binding.Input.Index] = input
  }

  // 调用controller的业务逻辑
  outputs := v.Call(inputs)
  // 请求结果分发处理
  if err := dispatchFuncResult(ctx, outputs, resultHandler); err != nil {
   c.GetErrorHandler(ctx).HandleError(ctx, err)
  }
 }
复制代码

篇头说过,注入模块有两部分,一部分是一开始的初始化,一部分是请求处理时的注入,这里的是注入。
Register的东西都被封装成Dependency,而注入是Dependency.Handle的执行,最终的注入有两种: 实例对象和函数注入

通过函数注入

hero.dependency.go.fromDependencyFunc

 handler := func(ctx *context.Context, _ *Input) (reflect.Value, error) {
  inputs := make([]reflect.Value, numIn)

  // 函数入参的处理
  for _, binding := range bindings {
   // 每个注入里面所需要的参数依然还是注入对象,直到不需要从外面传参数或者是静态注入
   input, err := binding.Dependency.Handle(ctx, binding.Input)
   if err != nil {
    if err == ErrSeeOther {
     continue
    }

    return emptyValue, err
   }

   inputs[binding.Input.Index] = input
  }

  // 调用函数
  outputs := v.Call(inputs)
  if firstOutIsError {
   return emptyValue, toError(outputs[0])
  } else if secondOutIsError {
   return outputs[0], toError(outputs[1])
  }

  // 从这里可以看出,Register的函数允许两个参数,一个是返回值,一个是error,返回值也就是我们要注入的对象,
  return outputs[0], nil
 }
复制代码

这里会递归 Dependency.Handle 以便使每个函数的参数都能得到正确的入参,但这里也可以得到一个结论是 mvcApp.Register(func (val B) A) mvcApp.Register(func (val A) B) 这是不允许的,但如果真的进行了这样的操作会发生什么事情?第一个注入函数由于B没有在它的注入列表中,也没地方生成,所以它需要从用户的RequestBody中获取,为什么会这样需要去看一下iris初始化handler注入源列表是怎样做的

静态注入的获取比较简单,因为经过了封装了,于是只有一行代码:

hero.dependency.go.fromStructValue

  handler := func(*context.Context, *Input) (reflect.Value, error) {
   return v, nil
  }

复制代码
RequestBody注入

hero.binding.go.payloadBinding

Handle: func(ctx *context.Context, input *Input) (newValue reflect.Value, err error) {
    wasPtr := input.Type.Kind() == reflect.Ptr

    if serveDepsV := ctx.Values().Get(context.DependenciesContextKey); serveDepsV != nil {
     if serveDeps, ok := serveDepsV.(context.DependenciesMap); ok {
      if newValue, ok = serveDeps[typ]; ok {
       return
      }
     }
    }

    if input.Type.Kind() == reflect.Slice {
     newValue = reflect.New(reflect.SliceOf(indirectType(input.Type)))
    } else {
     newValue = reflect.New(indirectType(input.Type))
    }

    ptr := newValue.Interface()
    err = ctx.ReadBody(ptr)
    if !wasPtr {
     newValue = newValue.Elem()
    }

    return
   }

复制代码

经过handler的循环和递归,对函数的注入就完成了。但此时心中有个疑惑是 Controller 的属性又是在什么时候被赋值的?

再次调试发现,注入的列表中会对Controller进行注入,是bindings的一份子(第1个),对它的处理是在struct中

对controller的处理

hero.struct.go.Struct.Acquire


// Acquire returns a struct value based on the request.
// If the dependencies are all static then these are already set-ed at the initialization of this Struct
// and the same struct value instance will be returned, ignoring the Context. Otherwise
// a new struct value with filled fields by its pre-calculated bindings will be returned instead.
func (s *Struct) Acquire(ctx *context.Context) (reflect.Value, error) {
 if s.Singleton {
  ctx.Values().Set(context.ControllerContextKey, s.ptrValue)
  return s.ptrValue, nil
 }

 ctrl := ctx.Controller()
 if ctrl.Kind() == reflect.Invalid ||
  ctrl.Type() != s.ptrType /* in case of changing controller in the same request (see RouteOverlap feature) */ {
  ctrl = reflect.New(s.elementType)
  ctx.Values().Set(context.ControllerContextKey, ctrl)
  elem := ctrl.Elem()
  for _, b := range s.bindings {
   input, err := b.Dependency.Handle(ctx, b.Input)
   if err != nil {
    if err == ErrSeeOther {
     continue
    }

    s.Container.GetErrorHandler(ctx).HandleError(ctx, err)

    if ctx.IsStopped() {
     // return emptyValue, err
     return ctrl, err
    } // #1629
   }

   // 注入数据到struct
   elem.FieldByIndex(b.Input.StructFieldIndex).Set(input)
  }
 }

 return ctrl, nil
}

复制代码

至此已经完成了注入了,但是对于注入很重要的bindings又是如何初始化的?

依赖源列表准备

mvcApp.Register方法内的的NewDependency

hero.dependency.NewDependency


// NewDependency converts a function or a function which accepts other dependencies or static struct value to a *Dependency.
//
// See `Container.Handler` for more.
func NewDependency(dependency interface{}, funcDependencies ...*Dependency) *Dependency {
    if dependency == nil {
        panic(fmt.Sprintf("bad value: nil: %T", dependency))
    }
    if d, ok := dependency.(*Dependency); ok {
        // already a *Dependency.
        return d
    }
    v := valueOf(dependency)
    if !goodVal(v) {
        panic(fmt.Sprintf("bad value: %#+v", dependency))
    }
    dest := &Dependency{
        Source: newSource(v),
        OriginalValue: dependency,
    }
    if !resolveDependency(v, dest, funcDependencies...) {
        panic(fmt.Sprintf("bad value: could not resolve a dependency from: %#+v", dependency))
    }
    return dest
}



func resolveDependency(v reflect.Value, dest *Dependency, funcDependencies ...*Dependency) bool {
 return fromDependencyHandler(v, dest) ||    // 本来就是DependencyHandler的拷贝封装
  fromStructValue(v, dest) ||    // 静态注入
  fromFunc(v, dest) ||    // 函数注入,第一个入参是context,且入参为1或2个
  len(funcDependencies) > 0 && fromDependentFunc(v, dest, funcDependencies) // 函数注入
}

复制代码
binding的获取

不管是func还是struct,除了自身的逻辑,还会调用这个函数来获取到他所需要的bindings

hero.binding.go.getBindingsFor


func getBindingsFor(inputs []reflect.Type, deps []*Dependency, paramsCount int) (bindings []*binding) {
 // Path parameter start index is the result of [total path parameters] - [total func path parameters inputs],
 // moving from last to first path parameters and first to last (probably) available input args.
 //
 // That way the above will work as expected:
 // 1. mvc.New(app.Party("/path/{firstparam}")).Handle(....Controller.GetBy(secondparam string))
 // 2. mvc.New(app.Party("/path/{firstparam}/{secondparam}")).Handle(...Controller.GetBy(firstparam, secondparam string))
 // 3. usersRouter := app.Party("/users/{id:uint64}"); usersRouter.ConfigureContainer().Handle(method, "/", handler(id uint64))
 // 4. usersRouter.Party("/friends").ConfigureContainer().Handle(method, "/{friendID:uint64}", handler(friendID uint64))
 //
 // Therefore, count the inputs that can be path parameters first.
 shouldBindParams := make(map[int]struct{})
 totalParamsExpected := 0
 if paramsCount != -1 {
  for i, in := range inputs {
   if _, canBePathParameter := context.ParamResolvers[in]; !canBePathParameter {
    continue
   }
   shouldBindParams[i] = struct{}{}

   totalParamsExpected++
  }
 }

 startParamIndex := paramsCount - totalParamsExpected
 if startParamIndex < 0 {
  startParamIndex = 0
 }

 lastParamIndex := startParamIndex

 getParamIndex := func() int {
  paramIndex := lastParamIndex
  lastParamIndex++
  return paramIndex
 }

 bindedInput := make(map[int]struct{})

 for i, in := range inputs { //order matters.
  _, canBePathParameter := shouldBindParams[i]

  prevN := len(bindings) // to check if a new binding is attached; a dependency was matched (see below).

  for j := len(deps) - 1; j >= 0; j-- {
   d := deps[j]
   // Note: we could use the same slice to return.
   //
   // Add all dynamic dependencies (caller-selecting) and the exact typed dependencies.
   //
   // A dependency can only be matched to 1 value, and 1 value has a single dependency
   // (e.g. to avoid conflicting path parameters of the same type).
   if _, alreadyBinded := bindedInput[j]; alreadyBinded {
    continue
   }

   match := matchDependency(d, in)
   if !match {
    continue
   }

   if canBePathParameter {
    // wrap the existing dependency handler.
    paramHandler := paramDependencyHandler(getParamIndex())
    prevHandler := d.Handle
    d.Handle = func(ctx *context.Context, input *Input) (reflect.Value, error) {
     v, err := paramHandler(ctx, input)
     if err != nil {
      v, err = prevHandler(ctx, input)
     }

     return v, err
    }
    d.Static = false
    d.OriginalValue = nil
   }

   bindings = append(bindings, &binding{
    Dependency: d,
    Input: newInput(in, i, nil),
   })

   if !d.Explicit { // if explicit then it can be binded to more than one input
    bindedInput[j] = struct{}{}
   }

   break
  }

  // 遍历了所有的传进来的依赖源都无法找到对应的依赖源,判断为需要解析
  if prevN == len(bindings) {
   // int string等基础类型可以为path参数,优先进行判断
   if canBePathParameter {
    // no new dependency added for this input,
    // let's check for path parameters.
    bindings = append(bindings, paramBinding(i, getParamIndex(), in))
    continue
   }

   // 都不是就从RequestBody获取
   // else add builtin bindings that may be registered by user too, but they didn't.
   if isPayloadType(in) {
    bindings = append(bindings, payloadBinding(i, in))
    continue
   }
  }
 }

 return
}

复制代码
Controller中路由的解析

application.Handler -> application.handler -> c := newControllerActivator -> c.activite() -> c.parseMethods(); c.parseHTTPErrorHandler() -> parseMethod -> ControllerActivitor.Handle

mvc.mvc.go.Application.handle

func (app *Application) handle(controller interface{}, options ...Option) *ControllerActivator {
 // initialize the controller's activator, nothing too magical so far.
  // 这里会保存下controller的一些信息,请求来的时候可以利用这些信息反射生成controller对象
 c := newControllerActivator(app, controller)

 // check the controller's "BeforeActivation" or/and "AfterActivation" method(s) between the `activate`
 // call, which is simply parses the controller's methods, end-dev can register custom controller's methods
 // by using the BeforeActivation's (a ControllerActivation) `.Handle` method.
 if before, ok := controller.(interface {
  BeforeActivation(BeforeActivation)
 }); ok {
  before.BeforeActivation(c)
 }

 for _, opt := range options {
  if opt != nil {
   opt.Apply(c)
  }
 }

 // 解析路由,注册到iris.app中,这时候会生成新的router
 c.activate()

 if after, okAfter := controller.(interface {
  AfterActivation(AfterActivation)
 }); okAfter {
  after.AfterActivation(c)
 }

  //  添加到 mvcApplication的Controllers列表
 app.Controllers = append(app.Controllers, c)
 return c
}

复制代码

iris初始化和请求:

595049671.png

小结

  1. iris mvc对Party进行封装,往Party调用Handler()注册Controller的时候会被转换APIBuilder,生成Router列表,每个Router会包含有其路径信息、controller、上级信息、中间件等
  2. 调用 app.Listen 的时候将iris对APIBuilder进行处理,router的一些信息会得到补充(如mainHandler),iris.New()的时候生成的Router做为树的顶点,将会被设置成请求到来时的处理服务
  3. 请求到来的时候,从根Router开始进行匹配找到最能符合路径的Router,Router再进行处理,到业务逻辑前还会再进行一次路径匹配和请求方式匹配来找到最终的业务逻辑处理器
  4. 依赖注入的依赖源是一个有序列表,后面注册的依赖可以引用之前注册的依赖,但不能引用之后注册的依赖,当引用尚未注册的依赖可能会导致空指针或请求解析异常
  5. iris最核心的应该是中间件,在iris的请求处理中,都是通过不同的中间件完成,包括依赖注入、参数解析、请求处理等
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享