mvc.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. package mvc
  2. import (
  3. "reflect"
  4. "strings"
  5. "github.com/kataras/iris/context"
  6. "github.com/kataras/iris/core/router"
  7. "github.com/kataras/iris/hero"
  8. "github.com/kataras/iris/websocket"
  9. "github.com/kataras/golog"
  10. )
  11. // Application is the high-level component of the "mvc" package.
  12. // It's the API that you will be using to register controllers among with their
  13. // dependencies that your controllers may expecting.
  14. // It contains the Router(iris.Party) in order to be able to register
  15. // template layout, middleware, done handlers as you used with the
  16. // standard Iris APIBuilder.
  17. //
  18. // The Engine is created by the `New` method and it's the dependencies holder
  19. // and controllers factory.
  20. //
  21. // See `mvc#New` for more.
  22. type Application struct {
  23. container *hero.Container
  24. // This Application's Name. Keep names unique to each other.
  25. Name string
  26. Router router.Party
  27. Controllers []*ControllerActivator
  28. websocketControllers []websocket.ConnHandler
  29. }
  30. func newApp(subRouter router.Party, container *hero.Container) *Application {
  31. app := &Application{
  32. Router: subRouter,
  33. container: container,
  34. }
  35. // Register this Application so any field or method's input argument of
  36. // *mvc.Application can point to the current MVC application that the controller runs on.
  37. registerBuiltinDependencies(container, app)
  38. return app
  39. }
  40. // See `hero.BuiltinDependencies` too, here we are registering dependencies per MVC Application.
  41. func registerBuiltinDependencies(container *hero.Container, deps ...interface{}) {
  42. for _, dep := range deps {
  43. depTyp := reflect.TypeOf(dep)
  44. for i, dependency := range container.Dependencies {
  45. if dependency.Static {
  46. if dependency.DestType == depTyp {
  47. // Remove any existing before register this one (see app.Clone).
  48. copy(container.Dependencies[i:], container.Dependencies[i+1:])
  49. container.Dependencies = container.Dependencies[:len(container.Dependencies)-1]
  50. break
  51. }
  52. }
  53. }
  54. container.Register(dep)
  55. }
  56. }
  57. // New returns a new mvc Application based on a "party".
  58. // Application creates a new engine which is responsible for binding the dependencies
  59. // and creating and activating the app's controller(s).
  60. //
  61. // Example: `New(app.Party("/todo"))` or `New(app)` as it's the same as `New(app.Party("/"))`.
  62. func New(party router.Party) *Application {
  63. return newApp(party, party.ConfigureContainer().Container.Clone())
  64. }
  65. // Configure creates a new controller and configures it,
  66. // this function simply calls the `New(party)` and its `.Configure(configurators...)`.
  67. //
  68. // A call of `mvc.New(app.Party("/path").Configure(buildMyMVC)` is equal to
  69. // `mvc.Configure(app.Party("/path"), buildMyMVC)`.
  70. //
  71. // Read more at `New() Application` and `Application#Configure` methods.
  72. func Configure(party router.Party, configurators ...func(*Application)) *Application {
  73. // Author's Notes->
  74. // About the Configure's comment: +5 space to be shown in equal width to the previous or after line.
  75. //
  76. // About the Configure's design chosen:
  77. // Yes, we could just have a `New(party, configurators...)`
  78. // but I think the `New()` and `Configure(configurators...)` API seems more native to programmers,
  79. // at least to me and the people I ask for their opinion between them.
  80. // Because the `New()` can actually return something that can be fully configured without its `Configure`,
  81. // its `Configure` is there just to design the apps better and help end-devs to split their code wisely.
  82. return New(party).Configure(configurators...)
  83. }
  84. // Configure can be used to pass one or more functions that accept this
  85. // Application, use this to add dependencies and controller(s).
  86. //
  87. // Example: `New(app.Party("/todo")).Configure(func(mvcApp *mvc.Application){...})`.
  88. func (app *Application) Configure(configurators ...func(*Application)) *Application {
  89. for _, c := range configurators {
  90. c(app)
  91. }
  92. return app
  93. }
  94. // SetName sets a unique name to this MVC Application.
  95. // Used for logging, not used in runtime yet, but maybe useful for future features.
  96. //
  97. // It returns this Application.
  98. func (app *Application) SetName(appName string) *Application {
  99. app.Name = appName
  100. return app
  101. }
  102. // Register appends one or more values as dependencies.
  103. // The value can be a single struct value-instance or a function
  104. // which has one input and one output, the input should be
  105. // an `iris.Context` and the output can be any type, that output type
  106. // will be bind-ed to the controller's field, if matching or to the
  107. // controller's methods, if matching.
  108. //
  109. // These dependencies "dependencies" can be changed per-controller as well,
  110. // via controller's `BeforeActivation` and `AfterActivation` methods,
  111. // look the `Handle` method for more.
  112. //
  113. // It returns this Application.
  114. //
  115. // Example: `.Register(loggerService{prefix: "dev"}, func(ctx iris.Context) User {...})`.
  116. func (app *Application) Register(dependencies ...interface{}) *Application {
  117. if len(dependencies) > 0 && len(app.container.Dependencies) == len(hero.BuiltinDependencies) && len(app.Controllers) > 0 {
  118. allControllerNamesSoFar := make([]string, len(app.Controllers))
  119. for i := range app.Controllers {
  120. allControllerNamesSoFar[i] = app.Controllers[i].Name()
  121. }
  122. golog.Warnf(`mvc.Application#Register called after mvc.Application#Handle.
  123. The controllers[%s] may miss required dependencies.
  124. Set the Logger's Level to "debug" to view the active dependencies per controller.`, strings.Join(allControllerNamesSoFar, ","))
  125. }
  126. for _, dependency := range dependencies {
  127. app.container.Register(dependency)
  128. }
  129. return app
  130. }
  131. type (
  132. // Option is an interface which does contain a single `Apply` method that accepts
  133. // a `ControllerActivator`. It can be passed on `Application.Handle` method to
  134. // mdoify the behavior right after the `BeforeActivation` state.
  135. //
  136. // See `GRPC` package-level structure
  137. // and `Version` package-level function too.
  138. Option interface {
  139. Apply(*ControllerActivator)
  140. }
  141. // OptionFunc is the functional type of `Option`.
  142. // Read `Option` docs.
  143. OptionFunc func(*ControllerActivator)
  144. )
  145. // Apply completes the `Option` interface.
  146. func (opt OptionFunc) Apply(c *ControllerActivator) {
  147. opt(c)
  148. }
  149. // Handle serves a controller for the current mvc application's Router.
  150. // It accept any custom struct which its functions will be transformed
  151. // to routes.
  152. //
  153. // If "controller" has `BeforeActivation(b mvc.BeforeActivation)`
  154. // or/and `AfterActivation(a mvc.AfterActivation)` then these will be called between the controller's `.activate`,
  155. // use those when you want to modify the controller before or/and after
  156. // the controller will be registered to the main Iris Application.
  157. //
  158. // It returns this mvc Application.
  159. //
  160. // Usage: `.Handle(new(TodoController))`.
  161. //
  162. // Controller accepts a sub router and registers any custom struct
  163. // as controller, if struct doesn't have any compatible methods
  164. // neither are registered via `ControllerActivator`'s `Handle` method
  165. // then the controller is not registered at all.
  166. //
  167. // A Controller may have one or more methods
  168. // that are wrapped to a handler and registered as routes before the server ran.
  169. // The controller's method can accept any input argument that are previously binded
  170. // via the dependencies or route's path accepts dynamic path parameters.
  171. // The controller's fields are also bindable via the dependencies, either a
  172. // static value (service) or a function (dynamically) which accepts a context
  173. // and returns a single value (this type is being used to find the relative field or method's input argument).
  174. //
  175. // func(c *ExampleController) Get() string |
  176. // (string, string) |
  177. // (string, int) |
  178. // int |
  179. // (int, string |
  180. // (string, error) |
  181. // bool |
  182. // (any, bool) |
  183. // error |
  184. // (int, error) |
  185. // (customStruct, error) |
  186. // customStruct |
  187. // (customStruct, int) |
  188. // (customStruct, string) |
  189. // Result or (Result, error)
  190. // where Get is an HTTP Method func.
  191. //
  192. // Default behavior can be changed through second, variadic, variable "options",
  193. // e.g. Handle(controller, GRPC {Server: grpcServer, Strict: true})
  194. //
  195. // Examples at: https://github.com/kataras/iris/tree/master/_examples/mvc
  196. func (app *Application) Handle(controller interface{}, options ...Option) *Application {
  197. app.handle(controller, options...)
  198. return app
  199. }
  200. // HandleWebsocket handles a websocket specific controller.
  201. // Its exported methods are the events.
  202. // If a "Namespace" field or method exists then namespace is set, otherwise empty namespace.
  203. // Note that a websocket controller is registered and ran under a specific connection connected to a namespace
  204. // and it cannot send HTTP responses on that state.
  205. // However all static and dynamic dependency injection features are working, as expected, like any regular MVC Controller.
  206. func (app *Application) HandleWebsocket(controller interface{}) *websocket.Struct {
  207. c := app.handle(controller)
  208. c.markAsWebsocket()
  209. websocketController := websocket.NewStruct(c.Value).SetInjector(makeInjector(c.injector))
  210. app.websocketControllers = append(app.websocketControllers, websocketController)
  211. return websocketController
  212. }
  213. func makeInjector(s *hero.Struct) websocket.StructInjector {
  214. return func(_ reflect.Type, nsConn *websocket.NSConn) reflect.Value {
  215. v, _ := s.Acquire(websocket.GetContext(nsConn.Conn))
  216. return v
  217. }
  218. }
  219. var _ websocket.ConnHandler = (*Application)(nil)
  220. // GetNamespaces completes the websocket ConnHandler interface.
  221. // It returns a collection of namespace and events that
  222. // were registered through `HandleWebsocket` controllers.
  223. func (app *Application) GetNamespaces() websocket.Namespaces {
  224. if golog.Default.Level == golog.DebugLevel {
  225. websocket.EnableDebug(golog.Default)
  226. }
  227. return websocket.JoinConnHandlers(app.websocketControllers...).GetNamespaces()
  228. }
  229. func (app *Application) handle(controller interface{}, options ...Option) *ControllerActivator {
  230. // initialize the controller's activator, nothing too magical so far.
  231. c := newControllerActivator(app, controller)
  232. // check the controller's "BeforeActivation" or/and "AfterActivation" method(s) between the `activate`
  233. // call, which is simply parses the controller's methods, end-dev can register custom controller's methods
  234. // by using the BeforeActivation's (a ControllerActivation) `.Handle` method.
  235. if before, ok := controller.(interface {
  236. BeforeActivation(BeforeActivation)
  237. }); ok {
  238. before.BeforeActivation(c)
  239. }
  240. for _, opt := range options {
  241. if opt != nil {
  242. opt.Apply(c)
  243. }
  244. }
  245. c.activate()
  246. if after, okAfter := controller.(interface {
  247. AfterActivation(AfterActivation)
  248. }); okAfter {
  249. after.AfterActivation(c)
  250. }
  251. app.Controllers = append(app.Controllers, c)
  252. return c
  253. }
  254. // HandleError registers a `hero.ErrorHandlerFunc` which will be fired when
  255. // application's controllers' functions returns an non-nil error.
  256. // Each controller can override it by implementing the `hero.ErrorHandler`.
  257. func (app *Application) HandleError(handler func(ctx *context.Context, err error)) *Application {
  258. errorHandler := hero.ErrorHandlerFunc(handler)
  259. app.container.GetErrorHandler = func(*context.Context) hero.ErrorHandler {
  260. return errorHandler
  261. }
  262. return app
  263. }
  264. // Clone returns a new mvc Application which has the dependencies
  265. // of the current mvc Application's `Dependencies` and its `ErrorHandler`.
  266. //
  267. // Example: `.Clone(app.Party("/path")).Handle(new(TodoSubController))`.
  268. func (app *Application) Clone(party router.Party) *Application {
  269. cloned := newApp(party, app.container.Clone())
  270. return cloned
  271. }
  272. // Party returns a new child mvc Application based on the current path + "relativePath".
  273. // The new mvc Application has the same dependencies of the current mvc Application,
  274. // until otherwise specified later manually.
  275. //
  276. // The router's root path of this child will be the current mvc Application's root path + "relativePath".
  277. func (app *Application) Party(relativePath string, middleware ...context.Handler) *Application {
  278. return app.Clone(app.Router.Party(relativePath, middleware...))
  279. }