mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 10:41:03 +01:00
performance close to handlers if no bindings but even if bindings except service (new feature is that we can bind functions as well) is x1.1 faster than the previous mvc implementation - make BaseController (so and C) optionally but not break the existing APIs that using iris.C or mvc.C
Former-commit-id: a26a8f836894c061e0f435df8ac1c2c534f0ee48
This commit is contained in:
parent
257f1318c9
commit
8dcbdc0741
23
_benchmarks/iris-mvc2/controllers/values_controller.go
Normal file
23
_benchmarks/iris-mvc2/controllers/values_controller.go
Normal file
|
@ -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() {}
|
17
_benchmarks/iris-mvc2/main.go
Normal file
17
_benchmarks/iris-mvc2/main.go
Normal file
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
40
mvc2/bind.go
40
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
|
||||
}
|
||||
|
|
99
mvc2/bind_values.go
Normal file
99
mvc2/bind_values.go
Normal file
|
@ -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
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -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",
|
||||
})
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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`)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user