diff --git a/_benchmarks/iris-mvc2/controllers/values_controller.go b/_benchmarks/iris-mvc2/controllers/values_controller.go index 08eab107..80ac5ba7 100644 --- a/_benchmarks/iris-mvc2/controllers/values_controller.go +++ b/_benchmarks/iris-mvc2/controllers/values_controller.go @@ -1,8 +1,10 @@ package controllers +// import "github.com/kataras/iris/mvc2" + // ValuesController is the equivalent // `ValuesController` of the .net core 2.0 mvc application. -type ValuesController struct{} +type ValuesController struct{} //{ mvc2.C } /* on windows tests(older) the Get was: func (vc *ValuesController) Get() { diff --git a/_examples/http_responsewriter/hero/app.go b/_examples/http_responsewriter/hero/app.go index f25bb9e1..bf5c1ecc 100644 --- a/_examples/http_responsewriter/hero/app.go +++ b/_examples/http_responsewriter/hero/app.go @@ -46,7 +46,7 @@ func main() { // using an io.Writer for automatic buffer management (i.e. hero built-in buffer pool), // iris context implements the io.Writer by its ResponseWriter - // which is an enhanced version of the standar http.ResponseWriter + // which is an enhanced version of the standard http.ResponseWriter // but still 100% compatible. template.UserListToWriter(userList, ctx) }) diff --git a/mvc2/bind.go b/mvc2/bind.go index 3ee9cc5a..5cfb8593 100644 --- a/mvc2/bind.go +++ b/mvc2/bind.go @@ -1,258 +1,34 @@ package mvc2 -import "reflect" - -type bindType uint32 - -const ( - objectType bindType = iota // simple assignable value. - functionResultType // dynamic value, depends on the context. +import ( + "github.com/kataras/di" + "reflect" ) -type bindObject struct { - Type reflect.Type // the Type of 'Value' or the type of the returned 'ReturnValue' . - Value reflect.Value - - BindType bindType - ReturnValue func(ctx []reflect.Value) reflect.Value -} - -// makeReturnValue takes any function -// that accept a context and returns something -// and returns a binder function, which accepts the context as slice of reflect.Value -// and returns a reflect.Value for that. -// Iris uses to -// resolve and set the input parameters when a handler is executed. -// -// The "fn" can have the following form: -// `func(iris.Context) UserViewModel`. -// -// The return type of the "fn" should be a value instance, not a pointer, for your own protection. -// The binder function should return only one value and -// it can accept only one input argument, -// the Iris' Context (`context.Context` or `iris.Context`). -func makeReturnValue(fn reflect.Value) (func([]reflect.Value) reflect.Value, reflect.Type, error) { - typ := indirectTyp(fn.Type()) - - // invalid if not a func. - if typ.Kind() != reflect.Func { - return nil, typ, errBad +var ( + typeChecker = func(fn reflect.Type) bool { + // invalid if that single input arg is not a typeof context.Context. + return isContext(fn.In(0)) } - // invalid if not returns one single value. - if typ.NumOut() != 1 { - return nil, typ, errBad - } - - // invalid if input args length is not one. - if typ.NumIn() != 1 { - return nil, typ, errBad - } - - // invalid if that single input arg is not a typeof context.Context. - if !isContext(typ.In(0)) { - return nil, typ, errBad - } - - outTyp := typ.Out(0) - zeroOutVal := reflect.New(outTyp).Elem() - - bf := func(ctxValue []reflect.Value) reflect.Value { - results := fn.Call(ctxValue) // ctxValue is like that because of; read makeHandler. - if len(results) == 0 { - return zeroOutVal + hijacker = func(fieldOrFuncInput reflect.Type) (*di.BindObject, bool) { + if isContext(fieldOrFuncInput) { + return newContextBindObject(), true } - - v := results[0] - if !v.IsValid() { - return zeroOutVal - } - return v + return nil, false } - - return bf, outTyp, nil -} - -func makeBindObject(v reflect.Value) (b bindObject, err error) { - if isFunc(v) { - b.BindType = functionResultType - b.ReturnValue, b.Type, err = makeReturnValue(v) - } else { - b.BindType = objectType - b.Type = v.Type() - b.Value = v - } - - return -} +) // newContextBindObject is being used on both targetFunc and targetStruct. // if the func's input argument or the struct's field is a type of Context // then we can do a fast binding using the ctxValue // which is used as slice of reflect.Value, because of the final method's `Call`. -func newContextBindObject() *bindObject { - return &bindObject{ +func newContextBindObject() *di.BindObject { + return &di.BindObject{ Type: contextTyp, - BindType: functionResultType, + BindType: di.Dynamic, ReturnValue: func(ctxValue []reflect.Value) reflect.Value { return ctxValue[0] }, } } - -func (b *bindObject) IsAssignable(to reflect.Type) bool { - return equalTypes(b.Type, to) -} - -func (b *bindObject) Assign(ctx []reflect.Value, toSetter func(reflect.Value)) { - if b.BindType == functionResultType { - toSetter(b.ReturnValue(ctx)) - return - } - toSetter(b.Value) -} - -type ( - targetField struct { - Object *bindObject - FieldIndex []int - } - targetFuncInput struct { - Object *bindObject - InputIndex int - } -) - -type targetStruct struct { - Fields []*targetField - Valid bool // is True when contains fields and it's a valid target struct. -} - -func newTargetStruct(v reflect.Value, bindValues ...reflect.Value) *targetStruct { - typ := indirectTyp(v.Type()) - s := &targetStruct{} - - fields := lookupFields(typ, nil) - for _, f := range fields { - // if it's context then bind it directly here and continue to the next field. - if isContext(f.Type) { - s.Fields = append(s.Fields, &targetField{ - FieldIndex: f.Index, - Object: newContextBindObject(), - }) - continue - } - - for _, val := range bindValues { - // the binded values to the struct's fields. - b, err := makeBindObject(val) - - if err != nil { - return s // if error stop here. - } - - if b.IsAssignable(f.Type) { - // fmt.Printf("bind the object to the field: %s at index: %#v and type: %s\n", f.Name, f.Index, f.Type.String()) - s.Fields = append(s.Fields, &targetField{ - FieldIndex: f.Index, - Object: &b, - }) - break - } - - } - } - - s.Valid = len(s.Fields) > 0 - return s -} - -func (s *targetStruct) Fill(destElem reflect.Value, ctx ...reflect.Value) { - for _, f := range s.Fields { - f.Object.Assign(ctx, func(v reflect.Value) { - // if isContext(v.Type()) { - // println("WTF BIND CONTEXT TYPE WHEN BASE CONTROLLER?") - // } - destElem.FieldByIndex(f.FieldIndex).Set(v) - }) - } -} - -type targetFunc struct { - Inputs []*targetFuncInput - Valid bool // is True when contains func inputs and it's a valid target func. -} - -func newTargetFunc(fn reflect.Value, bindValues ...reflect.Value) *targetFunc { - typ := indirectTyp(fn.Type()) - s := &targetFunc{ - Valid: false, - } - - if !isFunc(typ) { - return s - } - - n := typ.NumIn() - - // function input can have many values of the same types, - // so keep track of them in order to not set a func input to a next bind value, - // i.e (string, string) with two different binder funcs because of the different param's name. - consumedValues := make(map[int]bool, n) - - for i := 0; i < n; i++ { - inTyp := typ.In(i) - - // if it's context then bind it directly here and continue to the next func's input arg. - if isContext(inTyp) { - s.Inputs = append(s.Inputs, &targetFuncInput{ - InputIndex: i, - Object: newContextBindObject(), - }) - continue - } - - for valIdx, val := range bindValues { - if _, shouldSkip := consumedValues[valIdx]; shouldSkip { - continue - } - inTyp := typ.In(i) - - // the binded values to the func's inputs. - b, err := makeBindObject(val) - - if err != nil { - return s // if error stop here. - } - - if b.IsAssignable(inTyp) { - // fmt.Printf("binded input index: %d for type: %s and value: %v with pointer: %v\n", - // i, b.Type.String(), val.String(), val.Pointer()) - s.Inputs = append(s.Inputs, &targetFuncInput{ - InputIndex: i, - Object: &b, - }) - - consumedValues[valIdx] = true - break - } - } - } - - s.Valid = len(s.Inputs) > 0 - return s -} - -func (s *targetFunc) Fill(in *[]reflect.Value, ctx ...reflect.Value) { - args := *in - for _, input := range s.Inputs { - input.Object.Assign(ctx, func(v reflect.Value) { - // fmt.Printf("assign input index: %d for value: %v\n", - // input.InputIndex, v.String()) - args[input.InputIndex] = v - }) - - } - - *in = args -} diff --git a/mvc2/bind_values.go b/mvc2/bind_values.go deleted file mode 100644 index 2a7bd41a..00000000 --- a/mvc2/bind_values.go +++ /dev/null @@ -1,99 +0,0 @@ -package mvc2 - -import ( - "reflect" -) - -/// TODO: -// create another package because these bindings things are useful -// for other libraries I'm working on, so something like github.com/kataras/di -// will be great, combine these with the bind.go and controller's inside handler -// but generic things. - -type ValueStore []reflect.Value - -// Bind binds values to this controller, if you want to share -// binding values between controllers use the Engine's `Bind` function instead. -func (bv *ValueStore) Bind(values ...interface{}) { - for _, val := range values { - bv.bind(reflect.ValueOf(val)) - } -} - -func (bv *ValueStore) bind(v reflect.Value) { - if !goodVal(v) { - return - } - - *bv = append(*bv, v) -} - -// Unbind unbinds a binding value based on the type, -// it returns true if at least one field is not binded anymore. -// -// The "n" indicates the number of elements to remove, if <=0 then it's 1, -// this is useful because you may have bind more than one value to two or more fields -// with the same type. -func (bv *ValueStore) Unbind(value interface{}, n int) bool { - return bv.unbind(reflect.TypeOf(value), n) -} - -func (bv *ValueStore) unbind(typ reflect.Type, n int) (ok bool) { - input := *bv - for i, in := range input { - if equalTypes(in.Type(), typ) { - ok = true - input = input[:i+copy(input[i:], input[i+1:])] - if n > 1 { - continue - } - break - } - } - - *bv = input - - return -} - -// BindExists returns true if a binder responsible to -// bind and return a type of "typ" is already registered to this controller. -func (bv *ValueStore) BindExists(value interface{}) bool { - return bv.bindTypeExists(reflect.TypeOf(value)) -} - -func (bv *ValueStore) bindTypeExists(typ reflect.Type) bool { - input := *bv - for _, in := range input { - if equalTypes(in.Type(), typ) { - return true - } - } - return false -} - -// BindIfNotExists bind a value to the controller's field with the same type, -// if it's not binded already. -// -// Returns false if binded already or the value is not the proper one for binding, -// otherwise true. -func (bv *ValueStore) BindIfNotExists(value interface{}) bool { - return bv.bindIfNotExists(reflect.ValueOf(value)) -} - -func (bv *ValueStore) bindIfNotExists(v reflect.Value) bool { - var ( - typ = v.Type() // no element, raw things here. - ) - - if !goodVal(v) { - return false - } - - if bv.bindTypeExists(typ) { - return false - } - - bv.bind(v) - return true -} diff --git a/mvc2/controller.go b/mvc2/controller.go index 4afad118..4865644c 100644 --- a/mvc2/controller.go +++ b/mvc2/controller.go @@ -4,6 +4,8 @@ import ( "fmt" "reflect" + "github.com/kataras/di" + "github.com/kataras/iris/context" "github.com/kataras/iris/core/router" "github.com/kataras/iris/core/router/macro" @@ -89,17 +91,16 @@ type ControllerActivator struct { // the BeginRequest, EndRequest and OnActivate are reserved by the internal implementation. reservedMethods []string - // input are always empty after the `activate` - // are used to build the bindings, and we need this field - // because we have 3 states (Engine.Input, OnActivate, Bind) - // that we can add or override binding values. - ValueStore // TODO: or ... this is dirty code I will have to re format it a bit tomorrow. + // the bindings that comes from the Engine and the controller's filled fields if any. + // Can be binded to the the new controller's fields and method that is fired + // on incoming requests. + Dependencies *di.D - // the bindings that comes from input (and Engine) and can be binded to the controller's(initRef) fields. - bindings *targetStruct + // on activate. + injector *di.StructInjector } -func newControllerActivator(router router.Party, controller interface{}, bindValues ...reflect.Value) *ControllerActivator { +func newControllerActivator(router router.Party, controller interface{}, d *di.D) *ControllerActivator { var ( val = reflect.ValueOf(controller) typ = val.Type() @@ -115,7 +116,7 @@ func newControllerActivator(router router.Party, controller interface{}, bindVal // the end-developer when declaring the controller, // activate listeners needs them in order to know if something set-ed already or not, // look `BindTypeExists`. - bindValues = append(lookupNonZeroFieldsValues(val), bindValues...) + d.Values = append(lookupNonZeroFieldsValues(val), d.Values...) c := &ControllerActivator{ // give access to the Router to the end-devs if they need it for some reason, @@ -133,20 +134,22 @@ func newControllerActivator(router router.Party, controller interface{}, bindVal // // TODO: now that BaseController is totally optionally // we have to check if BeginRequest and EndRequest should be here. - reservedMethods: []string{ - "BeginRequest", - "EndRequest", - "OnActivate", - }, - // set the input as []reflect.Value in order to be able - // to check if a bind type is already exists, or even - // override the structBindings that are being generated later on. - ValueStore: bindValues, + reservedMethods: whatReservedMethods(typ), + Dependencies: d, } return c } +func whatReservedMethods(typ reflect.Type) []string { + methods := []string{"OnActivate"} + if isBaseController(typ) { + methods = append(methods, "BeginRequest", "EndRequest") + } + + return methods +} + // checks if a method is already registered. func (c *ControllerActivator) isReservedMethod(name string) bool { for _, s := range c.reservedMethods { @@ -178,15 +181,8 @@ func (c *ControllerActivator) parseMethods() { } } -// SetBindings will override any bindings with the new "values". -func (c *ControllerActivator) SetBindings(values ...reflect.Value) { - // set field index with matching binders, if any. - c.ValueStore = values - c.bindings = newTargetStruct(c.Value, values...) -} - func (c *ControllerActivator) activate() { - c.SetBindings(c.ValueStore...) + c.injector = c.Dependencies.Struct(c.Value) c.parseMethods() } @@ -236,11 +232,13 @@ func (c *ControllerActivator) Handle(method, path, funcName string, middleware . // end-dev's controller pointer. pathParams := getPathParamsForInput(tmpl.Params, funcIn[1:]...) // get the function's input arguments' bindings. - funcBindings := newTargetFunc(m.Func, pathParams...) + funcDependencies := c.Dependencies.Clone() + funcDependencies.Add(pathParams...) + funcInjector := funcDependencies.Func(m.Func) // 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 funcBindings. + // are will being filled by the funcDependencies. n := len(funcIn) // the element value, not the pointer, wil lbe used to create a // new controller on each incoming request. @@ -249,19 +247,8 @@ func (c *ControllerActivator) Handle(method, path, funcName string, middleware . implementsBase := isBaseController(c.Type) handler := func(ctx context.Context) { - // create a new controller instance of that type(>ptr). ctrl := reflect.New(elemTyp) - // // the Interface(). is faster than MethodByName or pre-selected methods. - // b := ctrl.Interface().(BaseController) - // // init the request. - // b.BeginRequest(ctx) - - // // if begin request stopped the execution. - // if ctx.IsStopped() { - // return - // } - if implementsBase { // the Interface(). is faster than MethodByName or pre-selected methods. b := ctrl.Interface().(BaseController) @@ -273,34 +260,32 @@ func (c *ControllerActivator) Handle(method, path, funcName string, middleware . return } - // EndRequest will be called at any case except the `BeginRequest` is - // stopped. defer b.EndRequest(ctx) } - if !c.bindings.Valid && !funcBindings.Valid { + if !c.injector.Valid && !funcInjector.Valid { DispatchFuncResult(ctx, ctrl.Method(m.Index).Call(emptyIn)) } else { ctxValue := reflect.ValueOf(ctx) - if c.bindings.Valid { + if c.injector.Valid { elem := ctrl.Elem() - c.bindings.Fill(elem, ctxValue) + c.injector.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 !funcBindings.Valid { + if !funcInjector.Valid { 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 funcBindings.Valid { + if funcInjector.Valid { in := make([]reflect.Value, n, n) in[0] = ctrl - funcBindings.Fill(&in, ctxValue) + funcInjector.Inject(&in, ctxValue) if ctx.IsStopped() { return } @@ -309,12 +294,6 @@ func (c *ControllerActivator) Handle(method, path, funcName string, middleware . } } - - // if ctx.IsStopped() { - // return - // } - - // b.EndRequest(ctx) } // register the handler now. diff --git a/mvc2/controller_test.go b/mvc2/controller_test.go index d0079f6b..785cf133 100644 --- a/mvc2/controller_test.go +++ b/mvc2/controller_test.go @@ -421,8 +421,8 @@ type testControllerActivateListener struct { } func (c *testControllerActivateListener) OnActivate(ca *ControllerActivator) { - if !ca.BindExists(&testBindType{}) { - ca.Bind(&testBindType{ + if !ca.Dependencies.BindExists(&testBindType{}) { + ca.Dependencies.Bind(&testBindType{ title: "default title", }) } diff --git a/mvc2/engine.go b/mvc2/engine.go index 50693b3a..59454752 100644 --- a/mvc2/engine.go +++ b/mvc2/engine.go @@ -2,9 +2,10 @@ package mvc2 import ( "errors" - "reflect" + "github.com/kataras/di" "github.com/kataras/golog" + "github.com/kataras/iris/context" "github.com/kataras/iris/core/router" ) @@ -16,38 +17,28 @@ var ( ) type Engine struct { - Input []reflect.Value + dependencies *di.D } func New() *Engine { - return new(Engine) + return &Engine{ + dependencies: di.New().Hijack(hijacker).GoodFunc(typeChecker), + } } func (e *Engine) Bind(values ...interface{}) *Engine { - for _, val := range values { - if v := reflect.ValueOf(val); goodVal(v) { - e.Input = append(e.Input, v) - } - } - + e.dependencies.Bind(values...) return e } func (e *Engine) Child() *Engine { child := New() - - // copy the current parent's ctx func binders and services to this new child. - if n := len(e.Input); n > 0 { - input := make([]reflect.Value, n, n) - copy(input, e.Input) - child.Input = input - } - + child.dependencies = e.dependencies.Clone() return child } func (e *Engine) Handler(handler interface{}) context.Handler { - h, err := MakeHandler(handler, e.Input...) + h, err := MakeHandler(handler, e.dependencies.Values...) if err != nil { golog.Errorf("mvc handler: %v", err) } @@ -55,7 +46,7 @@ func (e *Engine) Handler(handler interface{}) context.Handler { } func (e *Engine) Controller(router router.Party, controller interface{}, onActivate ...func(*ControllerActivator)) { - ca := newControllerActivator(router, controller, e.Input...) + ca := newControllerActivator(router, controller, e.dependencies) // give a priority to the "onActivate" // callbacks, if any. diff --git a/mvc2/func_result_test.go b/mvc2/func_result_test.go index 5df3b072..240a044b 100644 --- a/mvc2/func_result_test.go +++ b/mvc2/func_result_test.go @@ -267,7 +267,7 @@ func (t *testControllerViewResultRespectCtxViewData) Get() Result { func TestControllerViewResultRespectCtxViewData(t *testing.T) { app := iris.New() New().Controller(app, new(testControllerViewResultRespectCtxViewData), func(ca *ControllerActivator) { - ca.Bind(t) + ca.Dependencies.Bind(t) }) e := httptest.New(t, app) diff --git a/mvc2/handler.go b/mvc2/handler.go index e35ab7bd..99418677 100644 --- a/mvc2/handler.go +++ b/mvc2/handler.go @@ -2,6 +2,7 @@ package mvc2 import ( "fmt" + "github.com/kataras/di" "reflect" "runtime" @@ -64,7 +65,7 @@ func MakeHandler(handler interface{}, bindValues ...reflect.Value) (context.Hand return h, nil } - s := newTargetFunc(fn, bindValues...) + s := di.MakeFuncInjector(fn, hijacker, typeChecker, bindValues...) if !s.Valid { pc := fn.Pointer() fpc := runtime.FuncForPC(pc) @@ -72,14 +73,14 @@ func MakeHandler(handler interface{}, bindValues ...reflect.Value) (context.Hand callerName := fpc.Name() err := fmt.Errorf("input arguments length(%d) and valid binders length(%d) are not equal for typeof '%s' which is defined at %s:%d by %s", - n, len(s.Inputs), fn.Type().String(), callerFileName, callerLineNumber, callerName) + n, s.Length, fn.Type().String(), callerFileName, callerLineNumber, callerName) return nil, err } h := func(ctx context.Context) { in := make([]reflect.Value, n, n) - s.Fill(&in, reflect.ValueOf(ctx)) + s.Inject(&in, reflect.ValueOf(ctx)) if ctx.IsStopped() { return } diff --git a/mvc2/reflect.go b/mvc2/reflect.go index 1da58187..09d4b47d 100644 --- a/mvc2/reflect.go +++ b/mvc2/reflect.go @@ -110,6 +110,10 @@ func lookupFields(elemTyp reflect.Type, parentIndex []int) (fields []field) { for i, n := 0, elemTyp.NumField(); i < n; i++ { f := elemTyp.Field(i) + if f.PkgPath != "" { + continue // skip unexported. + } + if indirectTyp(f.Type).Kind() == reflect.Struct && !structFieldIgnored(f) { fields = append(fields, lookupFields(f.Type, append(parentIndex, i))...) diff --git a/mvc2/session_controller.go b/mvc2/session_controller.go index e369e877..8b7e815a 100644 --- a/mvc2/session_controller.go +++ b/mvc2/session_controller.go @@ -22,7 +22,7 @@ type SessionController struct { // It makes sure that its "Manager" field is filled // even if the caller didn't provide any sessions manager via the `app.Controller` function. func (s *SessionController) OnActivate(ca *ControllerActivator) { - if didntBindManually := ca.BindIfNotExists(defaultSessionManager); didntBindManually { + if didntBindManually := ca.Dependencies.BindIfNotExists(defaultSessionManager); didntBindManually { ca.Router.GetReporter().Add( `MVC SessionController: couldn't find any "*sessions.Sessions" bindable value to fill the "Manager" field, therefore this controller is using the default sessions manager instead.