diff --git a/_benchmarks/iris-mvc/main.go b/_benchmarks/iris-mvc/main.go index 77743dff..71719eb2 100644 --- a/_benchmarks/iris-mvc/main.go +++ b/_benchmarks/iris-mvc/main.go @@ -13,7 +13,9 @@ import ( func main() { app := iris.New() - mvc.New(app.Party("/api/values/{id}")).Register(new(controllers.ValuesController)) + mvc.New(app.Party("/api/values/{id}")). + Register(new(controllers.ValuesController)) + app.Run(iris.Addr(":5000"), iris.WithoutVersionChecker) } diff --git a/mvc/controller.go b/mvc/controller.go index 9f95fed9..e578c28b 100644 --- a/mvc/controller.go +++ b/mvc/controller.go @@ -35,7 +35,7 @@ type BeforeActivation interface { type AfterActivation interface { shared DependenciesReadOnly() di.ValuesReadOnly - IsRequestScoped() bool + Singleton() bool } var ( @@ -85,14 +85,10 @@ func newControllerActivator(router router.Party, controller interface{}, depende val = reflect.ValueOf(controller) typ = val.Type() - // the full name of the controller, it's its type including the package path. + // the full name of the controller: its type including the package path. fullName = getNameOf(typ) ) - // add the manual filled fields to the dependencies. - filledFieldValues := di.LookupNonZeroFieldsValues(val) - dependencies.AddValue(filledFieldValues...) - c := &ControllerActivator{ // give access to the Router to the end-devs if they need it for some reason, // i.e register done handlers. @@ -141,26 +137,16 @@ func (c *ControllerActivator) Router() router.Party { return c.router } -// IsRequestScoped returns new if each request has its own instance -// of the controller and it contains dependencies that are not manually -// filled by the struct initialization from the caller. -func (c *ControllerActivator) IsRequestScoped() bool { - // if the c.injector == nil means that is not seted to invalidate state, - // so it contains more fields that are filled by the end-dev. - // This "strange" check happens because the `IsRequestScoped` may - // called before the controller activation complete its task (see Handle: if c.injector == nil). - if c.injector == nil { // is nil so it contains more fields, maybe request-scoped or dependencies. - return true +// Singleton returns new if all incoming clients' requests +// have the same controller instance. +// This is done automatically by iris to reduce the creation +// of a new controller on each request, if the controller doesn't contain +// any unexported fields and all fields are services-like, static. +func (c *ControllerActivator) Singleton() bool { + if c.injector == nil { + panic("MVC: IsRequestScoped used on an invalid state the API gives access to it only `AfterActivation`, report this as bug") } - if c.injector.Valid { - // if injector is not nil: - // if it is !Valid then all fields are manually filled by the end-dev (see `newControllerActivator`). - // if it is Valid then it's filled on the first `Handle` - // and it has more than one valid dependency from the registered values. - return true - } - // it's not nil and it's !Valid. - return false + return c.injector.State == di.Singleton } // checks if a method is already registered. @@ -191,21 +177,16 @@ func (c *ControllerActivator) parseMethod(m reflect.Method) { // register all available, exported methods to handlers if possible. func (c *ControllerActivator) parseMethods() { n := c.Type.NumMethod() - // wg := &sync.WaitGroup{} - // wg.Add(n) for i := 0; i < n; i++ { m := c.Type.Method(i) c.parseMethod(m) } - // wg.Wait() } func (c *ControllerActivator) activate() { c.parseMethods() } -var emptyIn = []reflect.Value{} - // Handle registers a route based on a http method, the route's path // and a function name that belongs to the controller, it accepts // a forth, optionally, variadic parameter which is the before handlers. @@ -221,6 +202,30 @@ func (c *ControllerActivator) Handle(method, path, funcName string, middleware . return nil } + // Remember: + // we cannot simply do that and expect to work: + // hasStructInjector = c.injector != nil && c.injector.Valid + // hasFuncInjector = funcInjector != nil && funcInjector.Valid + // because + // the `Handle` can be called from `BeforeActivation` callbacks + // and before activation, the c.injector is nil because + // we may not have the dependencies binded yet. But if `c.injector.Valid` + // inside the Handelr works because it's set on the `activate()` method. + // To solve this we can make check on the FIRST `Handle`, + // if c.injector is nil, then set it with the current bindings, + // so the user should bind the dependencies needed before the `Handle` + // this is a logical flow, so we will choose that one -> + if c.injector == nil { + // first, set these bindings to the passed controller, they will be useless + // if the struct contains any dynamic value because this controller will + // be never fired as it's but we make that in order to get the length of the static + // matched dependencies of the struct. + c.injector = di.MakeStructInjector(c.Value, hijacker, typeChecker, c.dependencies...) + if c.injector.HasFields { + golog.Debugf("MVC dependencies of '%s':\n%s", c.fullName, c.injector.String()) + } + } + // get the method from the controller type. m, ok := c.Type.MethodByName(funcName) if !ok { @@ -238,77 +243,17 @@ func (c *ControllerActivator) Handle(method, path, funcName string, middleware . return nil } - // add this as a reserved method name in order to - // be sure that the same func will not be registered again, even if a custom .Handle later on. - c.reservedMethods = append(c.reservedMethods, funcName) - // get the function's input. funcIn := getInputArgsFromFunc(m.Type) - // get the path parameters bindings from the template, // use the function's input except the receiver which is the // end-dev's controller pointer. pathParams := getPathParamsForInput(tmpl.Params, funcIn[1:]...) // get the function's input arguments' bindings. funcDependencies := c.dependencies.Clone() - funcDependencies.AddValue(pathParams...) + funcDependencies.AddValues(pathParams...) - // fmt.Printf("for %s | values: %s\n", funcName, funcDependencies) - funcInjector := di.MakeFuncInjector(m.Func, hijacker, typeChecker, funcDependencies...) - // fmt.Printf("actual injector's inputs length: %d\n", funcInjector.Length) - - // the element value, not the pointer, wil lbe used to create a - // new controller on each incoming request. - - // Remember: - // we cannot simply do that and expect to work: - // hasStructInjector = c.injector != nil && c.injector.Valid - // hasFuncInjector = funcInjector != nil && funcInjector.Valid - // because - // the `Handle` can be called from `BeforeActivation` callbacks - // and before activation, the c.injector is nil because - // we may not have the dependencies binded yet. But if `c.injector.Valid` - // inside the Handelr works because it's set on the `activate()` method. - // To solve this we can make check on the FIRST `Handle`, - // if c.injector is nil, then set it with the current bindings, - // so the user should bind the dependencies needed before the `Handle` - // this is a logical flow, so we will choose that one -> - if c.injector == nil { - // check if manually filled + any dependencies are only static, if so - // and the total struct's fields are equal these static dependencies length - // then we don't need to create a new struct on each request. - // - // We use our custom NumFields here because the std "reflect" package - // checks only for the current struct and not for embedded's exported fields. - totalFieldsLength := di.NumFields(di.IndirectType(c.Type)) - - // first, set these bindings to the passed controller, they will be useless - // if the struct contains any dynamic value because this controller will - // be never fired as it's but we make that in order to get the length of the static - // matched dependencies of the struct. - c.injector = di.MakeStructInjector(c.Value, hijacker, typeChecker, c.dependencies...) - matchedStaticDependenciesLength := c.injector.InjectElemStaticOnly(di.IndirectValue(c.Value)) - - if c.injector.Valid { - golog.Debugf("MVC dependencies of '%s':\n%s", c.fullName, c.injector.String()) - } - - if matchedStaticDependenciesLength == totalFieldsLength { - // all fields are filled by the end-developer or via static dependencies (if context is there then it will be filled by the MakeStructInjector so we don't worry about it), - // the controller doesn't contain any other field neither any dynamic binding as well. - // Therefore we don't need to create a new controller each time. - // Set the c.injector now instead on the first `Handle` and set it to invalid state - // in order to `buildControllerHandler` ignore the - // creation of a new controller value on each incoming request. - c.injector = &di.StructInjector{Valid: false} - } - } - - if funcInjector.Valid { - golog.Debugf("MVC dependencies of method '%s.%s':\n%s", c.fullName, funcName, funcInjector.String()) - } - - handler := buildControllerHandler(m, c.Type, c.Value, c.injector, funcInjector, funcIn) + handler := c.handlerOf(m, funcDependencies) // register the handler now. route := c.router.Handle(method, path, append(middleware, handler)...) @@ -316,98 +261,55 @@ func (c *ControllerActivator) Handle(method, path, funcName string, middleware . // change the main handler's name in order to respect the controller's and give // a proper debug message. route.MainHandlerName = fmt.Sprintf("%s.%s", c.fullName, funcName) + + // add this as a reserved method name in order to + // be sure that the same func will not be registered again, + // even if a custom .Handle later on. + c.reservedMethods = append(c.reservedMethods, funcName) } return route } -// buildControllerHandler has many many dublications but we do that to achieve the best -// performance possible, to use the information we know -// and calculate what is needed and what not in serve-time. -func buildControllerHandler(m reflect.Method, typ reflect.Type, initRef reflect.Value, structInjector *di.StructInjector, funcInjector *di.FuncInjector, funcIn []reflect.Type) context.Handler { - var ( - hasStructInjector = structInjector != nil && structInjector.Valid - hasFuncInjector = funcInjector != nil && funcInjector.Valid - - implementsBase = isBaseController(typ) - // we will make use of 'n' to make a slice of reflect.Value - // to pass into if the function has input arguments that - // are will being filled by the funcDependencies. - n = len(funcIn) - - elemTyp = di.IndirectType(typ) - ) - - // if it doesn't implement the base controller, - // it may have struct injector and/or func injector. - if !implementsBase { - - if !hasStructInjector { - // if the controller doesn't have a struct injector - // and the controller's fields are empty or all set-ed by the end-dev - // then we don't need a new controller instance, we use the passed controller instance. - - if !hasFuncInjector { - return func(ctx context.Context) { - DispatchFuncResult(ctx, initRef.Method(m.Index).Call(emptyIn)) - } - } - - return func(ctx context.Context) { - in := make([]reflect.Value, n, n) - in[0] = initRef - funcInjector.Inject(&in, reflect.ValueOf(ctx)) - if ctx.IsStopped() { - return - } - - DispatchFuncResult(ctx, m.Func.Call(in)) - } - } - - // it has struct injector for sure and maybe a func injector. - if !hasFuncInjector { - return func(ctx context.Context) { - ctrl := reflect.New(elemTyp) - ctxValue := reflect.ValueOf(ctx) - elem := ctrl.Elem() - structInjector.InjectElem(elem, ctxValue) - if ctx.IsStopped() { - return - } - - DispatchFuncResult(ctx, ctrl.Method(m.Index).Call(emptyIn)) - } - } - - // has struct injector and func injector. - return func(ctx context.Context) { - ctrl := reflect.New(elemTyp) - ctxValue := reflect.ValueOf(ctx) - - elem := ctrl.Elem() - structInjector.InjectElem(elem, ctxValue) - if ctx.IsStopped() { - return - } - - in := make([]reflect.Value, n, n) - in[0] = ctrl - funcInjector.Inject(&in, ctxValue) - if ctx.IsStopped() { - return - } - - DispatchFuncResult(ctx, m.Func.Call(in)) - } +var emptyIn = []reflect.Value{} +func (c *ControllerActivator) handlerOf(m reflect.Method, funcDependencies []reflect.Value) context.Handler { + // fmt.Printf("for %s | values: %s\n", funcName, funcDependencies) + funcInjector := di.MakeFuncInjector(m.Func, hijacker, typeChecker, funcDependencies...) + // fmt.Printf("actual injector's inputs length: %d\n", funcInjector.Length) + if funcInjector.Valid { + golog.Debugf("MVC dependencies of method '%s.%s':\n%s", c.fullName, m.Name, funcInjector.String()) } - // if implements the base controller, - // it may have struct injector and func injector as well. - return func(ctx context.Context) { - ctrl := reflect.New(elemTyp) + var ( + implementsBase = isBaseController(c.Type) + hasBindableFields = c.injector.CanInject + hasBindableFuncInputs = funcInjector.Valid + call = m.Func.Call + ) + + if !implementsBase && !hasBindableFields && !hasBindableFuncInputs { + return func(ctx context.Context) { + DispatchFuncResult(ctx, call(c.injector.NewAsSlice())) + } + } + + n := m.Type.NumIn() + return func(ctx context.Context) { + var ( + ctrl = c.injector.New() + ctxValue reflect.Value + ) + + // inject struct fields first before the BeginRequest and EndRequest, if any, + // in order to be able to have access there. + if hasBindableFields { + ctxValue = reflect.ValueOf(ctx) + c.injector.InjectElem(ctrl.Elem(), ctxValue) + } + + // check if has BeginRequest & EndRequest, before try to bind the method's inputs. if implementsBase { // the Interface(). is faster than MethodByName or pre-selected methods. b := ctrl.Interface().(BaseController) @@ -422,36 +324,20 @@ func buildControllerHandler(m reflect.Method, typ reflect.Type, initRef reflect. defer b.EndRequest(ctx) } - if !hasStructInjector && !hasFuncInjector { - DispatchFuncResult(ctx, ctrl.Method(m.Index).Call(emptyIn)) - } else { - ctxValue := reflect.ValueOf(ctx) - if hasStructInjector { - elem := ctrl.Elem() - structInjector.InjectElem(elem, ctxValue) - if ctx.IsStopped() { - return - } - - // we do this in order to reduce in := make... - // if not func input binders, we execute the handler with empty input args. - if !hasFuncInjector { - DispatchFuncResult(ctx, ctrl.Method(m.Index).Call(emptyIn)) - } - } - // otherwise, it has one or more valid input binders, - // make the input and call the func using those. - if hasFuncInjector { - in := make([]reflect.Value, n, n) - in[0] = ctrl - funcInjector.Inject(&in, ctxValue) - if ctx.IsStopped() { - return - } - - DispatchFuncResult(ctx, m.Func.Call(in)) + if hasBindableFuncInputs { + // means that ctxValue is not initialized before by the controller's struct injector. + if !hasBindableFields { + ctxValue = reflect.ValueOf(ctx) } + in := make([]reflect.Value, n, n) + in[0] = ctrl + funcInjector.Inject(&in, ctxValue) + DispatchFuncResult(ctx, call(in)) + return } + + DispatchFuncResult(ctx, ctrl.Method(m.Index).Call(emptyIn)) } + } diff --git a/mvc/controller_test.go b/mvc/controller_test.go index 24ed310b..8238924a 100644 --- a/mvc/controller_test.go +++ b/mvc/controller_test.go @@ -469,8 +469,8 @@ func (c *testControllerNotCreateNewDueManuallySettingAllFields) AfterActivation( -- got dependencies length: %d`, n) } - if a.IsRequestScoped() { - c.T.Fatalf(`this controller shouldn't be tagged used as request scoped(create new instances on each request), + if !a.Singleton() { + c.T.Fatalf(`this controller should be tagged as Singleton. It shouldn't be tagged used as request scoped(create new instances on each request), it doesn't contain any dynamic value or dependencies that should be binded via the iris mvc engine`) } } diff --git a/mvc/di/func.go b/mvc/di/func.go index 629c8b01..36424b06 100644 --- a/mvc/di/func.go +++ b/mvc/di/func.go @@ -85,22 +85,15 @@ func MakeFuncInjector(fn reflect.Value, hijack Hijacker, goodFunc TypeChecker, v } } - // s.Length = n s.Length = len(s.inputs) s.Valid = s.Length > 0 for i, in := range s.inputs { - bindmethodTyp := "Static" - - if in.Object.BindType == Dynamic { - bindmethodTyp = "Dynamic" - } - + bindmethodTyp := bindTypeString(in.Object.BindType) typIn := typ.In(in.InputIndex) // remember: on methods that are part of a struct (i.e controller) // the input index = 1 is the begggining instead of the 0, // because the 0 is the controller receiver pointer of the method. - s.trace += fmt.Sprintf("[%d] %s binding: '%s' for input position: %d and type: '%s'\n", i+1, bindmethodTyp, in.Object.Type.String(), in.InputIndex, typIn.String()) } diff --git a/mvc/di/object.go b/mvc/di/object.go index 29de55f5..f33e7036 100644 --- a/mvc/di/object.go +++ b/mvc/di/object.go @@ -12,6 +12,15 @@ const ( Dynamic // dynamic value, depends on some input arguments from the caller. ) +func bindTypeString(typ BindType) string { + switch typ { + case Dynamic: + return "Dynamic" + default: + return "Static" + } +} + type BindObject struct { Type reflect.Type // the Type of 'Value' or the type of the returned 'ReturnValue' . Value reflect.Value diff --git a/mvc/di/reflect.go b/mvc/di/reflect.go index 3b03e6c0..a0674a2a 100644 --- a/mvc/di/reflect.go +++ b/mvc/di/reflect.go @@ -66,6 +66,13 @@ func ValueOf(o interface{}) reflect.Value { return reflect.ValueOf(o) } +func ValuesOf(valuesAsInterface []interface{}) (values []reflect.Value) { + for _, v := range valuesAsInterface { + values = append(values, ValueOf(v)) + } + return +} + func IndirectType(typ reflect.Type) reflect.Type { switch typ.Kind() { case reflect.Ptr, reflect.Array, reflect.Chan, reflect.Map, reflect.Slice: @@ -115,9 +122,10 @@ func structFieldIgnored(f reflect.StructField) bool { } type field struct { - Type reflect.Type - Index []int // the index of the field, slice if it's part of a embedded struct - Name string // the actual name + Type reflect.Type + Name string // the actual name. + Index []int // the index of the field, slice if it's part of a embedded struct + CanSet bool // is true if it's exported. // this could be empty, but in our cases it's not, // it's filled with the bind object (as service which means as static value) @@ -127,11 +135,11 @@ type field struct { // NumFields returns the total number of fields, and the embedded, even if the embedded struct is not exported, // it will check for its exported fields. -func NumFields(elemTyp reflect.Type) int { - return len(lookupFields(elemTyp, nil)) +func NumFields(elemTyp reflect.Type, skipUnexported bool) int { + return len(lookupFields(elemTyp, skipUnexported, nil)) } -func lookupFields(elemTyp reflect.Type, parentIndex []int) (fields []field) { +func lookupFields(elemTyp reflect.Type, skipUnexported bool, parentIndex []int) (fields []field) { if elemTyp.Kind() != reflect.Struct { return } @@ -141,14 +149,15 @@ func lookupFields(elemTyp reflect.Type, parentIndex []int) (fields []field) { if IndirectType(f.Type).Kind() == reflect.Struct && !structFieldIgnored(f) { - fields = append(fields, lookupFields(f.Type, append(parentIndex, i))...) + fields = append(fields, lookupFields(f.Type, skipUnexported, append(parentIndex, i))...) continue } // skip unexported fields here, // after the check for embedded structs, these can be binded if their // fields are exported. - if f.PkgPath != "" { + isExported := f.PkgPath == "" + if skipUnexported && !isExported { continue } @@ -158,9 +167,10 @@ func lookupFields(elemTyp reflect.Type, parentIndex []int) (fields []field) { } field := field{ - Type: f.Type, - Name: f.Name, - Index: index, + Type: f.Type, + Name: f.Name, + Index: index, + CanSet: isExported, } fields = append(fields, field) @@ -172,12 +182,13 @@ func lookupFields(elemTyp reflect.Type, parentIndex []int) (fields []field) { // LookupNonZeroFieldsValues lookup for filled fields based on the "v" struct value instance. // It returns a slice of reflect.Value (same type as `Values`) that can be binded, // like the end-developer's custom values. -func LookupNonZeroFieldsValues(v reflect.Value) (bindValues []reflect.Value) { +func LookupNonZeroFieldsValues(v reflect.Value, skipUnexported bool) (bindValues []reflect.Value) { elem := IndirectValue(v) - fields := lookupFields(IndirectType(v.Type()), nil) - for _, f := range fields { + fields := lookupFields(IndirectType(v.Type()), skipUnexported, nil) - if fieldVal := elem.FieldByIndex(f.Index); f.Type.Kind() == reflect.Ptr && !IsZero(fieldVal) { + for _, f := range fields { + if fieldVal := elem.FieldByIndex(f.Index); /*f.Type.Kind() == reflect.Ptr &&*/ + !IsZero(fieldVal) { bindValues = append(bindValues, fieldVal) } } diff --git a/mvc/di/struct.go b/mvc/di/struct.go index aaa88f89..a0ac9f19 100644 --- a/mvc/di/struct.go +++ b/mvc/di/struct.go @@ -5,6 +5,13 @@ import ( "reflect" ) +type State uint8 + +const ( + Stateless State = iota + Singleton +) + type ( targetStructField struct { Object *BindObject @@ -12,22 +19,38 @@ type ( } StructInjector struct { - elemType reflect.Type + initRef reflect.Value + initRefAsSlice []reflect.Value // useful when the struct is passed on a func as input args via reflection. + elemType reflect.Type // fields []*targetStructField - Valid bool // is true when contains fields and it's a valid target struct. - trace string // for debug info. + // is true when contains bindable fields and it's a valid target struct, + // it maybe 0 but struct may contain unexported fields or exported but no bindable (Stateless) + // see `setState`. + HasFields bool + CanInject bool // if any bindable fields when the state is NOT singleton. + State State } ) +func (s *StructInjector) countBindType(typ BindType) (n int) { + for _, f := range s.fields { + if f.Object.BindType == typ { + n++ + } + } + return +} + func MakeStructInjector(v reflect.Value, hijack Hijacker, goodFunc TypeChecker, values ...reflect.Value) *StructInjector { s := &StructInjector{ - elemType: IndirectType(v.Type()), + initRef: v, + initRefAsSlice: []reflect.Value{v}, + elemType: IndirectType(v.Type()), } - fields := lookupFields(s.elemType, nil) + fields := lookupFields(s.elemType, true, nil) for _, f := range fields { - if hijack != nil { if b, ok := hijack(f.Type); ok && b != nil { s.fields = append(s.fields, &targetStructField{ @@ -55,28 +78,75 @@ func MakeStructInjector(v reflect.Value, hijack Hijacker, goodFunc TypeChecker, }) break } - } } - s.Valid = len(s.fields) > 0 - - if s.Valid { - for i, f := range s.fields { - bindmethodTyp := "Static" - if f.Object.BindType == Dynamic { - bindmethodTyp = "Dynamic" - } - elemField := s.elemType.FieldByIndex(f.FieldIndex) - s.trace += fmt.Sprintf("[%d] %s binding: '%s' for field '%s %s'\n", i+1, bindmethodTyp, f.Object.Type.String(), elemField.Name, elemField.Type.String()) - } - } + s.HasFields = len(s.fields) > 0 + // set the overall state of this injector. + s.setState() + s.fillStruct() return s } -func (s *StructInjector) String() string { - return s.trace +// set the state, once. +func (s *StructInjector) setState() { + // note for zero length of struct's fields: + // if struct doesn't contain any field + // so both of the below variables will be 0, + // so it's a singleton. + // At the other hand the `s.HasFields` maybe false + // but the struct may contain UNEXPORTED fields or non-bindable fields (request-scoped on both cases) + // so a new controller/struct at the caller side should be initialized on each request, + // we should not depend on the `HasFields` for singleton or no, this is the reason I + // added the `.State` now. + + staticBindingsFieldsLength := s.countBindType(Static) + structFieldsLength := NumFields(s.elemType, false) + + // println("staticBindingsFieldsLength: ", staticBindingsFieldsLength) + // println("structFieldsLength: ", structFieldsLength) + + // if the number of static values binded is equal to the + // total struct's fields(including unexported fields this time) then set as singleton. + if staticBindingsFieldsLength == structFieldsLength { + s.State = Singleton + return + } + + s.CanInject = s.State == Stateless && s.HasFields + // the default is `Stateless`, which means that a new instance should be created + // on each inject action by the caller. +} + +// fill the static bindings values once. +func (s *StructInjector) fillStruct() { + if !s.HasFields { + return + } + // if field is Static then set it to the value that passed by the caller, + // so will have the static bindings already and we can just use that value instead + // of creating new instance. + destElem := IndirectValue(s.initRef) + for _, f := range s.fields { + // if field is Static then set it to the value that passed by the caller, + // so will have the static bindings already and we can just use that value instead + // of creating new instance. + if s.State == Singleton && f.Object.BindType == Static { + destElem.FieldByIndex(f.FieldIndex).Set(f.Object.Value) + } + } +} + +func (s *StructInjector) String() (trace string) { + for i, f := range s.fields { + elemField := s.elemType.FieldByIndex(f.FieldIndex) + trace += fmt.Sprintf("[%d] %s binding: '%s' for field '%s %s'\n", + i+1, bindTypeString(f.Object.BindType), f.Object.Type.String(), + elemField.Name, elemField.Type.String()) + } + + return } func (s *StructInjector) Inject(dest interface{}, ctx ...reflect.Value) { @@ -91,25 +161,21 @@ func (s *StructInjector) Inject(dest interface{}, ctx ...reflect.Value) { func (s *StructInjector) InjectElem(destElem reflect.Value, ctx ...reflect.Value) { for _, f := range s.fields { f.Object.Assign(ctx, func(v reflect.Value) { - // fmt.Printf("%s for %s at index: %d\n", destElem.Type().String(), f.Object.Type.String(), f.FieldIndex) destElem.FieldByIndex(f.FieldIndex).Set(v) }) } } -func (s *StructInjector) InjectElemStaticOnly(destElem reflect.Value) (n int) { - for _, f := range s.fields { - if f.Object.BindType != Static { - continue - } - destElem.FieldByIndex(f.FieldIndex).Set(f.Object.Value) - n++ +func (s *StructInjector) New() reflect.Value { + if s.State == Singleton { + return s.initRef } - return + return reflect.New(s.elemType) } -func (s *StructInjector) New(ctx ...reflect.Value) reflect.Value { - dest := reflect.New(s.elemType) - s.InjectElem(dest, ctx...) - return dest +func (s *StructInjector) NewAsSlice() []reflect.Value { + if s.State == Singleton { + return s.initRefAsSlice + } + return []reflect.Value{reflect.New(s.elemType)} } diff --git a/mvc/di/values.go b/mvc/di/values.go index da9342bb..f0801b84 100644 --- a/mvc/di/values.go +++ b/mvc/di/values.go @@ -8,6 +8,8 @@ type ValuesReadOnly interface { Has(value interface{}) bool // Len returns the length of the values. Len() int + // Clone returns a copy of the current values. + Clone() Values } type Values []reflect.Value @@ -27,21 +29,29 @@ func (bv Values) Clone() Values { return NewValues() } +// CloneWithFieldsOf will return a copy of the current values +// plus the "v" struct's fields that are filled(non-zero) by the caller. +func (bv Values) CloneWithFieldsOf(s interface{}) Values { + values := bv.Clone() + + // add the manual filled fields to the dependencies. + filledFieldValues := LookupNonZeroFieldsValues(ValueOf(s), true) + values = append(values, filledFieldValues...) + return values +} + func (bv Values) Len() int { return len(bv) } -// Add binds values to this controller, if you want to share -// binding values between controllers use the Engine's `Bind` function instead. +// Add adds values as dependencies, if the struct's fields +// or the function's input arguments needs them, they will be defined as +// bindings (at build-time) and they will be used (at serve-time). func (bv *Values) Add(values ...interface{}) { - for _, val := range values { - bv.AddValue(reflect.ValueOf(val)) - } + bv.AddValues(ValuesOf(values)...) } -// AddValue same as `Add` but accepts reflect.Value -// instead. -func (bv *Values) AddValue(values ...reflect.Value) { +func (bv *Values) AddValues(values ...reflect.Value) { for _, v := range values { if !goodVal(v) { continue @@ -115,6 +125,6 @@ func (bv *Values) addIfNotExists(v reflect.Value) bool { return false } - bv.AddValue(v) + bv.Add(v) return true } diff --git a/mvc/engine.go b/mvc/engine.go index 269e2032..83e88cd0 100644 --- a/mvc/engine.go +++ b/mvc/engine.go @@ -85,7 +85,9 @@ func (e *Engine) Handler(handler interface{}) context.Handler { // // Examples at: https://github.com/kataras/iris/tree/master/_examples/mvc. func (e *Engine) Controller(router router.Party, controller interface{}, beforeActivate ...func(BeforeActivation)) { - ca := newControllerActivator(router, controller, e.Dependencies.Clone()) + // add the manual filled fields to the dependencies. + dependencies := e.Dependencies.CloneWithFieldsOf(controller) + ca := newControllerActivator(router, controller, dependencies) // give a priority to the "beforeActivate" // callbacks, if any. diff --git a/mvc/func_result.go b/mvc/func_result.go index 9b64c033..8875f078 100644 --- a/mvc/func_result.go +++ b/mvc/func_result.go @@ -165,8 +165,7 @@ func DispatchCommon(ctx context.Context, // // where Get is an HTTP METHOD. func DispatchFuncResult(ctx context.Context, values []reflect.Value) { - numOut := len(values) - if numOut == 0 { + if len(values) == 0 { return } @@ -195,54 +194,107 @@ func DispatchFuncResult(ctx context.Context, values []reflect.Value) { ) for _, v := range values { + // order of these checks matters // for example, first we need to check for status code, // secondly the string (for content type and content)... + // if !v.IsValid() || !v.CanInterface() { + // continue + // } if !v.IsValid() { continue } f := v.Interface() + /* + if b, ok := f.(bool); ok { + found = b + if !found { + // skip everything, we don't care about other return values, + // this boolean is the higher in order. + break + } + continue + } - if b, ok := f.(bool); ok { - found = b + if i, ok := f.(int); ok { + statusCode = i + continue + } + + if s, ok := f.(string); ok { + // a string is content type when it contains a slash and + // content or custom struct is being calculated already; + // (string -> content, string-> content type) + // (customStruct, string -> content type) + if (len(content) > 0 || custom != nil) && strings.IndexByte(s, slashB) > 0 { + contentType = s + } else { + // otherwise is content + content = []byte(s) + } + + continue + } + + if b, ok := f.([]byte); ok { + // it's raw content, get the latest + content = b + continue + } + + if e, ok := f.(compatibleErr); ok { + if e != nil { // it's always not nil but keep it here. + err = e + if statusCode < 400 { + statusCode = DefaultErrStatusCode + } + break // break on first error, error should be in the end but we + // need to know break the dispatcher if any error. + // at the end; we don't want to write anything to the response if error is not nil. + } + continue + } + + // else it's a custom struct or a dispatcher, we'll decide later + // because content type and status code matters + // do that check in order to be able to correctly dispatch: + // (customStruct, error) -> customStruct filled and error is nil + if custom == nil && f != nil { + custom = f + } + + } + + */ + switch value := f.(type) { + case bool: + found = value if !found { - // skip everything, we don't care about other return values, + // skip everything, skip other values, we don't care about other return values, // this boolean is the higher in order. break } - continue - } - - if i, ok := f.(int); ok { - statusCode = i - continue - } - - if s, ok := f.(string); ok { + case int: + statusCode = value + case string: // a string is content type when it contains a slash and // content or custom struct is being calculated already; // (string -> content, string-> content type) // (customStruct, string -> content type) - if (len(content) > 0 || custom != nil) && strings.IndexByte(s, slashB) > 0 { - contentType = s + if (len(content) > 0 || custom != nil) && strings.IndexByte(value, slashB) > 0 { + contentType = value } else { // otherwise is content - content = []byte(s) + content = []byte(value) } - continue - } - - if b, ok := f.([]byte); ok { + case []byte: // it's raw content, get the latest - content = b - continue - } - - if e, ok := f.(compatibleErr); ok { - if e != nil { // it's always not nil but keep it here. - err = e + content = value + case compatibleErr: + if value != nil { // it's always not nil but keep it here. + err = value if statusCode < 400 { statusCode = DefaultErrStatusCode } @@ -250,17 +302,15 @@ func DispatchFuncResult(ctx context.Context, values []reflect.Value) { // need to know break the dispatcher if any error. // at the end; we don't want to write anything to the response if error is not nil. } - continue + default: + // else it's a custom struct or a dispatcher, we'll decide later + // because content type and status code matters + // do that check in order to be able to correctly dispatch: + // (customStruct, error) -> customStruct filled and error is nil + if custom == nil && f != nil { + custom = f + } } - - // else it's a custom struct or a dispatcher, we'll decide later - // because content type and status code matters - // do that check in order to be able to correctly dispatch: - // (customStruct, error) -> customStruct filled and error is nil - if custom == nil && f != nil { - custom = f - } - } DispatchCommon(ctx, statusCode, contentType, content, custom, err, found) diff --git a/mvc/ideas/1/main.go b/mvc/ideas/1/main.go index 329f5c66..2c46f815 100644 --- a/mvc/ideas/1/main.go +++ b/mvc/ideas/1/main.go @@ -15,6 +15,7 @@ import ( // Therefore I should reduce some "freedom of change" for the shake of code maintanability in the core/router files: handler.go | router.go and single change on APIBuilder's field. func main() { app := iris.New() + app.Logger().SetLevel("debug") mvc.New(app.Party("/todo")).Configure(TodoApp) // no let's have a clear "mvc" package without any conversions and type aliases, // it's one extra import path for a whole new world, it worths it. @@ -74,8 +75,8 @@ func (c *TodoController) BeforeActivation(b mvc.BeforeActivation) { } func (c *TodoController) AfterActivation(a mvc.AfterActivation) { - if !a.IsRequestScoped() { - panic("TodoController should be request scoped, we have a 'Session' which depends on the context.") + if a.Singleton() { + panic("TodoController should be stateless, a request-scoped, we have a 'Session' which depends on the context.") } }