diff --git a/_benchmarks/iris-mvc2/controllers/values_controller.go b/_benchmarks/iris-mvc2/controllers/values_controller.go new file mode 100644 index 00000000..08eab107 --- /dev/null +++ b/_benchmarks/iris-mvc2/controllers/values_controller.go @@ -0,0 +1,23 @@ +package controllers + +// ValuesController is the equivalent +// `ValuesController` of the .net core 2.0 mvc application. +type ValuesController struct{} + +/* on windows tests(older) the Get was: +func (vc *ValuesController) Get() { + // id,_ := vc.Params.GetInt("id") + // vc.Ctx.WriteString("value") +} +but as Iris is always going better, now supports return values as well*/ + +// Get handles "GET" requests to "api/values/{id}". +func (vc *ValuesController) Get() string { + return "value" +} + +// Put handles "PUT" requests to "api/values/{id}". +func (vc *ValuesController) Put() {} + +// Delete handles "DELETE" requests to "api/values/{id}". +func (vc *ValuesController) Delete() {} diff --git a/_benchmarks/iris-mvc2/main.go b/_benchmarks/iris-mvc2/main.go new file mode 100644 index 00000000..6366ac2c --- /dev/null +++ b/_benchmarks/iris-mvc2/main.go @@ -0,0 +1,17 @@ +package main + +/// TODO: remove this on the "master" branch, or even replace it +// with the "iris-mvc" (the new implementatioin is even faster, close to handlers version, +// with bindings or without). + +import ( + "github.com/kataras/iris" + "github.com/kataras/iris/_benchmarks/iris-mvc2/controllers" + "github.com/kataras/iris/mvc2" +) + +func main() { + app := iris.New() + mvc2.New().Controller(app.Party("/api/values/{id}"), new(controllers.ValuesController)) + app.Run(iris.Addr(":5000"), iris.WithoutVersionChecker) +} diff --git a/configuration.go b/configuration.go index a171ae3e..885555f2 100644 --- a/configuration.go +++ b/configuration.go @@ -32,7 +32,7 @@ func init() { filename := homeConfigurationFilename(".yml") c, err := parseYAML(filename) if err != nil { - // this error will be occured the first time that the configuration + // this error will be occurred the first time that the configuration // file doesn't exist. // Create the YAML-ONLY global configuration file now using the default configuration 'c'. // This is useful when we run multiple iris servers that share the same diff --git a/core/memstore/memstore.go b/core/memstore/memstore.go index 93e5a557..2e161560 100644 --- a/core/memstore/memstore.go +++ b/core/memstore/memstore.go @@ -25,6 +25,13 @@ type ( Store []Entry ) +// GetByKindOrNil will try to get this entry's value of "k" kind, +// if value is not that kind it will NOT try to convert it the "k", instead +// it will return nil, except if boolean; then it will return false +// even if the value was not bool. +// +// If the "k" kind is not a string or int or int64 or bool +// then it will return the raw value of the entry as it's. func (e Entry) GetByKindOrNil(k reflect.Kind) interface{} { switch k { case reflect.String: diff --git a/mvc2/bind.go b/mvc2/bind.go index d305dc50..3ee9cc5a 100644 --- a/mvc2/bind.go +++ b/mvc2/bind.go @@ -86,6 +86,20 @@ func makeBindObject(v reflect.Value) (b bindObject, err error) { 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{ + Type: contextTyp, + BindType: functionResultType, + ReturnValue: func(ctxValue []reflect.Value) reflect.Value { + return ctxValue[0] + }, + } +} + func (b *bindObject) IsAssignable(to reflect.Type) bool { return equalTypes(b.Type, to) } @@ -120,6 +134,15 @@ func newTargetStruct(v reflect.Value, bindValues ...reflect.Value) *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) @@ -147,12 +170,9 @@ func newTargetStruct(v reflect.Value, bindValues ...reflect.Value) *targetStruct func (s *targetStruct) Fill(destElem reflect.Value, ctx ...reflect.Value) { for _, f := range s.Fields { f.Object.Assign(ctx, func(v reflect.Value) { - // defer func() { - // if err := recover(); err != nil { - // fmt.Printf("for index: %#v on: %s where num fields are: %d\n", - // f.FieldIndex, f.Object.Type.String(), destElem.NumField()) - // } - // }() + // if isContext(v.Type()) { + // println("WTF BIND CONTEXT TYPE WHEN BASE CONTROLLER?") + // } destElem.FieldByIndex(f.FieldIndex).Set(v) }) } @@ -187,13 +207,7 @@ func newTargetFunc(fn reflect.Value, bindValues ...reflect.Value) *targetFunc { if isContext(inTyp) { s.Inputs = append(s.Inputs, &targetFuncInput{ InputIndex: i, - Object: &bindObject{ - Type: contextTyp, - BindType: functionResultType, - ReturnValue: func(ctxValue []reflect.Value) reflect.Value { - return ctxValue[0] - }, - }, + Object: newContextBindObject(), }) continue } diff --git a/mvc2/bind_values.go b/mvc2/bind_values.go new file mode 100644 index 00000000..2a7bd41a --- /dev/null +++ b/mvc2/bind_values.go @@ -0,0 +1,99 @@ +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 14dbd86f..e024841c 100644 --- a/mvc2/controller.go +++ b/mvc2/controller.go @@ -62,8 +62,10 @@ type C struct { var _ BaseController = &C{} -// BeginRequest starts the request by initializing the `Context` field. -func (c *C) BeginRequest(ctx context.Context) { c.Ctx = ctx } +// BeginRequest does nothing anymore, is here to complet ethe `BaseController` interface. +// BaseController is not required anymore, `Ctx` is binded automatically by the engine's +// wrapped Handler. +func (c *C) BeginRequest(ctx context.Context) {} // EndRequest does nothing, is here to complete the `BaseController` interface. func (c *C) EndRequest(ctx context.Context) {} @@ -91,13 +93,13 @@ type ControllerActivator struct { // 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. - input []reflect.Value + ValueStore // TODO: or ... this is dirty code I will have to re format it a bit tomorrow. // the bindings that comes from input (and Engine) and can be binded to the controller's(initRef) fields. bindings *targetStruct } -func newControllerActivator(router router.Party, controller BaseController, bindValues ...reflect.Value) *ControllerActivator { +func newControllerActivator(router router.Party, controller interface{}, bindValues ...reflect.Value) *ControllerActivator { var ( val = reflect.ValueOf(controller) typ = val.Type() @@ -128,6 +130,9 @@ func newControllerActivator(router router.Party, controller BaseController, bind // are being appended to the slice at the `parseMethods`, // if a new method is registered via `Handle` its function name // is also appended to that slice. + // + // TODO: now that BaseController is totally optionally + // we have to check if BeginRequest and EndRequest should be here. reservedMethods: []string{ "BeginRequest", "EndRequest", @@ -136,10 +141,9 @@ func newControllerActivator(router router.Party, controller BaseController, bind // 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. - input: bindValues, + ValueStore: bindValues, } - c.parseMethods() return c } @@ -178,33 +182,13 @@ 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.input = values + c.ValueStore = values c.bindings = newTargetStruct(c.Value, values...) } -// Bind binds values to this controller, if you want to share -// binding values between controllers use the Engine's `Bind` function instead. -func (c *ControllerActivator) Bind(values ...interface{}) { - for _, val := range values { - if v := reflect.ValueOf(val); goodVal(v) { - c.input = append(c.input, v) - } - } -} - -// BindTypeExists returns true if a binder responsible to -// bind and return a type of "typ" is already registered to this controller. -func (c *ControllerActivator) BindTypeExists(typ reflect.Type) bool { - for _, in := range c.input { - if equalTypes(in.Type(), typ) { - return true - } - } - return false -} - func (c *ControllerActivator) activate() { - c.SetBindings(c.input...) + c.SetBindings(c.ValueStore...) + c.parseMethods() } var emptyIn = []reflect.Value{} @@ -255,32 +239,50 @@ func (c *ControllerActivator) Handle(method, path, funcName string, middleware . // get the function's input arguments' bindings. funcBindings := newTargetFunc(m.Func, pathParams...) - // the element value, not the pointer. - elemTyp := indirectTyp(c.Type) - // 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. n := len(funcIn) - handler := func(ctx context.Context) { + // the element value, not the pointer, wil lbe used to create a + // new controller on each incoming request. + elemTyp := indirectTyp(c.Type) + 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 + // // 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) + // init the request. + b.BeginRequest(ctx) + + // if begin request stopped the execution. + if ctx.IsStopped() { + return + } + + // EndRequest will be called at any case except the `BeginRequest` is + // stopped. + defer b.EndRequest(ctx) } if !c.bindings.Valid && !funcBindings.Valid { DispatchFuncResult(ctx, ctrl.Method(m.Index).Call(emptyIn)) } else { ctxValue := reflect.ValueOf(ctx) - if c.bindings.Valid { elem := ctrl.Elem() c.bindings.Fill(elem, ctxValue) @@ -309,11 +311,11 @@ func (c *ControllerActivator) Handle(method, path, funcName string, middleware . } - if ctx.IsStopped() { - return - } + // if ctx.IsStopped() { + // return + // } - b.EndRequest(ctx) + // b.EndRequest(ctx) } // register the handler now. diff --git a/mvc2/controller_test.go b/mvc2/controller_test.go index 677222e5..3c48c6a9 100644 --- a/mvc2/controller_test.go +++ b/mvc2/controller_test.go @@ -2,7 +2,6 @@ package mvc2_test import ( - "reflect" "testing" "github.com/kataras/iris" @@ -413,7 +412,7 @@ type testControllerActivateListener struct { } func (c *testControllerActivateListener) OnActivate(ca *ControllerActivator) { - if !ca.BindTypeExists(reflect.TypeOf(&testBindType{})) { + if !ca.BindExists(&testBindType{}) { ca.Bind(&testBindType{ title: "default title", }) diff --git a/mvc2/engine.go b/mvc2/engine.go index f421b26f..50693b3a 100644 --- a/mvc2/engine.go +++ b/mvc2/engine.go @@ -37,8 +37,8 @@ func (e *Engine) Child() *Engine { child := New() // copy the current parent's ctx func binders and services to this new child. - if l := len(e.Input); l > 0 { - input := make([]reflect.Value, l, l) + if n := len(e.Input); n > 0 { + input := make([]reflect.Value, n, n) copy(input, e.Input) child.Input = input } @@ -54,7 +54,7 @@ func (e *Engine) Handler(handler interface{}) context.Handler { return h } -func (e *Engine) Controller(router router.Party, controller BaseController, onActivate ...func(*ControllerActivator)) { +func (e *Engine) Controller(router router.Party, controller interface{}, onActivate ...func(*ControllerActivator)) { ca := newControllerActivator(router, controller, e.Input...) // give a priority to the "onActivate" diff --git a/mvc2/reflect.go b/mvc2/reflect.go index 4405d784..1da58187 100644 --- a/mvc2/reflect.go +++ b/mvc2/reflect.go @@ -8,10 +8,16 @@ import ( "github.com/kataras/pkg/zerocheck" ) -var contextTyp = reflect.TypeOf(context.NewContext(nil)) +var baseControllerTyp = reflect.TypeOf((*BaseController)(nil)).Elem() + +func isBaseController(ctrlTyp reflect.Type) bool { + return ctrlTyp.Implements(baseControllerTyp) +} + +var contextTyp = reflect.TypeOf((*context.Context)(nil)).Elem() func isContext(inTyp reflect.Type) bool { - return inTyp.String() == "context.Context" // I couldn't find another way; context/context.go is not exported. + return inTyp.Implements(contextTyp) } func indirectVal(v reflect.Value) reflect.Value { diff --git a/mvc2/session_controller.go b/mvc2/session_controller.go index 6ca5bce4..8c88f863 100644 --- a/mvc2/session_controller.go +++ b/mvc2/session_controller.go @@ -1,9 +1,6 @@ package mvc2 import ( - "reflect" - - "github.com/kataras/golog" "github.com/kataras/iris/context" "github.com/kataras/iris/sessions" ) @@ -25,11 +22,11 @@ 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 !ca.BindTypeExists(reflect.TypeOf(defaultManager)) { - ca.Bind(defaultManager) - golog.Warnf(`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. -Please refer to the documentation to learn how you can provide the session manager`) + if didntBindManually := ca.BindIfNotExists(defaultManager); 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. + Please refer to the documentation to learn how you can provide the session manager`) } }