diff --git a/mvc2/controller.go b/mvc2/controller.go index ff7fb964..14dbd86f 100644 --- a/mvc2/controller.go +++ b/mvc2/controller.go @@ -9,6 +9,15 @@ import ( "github.com/kataras/iris/core/router/macro" ) +// BaseController is the controller interface, +// which the main request `C` will implement automatically. +// End-dev doesn't need to have any knowledge of this if she/he doesn't want to implement +// a new Controller type. +// Controller looks the whole flow as one handler, so `ctx.Next` +// inside `BeginRequest` is not be respected. +// Alternative way to check if a middleware was procceed successfully +// and called its `ctx.Next` is the `ctx.Proceed(handler) bool`. +// You have to navigate to the `context/context#Proceed` function's documentation. type BaseController interface { BeginRequest(context.Context) EndRequest(context.Context) @@ -59,8 +68,9 @@ func (c *C) BeginRequest(ctx context.Context) { c.Ctx = ctx } // EndRequest does nothing, is here to complete the `BaseController` interface. func (c *C) EndRequest(ctx context.Context) {} +// ControllerActivator returns a new controller type info description. +// Its functionality can be overriden by the end-dev. type ControllerActivator struct { - Engine *Engine // the router is used on the `Activate` and can be used by end-dev on the `OnActivate` // to register any custom controller's functions as handlers but we will need it here // in order to not create a new type like `ActivationPayload` for the `OnActivate`. @@ -88,6 +98,14 @@ type ControllerActivator struct { } func newControllerActivator(router router.Party, controller BaseController, bindValues ...reflect.Value) *ControllerActivator { + var ( + val = reflect.ValueOf(controller) + typ = val.Type() + + // the full name of the controller, it's its type including the package path. + fullName = getNameOf(typ) + ) + // the following will make sure that if // the controller's has set-ed pointer struct fields by the end-dev // we will include them to the bindings. @@ -95,31 +113,37 @@ func newControllerActivator(router router.Party, controller BaseController, bind // the end-developer when declaring the controller, // activate listeners needs them in order to know if something set-ed already or not, // look `BindTypeExists`. - - var ( - val = reflect.ValueOf(controller) - typ = val.Type() - - fullName = getNameOf(typ) - ) + bindValues = append(lookupNonZeroFieldsValues(val), bindValues...) c := &ControllerActivator{ + // give access to the Router to the end-devs if they need it for some reason, + // i.e register done handlers. Router: router, Value: val, Type: typ, FullName: fullName, + // set some methods that end-dev cann't use accidentally + // to register a route via the `Handle`, + // all available exported and compatible methods + // 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. reservedMethods: []string{ "BeginRequest", "EndRequest", "OnActivate", }, - input: append(lookupNonZeroFieldsValues(val), bindValues...), + // 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, } c.parseMethods() return c } +// checks if a method is already registered. func (c *ControllerActivator) isReservedMethod(name string) bool { for _, s := range c.reservedMethods { if s == name { @@ -130,8 +154,9 @@ func (c *ControllerActivator) isReservedMethod(name string) bool { return false } +// register all available, exported methods to handlers if possible. func (c *ControllerActivator) parseMethods() { - // register all available, exported methods to handlers if possible. + n := c.Type.NumMethod() for i := 0; i < n; i++ { m := c.Type.Method(i) @@ -184,49 +209,65 @@ func (c *ControllerActivator) activate() { var emptyIn = []reflect.Value{} -func (c *ControllerActivator) Handle(method, path, funcName string, middleware ...context.Handler) error { +// 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. +// +// Just like `APIBuilder`, it returns the `*router.Route`, if failed +// then it logs the errors and it returns nil, you can check the errors +// programmatically by the `APIBuilder#GetReporter`. +func (c *ControllerActivator) Handle(method, path, funcName string, middleware ...context.Handler) *router.Route { if method == "" || path == "" || funcName == "" || c.isReservedMethod(funcName) { // isReservedMethod -> if it's already registered // by a previous Handle or analyze methods internally. - return errSkip + return nil } + // get the method from the controller type. m, ok := c.Type.MethodByName(funcName) if !ok { err := fmt.Errorf("MVC: function '%s' doesn't exist inside the '%s' controller", funcName, c.FullName) c.Router.GetReporter().AddErr(err) - return err + return nil } + // parse a route template which contains the parameters organised. tmpl, err := macro.Parse(path, c.Router.Macros()) if err != nil { err = fmt.Errorf("MVC: fail to parse the path for '%s.%s': %v", c.FullName, funcName, err) c.Router.GetReporter().AddErr(err) - return err + 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) - // fmt.Printf("===============%s.%s==============\n", c.FullName, funcName) - - funcIn := getInputArgsFromFunc(m.Type) // except the receiver, which is the controller pointer itself. + // 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. funcBindings := newTargetFunc(m.Func, pathParams...) - elemTyp := indirectTyp(c.Type) // the element value, not the pointer. + // 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) { // create a new controller instance of that type(>ptr). ctrl := reflect.New(elemTyp) - b := ctrl.Interface().(BaseController) // the Interface(). is faster than MethodByName or pre-selected methods. + // the Interface(). is faster than MethodByName or pre-selected methods. + b := ctrl.Interface().(BaseController) // init the request. b.BeginRequest(ctx) @@ -268,16 +309,20 @@ func (c *ControllerActivator) Handle(method, path, funcName string, middleware . } - // end the request, don't check for stopped because this does the actual writing - // if no response written already. + if ctx.IsStopped() { + return + } + b.EndRequest(ctx) } // register the handler now. - c.Router.Handle(method, path, append(middleware, handler)...). + route := c.Router.Handle(method, path, append(middleware, handler)...) + if route != nil { // change the main handler's name in order to respect the controller's and give // a proper debug message. - MainHandlerName = fmt.Sprintf("%s.%s", c.FullName, funcName) + route.MainHandlerName = fmt.Sprintf("%s.%s", c.FullName, funcName) + } - return nil + return route }