Capstan is a Golang web framework that shares some similarities with others in its segment.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

279 lines
6.1KB

  1. package capstan
  2. import (
  3. "net/http"
  4. "os"
  5. "os/signal"
  6. "strings"
  7. "sync"
  8. "git.destrealm.org/go/capstan/config"
  9. . "git.destrealm.org/go/capstan/errors"
  10. "git.destrealm.org/go/capstan/mappers"
  11. "git.destrealm.org/go/capstan/render"
  12. "git.destrealm.org/go/capstan/session"
  13. "git.destrealm.org/go/logging"
  14. "github.com/go-chi/chi/middleware"
  15. )
  16. type Application struct {
  17. router *Router
  18. logger *logging.Log
  19. config *config.ServerConfig
  20. dependencies *mappers.DependencyMapper
  21. extensions *ExtensionManager
  22. subservers map[string]*Application
  23. shutdownHooks []ShutdownHook
  24. master bool
  25. sync.RWMutex
  26. }
  27. func New(conf ...*config.ServerConfig) *Application {
  28. dependencies := mappers.NewDependencyMapper()
  29. app := &Application{
  30. logger: logging.MustGetLogger("main"),
  31. dependencies: dependencies,
  32. subservers: make(map[string]*Application),
  33. shutdownHooks: make([]ShutdownHook, 0),
  34. master: true,
  35. }
  36. if len(conf) == 1 {
  37. app.config = conf[0]
  38. } else {
  39. app.config = &config.ServerConfig{}
  40. }
  41. // ListenAddress wasn't set, so we'll pick something reasonable.
  42. if app.config.ListenAddress == "" {
  43. app.config.ListenAddress = "localhost:8080"
  44. }
  45. // Hostname wasn't set; interpret it as the listen address. This will need
  46. // changing if we support multiple domains names as an option.
  47. if app.config.Hostname == "" {
  48. if app.config.ListenAddress[0] == ':' {
  49. app.config.Hostname = "localhost"
  50. app.config.FullHost = "localhost" + app.config.ListenAddress[strings.IndexRune(app.config.ListenAddress, ':'):]
  51. } else {
  52. app.config.Hostname = app.config.ListenAddress
  53. app.config.FullHost = app.config.Hostname
  54. }
  55. } else {
  56. app.config.FullHost = app.config.Hostname
  57. }
  58. if app.config.ListenAddress != "" {
  59. if app.config.Protocol == "" {
  60. app.config.Protocol = "http"
  61. }
  62. }
  63. if app.config.ListenAddressTLS != "" {
  64. if app.config.Protocol == "" {
  65. app.config.Protocol = "https"
  66. }
  67. }
  68. // Init session cookies.
  69. if app.config.Session == nil {
  70. app.config.Session = &config.SessionConfig{
  71. CookieOptions: &http.Cookie{},
  72. }
  73. }
  74. if app.config.Session.CookieOptions.Domain == "" {
  75. app.config.Session.CookieOptions.Domain = app.config.Hostname
  76. }
  77. app.router = NewRouter(app)
  78. app.DefaultMiddleware()
  79. app.LoadSession()
  80. dependencies.Register("app", app)
  81. app.extensions = NewExtensionManager(app)
  82. return app
  83. }
  84. func (a *Application) NewServer(name string, conf *config.ServerConfig) (*Application, error) {
  85. server := &Application{
  86. logger: a.logger,
  87. dependencies: mappers.CopyDependencyMapper(a.dependencies),
  88. config: conf,
  89. }
  90. if conf.Hostname == "" {
  91. return nil, ErrNoHostname
  92. }
  93. if conf.Session == nil {
  94. conf.Session = &config.SessionConfig{
  95. CookieOptions: &http.Cookie{},
  96. }
  97. }
  98. if conf.Session.CookieOptions.Domain == "" {
  99. conf.Session.CookieOptions.Domain = conf.Hostname
  100. }
  101. server.router = NewRouter(server)
  102. server.DefaultMiddleware()
  103. server.LoadSession()
  104. server.dependencies.Register("app::"+name, server)
  105. a.Lock()
  106. a.subservers[name] = server
  107. a.Unlock()
  108. server.extensions = NewExtensionManager(server)
  109. return server, nil
  110. }
  111. func (a *Application) LoadSession() {
  112. if a.config.Session != nil {
  113. session.New(a.config.Session)
  114. }
  115. }
  116. func (a *Application) BindGroup(path string) *RouterGroup {
  117. return a.router.Group(path)
  118. }
  119. func (a *Application) Bind(controller Controller) error {
  120. return a.router.Bind(controller)
  121. }
  122. func (a *Application) ReplaceController(from, to Controller) error {
  123. return nil
  124. }
  125. func (a *Application) ReplacePath(from, to string) {
  126. a.router.ReplacePath(from, to)
  127. }
  128. func (a *Application) Unmount(path string) {
  129. a.router.Unmount(path)
  130. }
  131. func (a *Application) UnmountController(controller Controller) error {
  132. return nil
  133. }
  134. func (a *Application) UnmountGroup(path string) error {
  135. return nil
  136. }
  137. func (a *Application) DefaultMiddleware() {
  138. noColor := NoColor != ""
  139. if v, ok := os.LookupEnv("NO_COLOR"); ok {
  140. switch strings.ToLower(v) {
  141. case "1":
  142. fallthrough
  143. case "yes":
  144. fallthrough
  145. case "true":
  146. noColor = true
  147. }
  148. }
  149. if a.config.RealIP {
  150. a.router.Middleware(middleware.RealIP)
  151. }
  152. logger := a.logger
  153. if a.config.RequestLog != "" {
  154. logger = logging.MustGetLogger(a.config.RequestLog)
  155. //logger.SetLevel(a.config.LogLevel)
  156. }
  157. middleware.DefaultLogger = middleware.RequestLogger(&middleware.DefaultLogFormatter{
  158. Logger: logger,
  159. NoColor: noColor,
  160. })
  161. a.router.Middleware(middleware.Logger)
  162. }
  163. func (a *Application) SetMiddleware(middleware ...func(http.Handler) http.Handler) *Application {
  164. a.router.Middleware(middleware...)
  165. return a
  166. }
  167. func (a *Application) SetDefaultRenderer(renderer render.Renderer) *Application {
  168. a.router.SetRenderer(renderer)
  169. return a
  170. }
  171. func (a *Application) RegisterShutdownHook(hook ShutdownHook) {
  172. a.shutdownHooks = append(a.shutdownHooks, hook)
  173. }
  174. // TODO: Replace with a run method or something suitable for CLI (ab)use. In
  175. // particular, make sure we can rename the application as appropriate.
  176. func (a *Application) Listen() error {
  177. wg := sync.WaitGroup{}
  178. if len(a.shutdownHooks) > 0 {
  179. interrupt := make(chan os.Signal, 1)
  180. signal.Notify(interrupt, os.Interrupt)
  181. wg.Add(1)
  182. go func() {
  183. <-interrupt
  184. for _, hook := range a.shutdownHooks {
  185. hook()
  186. }
  187. wg.Done()
  188. }()
  189. }
  190. err := a.router.Listen()
  191. wg.Wait()
  192. return err
  193. }
  194. func (a *Application) Stop() {
  195. a.router.Close()
  196. }
  197. func (a *Application) Config() *config.ServerConfig {
  198. return a.config
  199. }
  200. func (a *Application) Dependencies() *mappers.DependencyMapper {
  201. return a.dependencies
  202. }
  203. func (a *Application) IsMaster() bool {
  204. return a.master
  205. }
  206. func (a *Application) Logger() *logging.Log {
  207. return a.logger
  208. }
  209. func (a *Application) Router() *Router {
  210. return a.router
  211. }
  212. func (a *Application) Extensions() *ExtensionManager {
  213. return a.extensions
  214. }
  215. func (a *Application) SubServer(name string) (*Application, bool) {
  216. if server, ok := a.subservers[name]; ok {
  217. return server, ok
  218. }
  219. return nil, false
  220. }
  221. func (a *Application) SubServers() map[string]*Application {
  222. return a.subservers
  223. }
  224. // Reload the Capstan server. This reconfigures all dependent services and
  225. // rebinds all endpoints.
  226. func (a *Application) Reload(config *config.ServerConfig) {
  227. a.router.Rebind()
  228. }