Iris V1.12.0-alpha2 is currently used, which automatically parses the RequestBody into an object to inject into the parameters

Iris injection is divided into two parts:

  • Dependent source list ready

Register(dependencies… Interface {}). Global dependencies are only stored at registration time and injected after the request is received

Registration process:

  • Inject dependencies on request

Mvcap.register (), if passed in to a function, will perform the injection after receiving the request to the request

Dependency injection starts when a request is received, and tracing dependency injection starts at the request

Request process

  • The request is received and processed

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)

            // Match the route
            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.}}// There is no matching route
        if! filterExecuted {// If not at least one match filter found and executed,
            // then just run the router.
            router.requestHandler.HandleRequest(ctx)
        }

        // Release iris context
        cPool.Release(ctx)
    }

Copy the code
Route matching calls the default

router.api_builder.go.defaultPartyMatcher


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

    // The path before the variable, eg: /users/{uid} gets /usersstaticPath := 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)
}

Copy the code

As you can see, only matching paths does not match methods, which will be determined later when operating on the request

After the route is matched, the Iris context begins to call the interware

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)
}

Copy the code

This method is relatively simple, starting with the first call to the middleware, and then the middleware itself traverses the call

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(a) {
 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)

 }
}

Copy the code

If there’s a next one, there’s no next step, the job is done, and both the global middleware and the single request middleware are in the form of these two methods that trigger the chain call, and from the single request there’s actually a handler at the bottom of the chain, To the Router. BuildMainHandlerWithFilters. Handlers

Request context invocation

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


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

            // The context is reusable and the data needs to be reset
            // 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)
        })

Copy the code
The router. The requestHandler. HandleRequest is the starting point of a single request, iris judge whether routing fully meet here, method is correct

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() { ... Disable path correction, redirection, and other code elisions}// The route is matched again, starting with the nearest party and judging the parameters
    for i := range h.trees {
        t := h.trees[i]
        ifmethod ! = t.method {continue
        }
        ifh.hosts && ! canHandleSubdomain(ctx, t.subdomain) {continue
        }

        // Route tree traversal with parameters
        n := t.search(path, ctx.Params())
        ifn ! =nil {
            ctx.SetCurrentRoute(n.Route)
            ctx.Do(n.Handlers)
            // found
            return
        }
        // not found or method not allowed.
        break}...404Or other redirects to handle ctx.statusCode (http.statusNotFound)}Copy the code

A single request still runs its own middleware, usually JWT, casbin, etc

The first middleware chain of a single request is special to the last one, where the dependency injection and the result of the request will be distributed

hero.handler.go.makeHandler#Func2

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

  Bindings are ready when iris.application is initialized by the handler via getBindingsForFunc when the handler registers with iris.Application
  for _, binding := range bindings {
   input, err := binding.Dependency.Handle(ctx, binding.Input)
   iferr ! =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
  }

  // Call the controller's business logic
  outputs := v.Call(inputs)
  // Request result distribution processing
  iferr := dispatchFuncResult(ctx, outputs, resultHandler); err ! =nil {
   c.GetErrorHandler(ctx).HandleError(ctx, err)
  }
 }
Copy the code

As mentioned earlier, the injection module has two parts, one is initial initialization, the other is injection at request processing, here is injection. Registers are wrapped as Dependency, while injection is an implementation of Dependency.Handle. There are two final injections: instance object and function injection

By function injection

hero.dependency.go.fromDependencyFunc

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

  // Processing of function entry parameters
  for _, binding := range bindings {
   // The parameters needed for each injection are still injected objects, until there is no need to pass parameters from outside or static injection
   input, err := binding.Dependency.Handle(ctx, binding.Input)
   iferr ! =nil {
    if err == ErrSeeOther {
     continue
    }

    return emptyValue, err
   }

   inputs[binding.Input.Index] = input
  }

  // Call the function
  outputs := v.Call(inputs)
  if firstOutIsError {
   return emptyValue, toError(outputs[0])}else if secondOutIsError {
   return outputs[0], toError(outputs[1])}// Register allows two parameters, one is the return value and the other is error. The return value is the object we want to inject.
  return outputs[0].nil
 }
Copy the code

The Dependency.Handle is recursed so that each function parameter is properly entered, Mvcapp.register (func (val B) A) mvcapp.register (func (val A) B) this is not allowed, but what happens if this operation is actually done? The first injection function needs to be retrieved from the user’s RequestBody since B is not in its injection list and has no place to be generated

Statically injected fetching is simpler because it is wrapped, so there is only one line of code:

hero.dependency.go.fromStructValue

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

Copy the code
RequestBody injection

hero.binding.go.payloadBinding

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

    ifserveDepsV := 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
   }

Copy the code

After the handler loops and recurses, the injection of the function is complete. But the question in mind is when is the Controller property assigned?

Redebugging shows that Controller bindings are injected into the list, which is a member of bindings (first) and handled in struct

Handling of 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()
 ifctrl.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)
   iferr ! =nil {
    if err == ErrSeeOther {
     continue
    }

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

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

   // Inject data into struct
   elem.FieldByIndex(b.Input.StructFieldIndex).Set(input)
  }
 }

 return ctrl, nil
}

Copy the code

Now that the injection is complete, how are bindings, which are important for injection, initialized?

Dependent source list ready

NewDependency in the mvcapp.register method

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) ||    // This is a copy of DependencyHandler
  fromStructValue(v, dest) ||    // Static injection
  fromFunc(v, dest) ||    // Function injection. The first input parameter is context and has 1 or 2 inputs
  len(funcDependencies) > 0 && fromDependentFunc(v, dest, funcDependencies) // Function injection
}

Copy the code
The binding of obtaining

Either func or struct, this function is called to get bindings that it needs in addition to its own logic

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
 ifparamsCount ! =- 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(a) 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)
     iferr ! =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
  }

  // The dependency source cannot be found after traversing all the dependencies passed in
  if prevN == len(bindings) {
   // Basic types such as int string can be path parameters
   if canBePathParameter {
    // no new dependency added for this input,
    // let's check for path parameters.
    bindings = append(bindings, paramBinding(i, getParamIndex(), in))
    continue
   }

   // Do not get them from the 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
}

Copy the code
Route parsing in 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.
  // Some information about the controller is stored here, which can be used to generate the Controller object when the request comes in
 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 {
  ifopt ! =nil {
   opt.Apply(c)
  }
 }

 // The route is parsed and registered in iris.app, which generates a new router
 c.activate()

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

  // Add mvcApplication to the Controllers list
 app.Controllers = append(app.Controllers, c)
 return c
}

Copy the code

Iris initialization and request:

summary

  1. Iris MVC forPartyTo encapsulate, toPartycallHandler()registeredControllerWill be convertedAPIBuilder, generate a list of routers, each Router will contain its path information, controller, superior information, middleware, etc
  2. callapp.ListenWhen will iris be rightAPIBuilderSome information about the router is added (e.gmainHandler),iris.New()Is generated whenRouterAs the vertex of the tree, it will be set to handle requests when they arrive
  3. When the request arrives, the Router will be matched from the root Router to find the Router that best matches the path. The Router will then process the request. The path and request mode will be matched again before the business logic to find the final business logic processor
  4. Dependency injection’s dependency sources are an ordered list. Later registered dependencies can reference previously registered dependencies, but cannot reference later registered dependencies. Referencing unregistered dependencies may result in null Pointers or request resolution exceptions
  5. The core of IRIS should be middleware. Request processing of IRIS is accomplished through different middleware, including dependency injection, parameter parsing, request processing, etc