mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 18:51:03 +01:00
de3fc8ae46
https://github.com/kataras/iris/blob/master/HISTORY.md#tu-10-october-2017--v851 Former-commit-id: af1424c691613ccde911bef7d1371aa52e6abb79
224 lines
6.8 KiB
Go
224 lines
6.8 KiB
Go
package activator
|
|
|
|
import (
|
|
"reflect"
|
|
"strings"
|
|
|
|
"github.com/kataras/iris/mvc/activator/methodfunc"
|
|
"github.com/kataras/iris/mvc/activator/model"
|
|
"github.com/kataras/iris/mvc/activator/persistence"
|
|
|
|
"github.com/kataras/golog"
|
|
|
|
"github.com/kataras/iris/context"
|
|
"github.com/kataras/iris/core/errors"
|
|
)
|
|
|
|
type (
|
|
// TController is the type of the controller,
|
|
// it contains all the necessary information to load
|
|
// and serve the controller to the outside world,
|
|
// think it as a "supervisor" of your Controller which
|
|
// cares about you.
|
|
TController struct {
|
|
// The name of the front controller struct.
|
|
Name string
|
|
// FullName it's the last package path segment + "." + the Name.
|
|
// i.e: if login-example/user/controller.go, the FullName is "user.Controller".
|
|
FullName string
|
|
// the type of the user/dev's "c" controller (interface{}).
|
|
Type reflect.Type
|
|
// it's the first passed value of the controller instance,
|
|
// we need this to collect and save the persistence fields' values.
|
|
Value reflect.Value
|
|
|
|
binder *binder // executed even before the BeginRequest if not nil.
|
|
modelController *model.Controller
|
|
persistenceController *persistence.Controller
|
|
}
|
|
)
|
|
|
|
// the parent package should complete this "interface"
|
|
// it's not exported, so their functions
|
|
// but reflect doesn't care about it, so we are ok
|
|
// to compare the type of the base controller field
|
|
// with this "ctrl", see `buildTypeInfo` and `buildMethodHandler`.
|
|
|
|
var (
|
|
// ErrMissingControllerInstance is a static error which fired from `Controller` when
|
|
// the passed "c" instnace is not a valid type of `Controller`.
|
|
ErrMissingControllerInstance = errors.New("controller should have a field of Controller type")
|
|
// ErrInvalidControllerType fired when the "Controller" field is not
|
|
// the correct type.
|
|
ErrInvalidControllerType = errors.New("controller instance is not a valid implementation")
|
|
)
|
|
|
|
// BaseController is the controller interface,
|
|
// which the main request `Controller` will implement automatically.
|
|
// End-User doesn't need to have any knowledge of this if she/he doesn't want to implement
|
|
// a new Controller type.
|
|
type BaseController interface {
|
|
SetName(name string)
|
|
BeginRequest(ctx context.Context)
|
|
EndRequest(ctx context.Context)
|
|
}
|
|
|
|
// ActivateController returns a new controller type info description.
|
|
func ActivateController(base BaseController, bindValues []interface{}) (TController, error) {
|
|
// get and save the type.
|
|
typ := reflect.TypeOf(base)
|
|
if typ.Kind() != reflect.Ptr {
|
|
typ = reflect.PtrTo(typ)
|
|
}
|
|
|
|
// first instance value, needed to validate
|
|
// the actual type of the controller field
|
|
// and to collect and save the instance's persistence fields'
|
|
// values later on.
|
|
val := reflect.Indirect(reflect.ValueOf(base))
|
|
ctrlName := val.Type().Name()
|
|
pkgPath := val.Type().PkgPath()
|
|
fullName := pkgPath[strings.LastIndexByte(pkgPath, '/')+1:] + "." + ctrlName
|
|
|
|
// set the binder, can be nil this check at made at runtime.
|
|
binder := newBinder(typ.Elem(), bindValues)
|
|
if binder != nil {
|
|
for _, bf := range binder.fields {
|
|
golog.Debugf("MVC %s: binder loaded for '%s' with value:\n%#v",
|
|
fullName, bf.GetFullName(), bf.GetValue())
|
|
}
|
|
}
|
|
|
|
t := TController{
|
|
Name: ctrlName,
|
|
FullName: fullName,
|
|
Type: typ,
|
|
Value: val,
|
|
binder: binder,
|
|
modelController: model.Load(typ),
|
|
persistenceController: persistence.Load(typ, val),
|
|
}
|
|
|
|
return t, nil
|
|
}
|
|
|
|
// HandlerOf builds the handler for a type based on the specific method func.
|
|
func (t TController) HandlerOf(methodFunc methodfunc.MethodFunc) context.Handler {
|
|
var (
|
|
// shared, per-controller
|
|
elem = t.Type.Elem()
|
|
ctrlName = t.Name
|
|
|
|
hasPersistenceData = t.persistenceController != nil
|
|
hasModels = t.modelController != nil
|
|
// per-handler
|
|
handleRequest = methodFunc.MethodCall
|
|
)
|
|
|
|
return func(ctx context.Context) {
|
|
// create a new controller instance of that type(>ptr).
|
|
c := reflect.New(elem)
|
|
if t.binder != nil {
|
|
t.binder.handle(c)
|
|
}
|
|
|
|
b := c.Interface().(BaseController)
|
|
b.SetName(ctrlName)
|
|
|
|
// if has persistence data then set them
|
|
// before the end-developer's handler in order to be available there.
|
|
if hasPersistenceData {
|
|
t.persistenceController.Handle(c)
|
|
}
|
|
|
|
// if previous (binded) handlers stopped the execution
|
|
// we should know that.
|
|
if ctx.IsStopped() {
|
|
return
|
|
}
|
|
|
|
// init the request.
|
|
b.BeginRequest(ctx)
|
|
if ctx.IsStopped() { // if begin request stopped the execution
|
|
return
|
|
}
|
|
|
|
// the most important, execute the specific function
|
|
// from the controller that is responsible to handle
|
|
// this request, by method and path.
|
|
handleRequest(ctx, c.Method(methodFunc.Index))
|
|
// if had models, set them after the end-developer's handler.
|
|
if hasModels {
|
|
t.modelController.Handle(ctx, c)
|
|
}
|
|
|
|
// end the request, don't check for stopped because this does the actual writing
|
|
// if no response written already.
|
|
b.EndRequest(ctx)
|
|
}
|
|
}
|
|
|
|
// RegisterFunc used by the caller to register the result routes.
|
|
type RegisterFunc func(relPath string, httpMethod string, handler ...context.Handler)
|
|
|
|
// RegisterMethodHandlers receives a `TController`, description of the
|
|
// user's controller, and calls the "registerFunc" for each of its
|
|
// method handlers.
|
|
//
|
|
// Not useful for the end-developer, but may needed for debugging
|
|
// at the future.
|
|
func RegisterMethodHandlers(t TController, registerFunc RegisterFunc) {
|
|
var middleware context.Handlers
|
|
|
|
if t.binder != nil {
|
|
if m := t.binder.middleware; len(m) > 0 {
|
|
middleware = m
|
|
}
|
|
}
|
|
// the actual method functions
|
|
// i.e for "GET" it's the `Get()`.
|
|
methods, err := methodfunc.Resolve(t.Type)
|
|
if err != nil {
|
|
golog.Errorf("MVC %s: %s", t.FullName, err.Error())
|
|
// don't stop here.
|
|
}
|
|
// range over the type info's method funcs,
|
|
// build a new handler for each of these
|
|
// methods and register them to their
|
|
// http methods using the registerFunc, which is
|
|
// responsible to convert these into routes
|
|
// and add them to router via the APIBuilder.
|
|
for _, m := range methods {
|
|
h := t.HandlerOf(m)
|
|
if h == nil {
|
|
golog.Warnf("MVC %s: nil method handler found for %s", t.FullName, m.Name)
|
|
continue
|
|
}
|
|
registeredHandlers := append(middleware, h)
|
|
registerFunc(m.RelPath, m.HTTPMethod, registeredHandlers...)
|
|
|
|
golog.Debugf("MVC %s: %s %s maps to function[%d] '%s'", t.FullName,
|
|
m.HTTPMethod,
|
|
m.RelPath,
|
|
m.Index,
|
|
m.Name)
|
|
}
|
|
}
|
|
|
|
// Register receives a "controller",
|
|
// a pointer of an instance which embeds the `Controller`,
|
|
// the value of "baseControllerFieldName" should be `Controller`.
|
|
func Register(controller BaseController, bindValues []interface{},
|
|
registerFunc RegisterFunc) error {
|
|
|
|
CallOnActivate(controller, &bindValues, registerFunc)
|
|
|
|
t, err := ActivateController(controller, bindValues)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
RegisterMethodHandlers(t, registerFunc)
|
|
return nil
|
|
}
|