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.

519 lines
15KB

  1. package capstan
  2. import (
  3. "context"
  4. "fmt"
  5. "net"
  6. "net/http"
  7. "path/filepath"
  8. "strings"
  9. "sync"
  10. "git.destrealm.org/go/capstan/mappers"
  11. "git.destrealm.org/go/capstan/render"
  12. "github.com/go-chi/chi"
  13. )
  14. var reserved = map[string]struct{}{
  15. "Index": struct{}{}, "Connect": struct{}{},
  16. "Delete": struct{}{}, "Get": struct{}{},
  17. "Head": struct{}{}, "Options": struct{}{},
  18. "Post": struct{}{}, "Put": struct{}{},
  19. "Patch": struct{}{}, "Trace": struct{}{},
  20. "WebSocket": struct{}{}, "Bind": struct{}{},
  21. "Mapper": struct{}{}, "Init": struct{}{},
  22. }
  23. type ListenerTLSConfig struct {
  24. // CertPath is the location of a TLS certificate on the file system.
  25. CertPath string
  26. // KeyPath is the location of the server's private key on the file system.
  27. KeyPath string
  28. // CertBytes is a PEM-encoded certificate.
  29. CertBytes []byte
  30. // KeyBytes is a PEM-encoded private key.
  31. KeyBytes []byte
  32. }
  33. type socketMeta struct {
  34. Addr string
  35. IsUnix bool
  36. IsTLS bool
  37. FD uintptr
  38. File string
  39. }
  40. type namedListeners struct {
  41. tcp net.Listener
  42. tls net.Listener
  43. sock net.Listener
  44. }
  45. type Router struct {
  46. // Capstan server reference which contains most high-order things not
  47. // involving direct management of routes and communication, like
  48. // configuration.
  49. app Application
  50. // The router basePath is used to mount the route at a location other than
  51. // "/". If unspecified, this will default to the root path ("/").
  52. basePath string
  53. // RouterGroup indicates this router is bound to a group. Routers associated
  54. // with a group don't handle the listener themselves and instead only
  55. // maintaing the ServerHTTP handler.
  56. group *RouterGroup
  57. // Symbolic name assigned by RouterGroup.Name(). When set, this will control
  58. // the Route.Prefix which affects route mapping via URLFor(). e.g., given a
  59. // controller, IndexController, and a RouterGroup with the name "products,"
  60. // URLFor() will return the URL associated with that route using the string
  61. // "products.IndexController."
  62. name string
  63. // go-chi muxer.
  64. mux chi.Router
  65. // ProxyHandler proxies requests to the go-chi muxer and allows for route
  66. // rebinding. Rebinding is useful when remounting, adding, or removing
  67. // handlers. During a rebind, the go-chi muxer is reinitialized,
  68. // reconfigured, and once this process is complex, the proxy switches
  69. // requests over to the new muxer.
  70. proxy ProxyHandler
  71. // URLMapper. This is the master mapper if used as the parent router;
  72. // routers associated with RouterGroups will reference the mapper associated
  73. // with their parent (the master).
  74. urls *URLMapper
  75. // DependencyMapper. This maps dependencies for injection against registered
  76. // controllers. Routers assigned to RouterGroups will reference their
  77. // parent's mapper.
  78. dependencies *mappers.DependencyMapper
  79. // Default template renderer.
  80. renderer render.Renderer
  81. // Shutdown timeout context for sockets. By default, this is 10 seconds
  82. // after which the context will abort. If the server is in shutdown mode
  83. // when this times out, it will terminate all open connections.
  84. //
  85. // This will eventually be made configurable.
  86. shutdownCtx context.Context
  87. // Slice of listeners that have been added to the route. This contains all
  88. // listeners configured by the Listen* options in addition to any others
  89. // that have been configured.
  90. listeners []net.Listener
  91. // Slice containing our HTTPServer wrapper. This is used to derive listeners
  92. // for comparison during shutdown of individual listeners.
  93. servers []*HTTPServer
  94. // WaitGroup used to halt termination progress on cancel until all servers
  95. // have returned.
  96. wg sync.WaitGroup
  97. // namedListeners are those listeners that have been assigned from the
  98. // configuration instance Listen* options. At present, this contains a
  99. // standard TCP listener, a TLS listener, and a Unix socket listener.
  100. //
  101. // Currently, this is used to determine if the server is listening on plain
  102. // TCP, TLS, and/or Unix sockets. See the Router.Has* functions.
  103. namedListeners *namedListeners
  104. // Slice containing all afterResponse callbacks.
  105. //
  106. // If this router is assigned to a RouteGroup, this will receive
  107. // afterResponse targets from the parent Router.
  108. afterResponse []func(Context, error) error
  109. // Slice containing all beforeResponse callbacks.
  110. //
  111. // If this router is assigned to a RouteGroup, this will receive
  112. // afterResponse targets from the parent Router.
  113. beforeResponse []func(Context, *Route) error
  114. // Middleware assigned to this Router.
  115. //
  116. // If this router is assigned to a RouteGroup, this will be populated with
  117. // middleware from the parent Router.
  118. middleware []func(http.Handler) http.Handler
  119. // We store a map of the routes as they are added for duplicate prevention
  120. // and rebuilding go-chi's internal routing state on removal.
  121. cnames map[string]struct{}
  122. // We also map the routes based on their path. This is mapped based on
  123. // Capstan-style paths, not Chi.
  124. pathmap map[string]*Route
  125. // routeGroups maps a base path component to the RouterGroup that it belongs
  126. // to. This is used primarily for rebinding RouterGroups.
  127. routerGroups map[string]*RouterGroup
  128. // Reserved contains a map of names that have been manually reserved from
  129. // use as controller methods. Methods that appear in this list cannot be
  130. // bound to a route.
  131. //
  132. // In most circumstances, this should never be necessary, but sometimes it
  133. // may be entirely sensible to create a method that matches the Endpoint
  134. // function signature. This provides callers with an option to ignore these
  135. // methods.
  136. reserved map[string]struct{}
  137. // Route map.
  138. routes []*Route
  139. // Context generation function. If this is nil, this will be set to
  140. // capstan.MakeRouteContext.
  141. contextMaker ContextMaker
  142. // Error handling function. If this is nil, the default error handler will
  143. // be used instead, which outputs either HTML or JSON responses depending on
  144. // the configured content-type for the context.
  145. //
  146. // Context instances passed to the errorFunc are NOT guaranteed to have
  147. // access to route information as not all error conditions are due to
  148. // failures within the route (404).
  149. errorFunc func(ctx Context)
  150. sync.RWMutex
  151. }
  152. func NewRouter(app Application) *Router {
  153. router := NewEmptyRouter()
  154. router.app = app
  155. router.mux = chi.NewRouter()
  156. router.urls = NewURLMapper(app.Config().Server)
  157. router.dependencies = app.Dependencies()
  158. if app.Config().Server.BasePath != "" {
  159. router.basePath = app.Config().Server.BasePath
  160. if !strings.HasPrefix(router.basePath, "/") {
  161. router.basePath = "/" + router.basePath
  162. }
  163. if strings.HasSuffix(router.basePath, "/") {
  164. router.basePath = router.basePath[:len(router.basePath)-1]
  165. }
  166. }
  167. if app.Config().Server.IdleConnectionTimeout == 0 {
  168. app.Config().Server.IdleConnectionTimeout = 10
  169. }
  170. if app.Config().Server.HeaderTimeout == 0 {
  171. app.Config().Server.HeaderTimeout = 60
  172. }
  173. if app.Config().Server.ShutdownTimeout == 0 {
  174. app.Config().Server.ShutdownTimeout = 10
  175. }
  176. if app.Config().Server.MaxRequestDuration == 0 {
  177. app.Config().Server.MaxRequestDuration = 30
  178. }
  179. if app.Config().Server.MaxHeaderLength == 0 {
  180. app.Config().Server.MaxHeaderLength = 1 << 20
  181. }
  182. router.proxy = newProxy(&proxyOptions{
  183. ListenAddr: app.Config().Server.ListenAddress,
  184. Hostname: app.Config().Server.CalculatedHostname(),
  185. EnforcePort: app.Config().Server.EnforcePortInHost,
  186. EnforceHost: app.Config().Server.EnforceHostname,
  187. MaxRequestDuration: app.Config().Server.MaxRequestDuration,
  188. })
  189. router.mux.NotFound(func(w http.ResponseWriter, r *http.Request) {
  190. ctx := MakeRouteContext(w, r, nil)
  191. ctx.SetCode(404)
  192. router.HTTPError(ctx)
  193. })
  194. router.errorFunc = defaultErrorFunc(router)
  195. err, errc, close := InitNetwork()
  196. if err != nil {
  197. app.Logger().Error("error initializing network state:", err)
  198. }
  199. go func() {
  200. for {
  201. select {
  202. case err := <-errc:
  203. app.Logger().Error("network error:", err)
  204. router.Close()
  205. case <-close:
  206. router.Close()
  207. }
  208. }
  209. }()
  210. AddPreLaunchFunc(func() error {
  211. for _, route := range router.routes {
  212. if route.Renderer != nil {
  213. route.Renderer.CloseNotifier()
  214. }
  215. }
  216. return nil
  217. })
  218. router.init()
  219. return router
  220. }
  221. func NewEmptyRouter() *Router {
  222. return &Router{
  223. cnames: make(map[string]struct{}),
  224. middleware: make([]func(http.Handler) http.Handler, 0),
  225. pathmap: make(map[string]*Route),
  226. routerGroups: make(map[string]*RouterGroup),
  227. routes: make([]*Route, 0),
  228. wg: sync.WaitGroup{},
  229. shutdownCtx: context.Background(),
  230. servers: make([]*HTTPServer, 0),
  231. namedListeners: &namedListeners{},
  232. reserved: make(map[string]struct{}),
  233. contextMaker: MakeRouteContext,
  234. }
  235. }
  236. // Mux returns the chi.Router mux associated with this Router instance.
  237. func (r *Router) Mux() chi.Router {
  238. return r.mux
  239. }
  240. // Proxy returns the ProxyHandler associated with this Router instance.
  241. func (r *Router) Proxy() ProxyHandler {
  242. return r.proxy
  243. }
  244. func (r *Router) ServeHTTP(w http.ResponseWriter, rq *http.Request) {
  245. r.proxy.ServeHTTP(w, rq)
  246. }
  247. func (r *Router) SetupMuxer() {
  248. r.proxy.Switch(r.mux)
  249. }
  250. // URLs retrieves the Router's configured URLMapper.
  251. func (r *Router) URLs() *URLMapper {
  252. return r.urls
  253. }
  254. // SetProxy changes the proxy for this router.
  255. func (r *Router) SetProxy(p ProxyHandler) {
  256. r.proxy = p
  257. }
  258. // SetRender configures a global render.Renderer for all endpoints handled by
  259. // this router. This may be overridden by renderer specified via the
  260. // BaseController options for any given route. If this is never set, the
  261. // Context.Render() becomes a noop and returns the noop implementation of
  262. // Renderer.
  263. func (r *Router) SetRenderer(renderer render.Renderer) {
  264. r.renderer = renderer
  265. }
  266. // AfterResponseFuncc configures a function to call after the response has been
  267. // handled. Response data will have already been dispatched by the time this
  268. // function is called; consequently, care should be taken not to manipulate the
  269. // response.
  270. func (r *Router) AfterResponseFunc(fn func(Context, error) error) {
  271. r.afterResponse = append(r.afterResponse, fn)
  272. }
  273. // BeforeResponseFunc configures a function to call before the response is about
  274. // to be handled. This function must accept the route as its second argument.
  275. // Errors returned by this function will, depending on their nature, prevent the
  276. // route handler from being called.
  277. func (r *Router) BeforeResponseFunc(fn func(Context, *Route) error) {
  278. r.beforeResponse = append(r.beforeResponse, fn)
  279. }
  280. // Group creates and returns a new router group.
  281. func (r *Router) Group(path string) *RouterGroup {
  282. if _, ok := r.routerGroups[path]; ok {
  283. panic("cannot bind group: path in use:" + path)
  284. }
  285. group := NewRouterGroup(path, r)
  286. r.routerGroups[path] = group
  287. return group
  288. }
  289. // Middleware configures Router-global middleware for all routes bound to this
  290. // Router.
  291. func (r *Router) Middleware(middleware ...func(http.Handler) http.Handler) *Router {
  292. r.middleware = append(r.middleware, middleware...)
  293. r.mux.Use(middleware...)
  294. return r
  295. }
  296. // Rebind() incrementally rebinds all routes associated with this Router. This
  297. // is useful when remapping a route's endpoint. A new muxer is returned with all
  298. // the bindings set. This function should be used with the proxy Switch method.
  299. //
  300. // When a Rebind is called, the routes are not immediately rebound. Instead, a
  301. // new muxer is created with the same configuration already used by the existing
  302. // muxer and all routes associated with this Router are bound, individually, to
  303. // this muxer. Only when the routes have successfully been bound will the muxer
  304. // be returned.
  305. //
  306. // FIXME: This needs to work across subrouters as well.
  307. func (r *Router) Rebind() chi.Router {
  308. r.mux = chi.NewRouter()
  309. r.mux.Use(r.middleware...)
  310. for _, route := range r.routes {
  311. bindRoute(r, route)
  312. }
  313. return r.mux
  314. }
  315. // ReplacePath swaps a route endpoint for the route associated with `from` to
  316. // the new endpoint `to`. Rebind is triggered if this call is successful.
  317. //
  318. // This method also switches the proxy handler to the one returned by Rebind.
  319. func (r *Router) ReplacePath(from, to string) {
  320. routes := make([]*Route, 0)
  321. cpath := r.ParseRoute(from).route
  322. tpath := r.ParseRoute(to).route
  323. if route, ok := r.pathmap[from]; ok {
  324. delete(r.pathmap, from)
  325. r.pathmap[to] = route
  326. for _, rt := range r.routes {
  327. if rt.Path != cpath {
  328. routes = append(routes, rt)
  329. } else {
  330. rt.Path = tpath
  331. routes = append(routes, rt)
  332. }
  333. }
  334. }
  335. r.Lock()
  336. r.routes = routes
  337. r.mux = r.Rebind()
  338. if group, ok := r.routerGroups[from]; ok {
  339. if _, ok := r.routerGroups[to]; ok {
  340. panic("unable to rebind group to path: path exists: " + to)
  341. }
  342. group.Rebase(to)
  343. group.RebindSelf()
  344. }
  345. delete(r.routerGroups, from)
  346. r.proxy.Switch(r.mux)
  347. r.Unlock()
  348. }
  349. // Unmount removes the route associated with `path` from the router. This calls
  350. // Rebind if successful.
  351. //
  352. // This method also switches the proxy handler to the one returned by Rebind.
  353. func (r *Router) Unmount(path string) {
  354. routes := make([]*Route, 0)
  355. cpath := r.ParseRoute(path).route
  356. r.Lock()
  357. if route, ok := r.pathmap[path]; ok {
  358. delete(r.cnames, route.ForName())
  359. delete(r.pathmap, path)
  360. for _, rt := range r.routes {
  361. if rt.Path != cpath {
  362. routes = append(routes, rt)
  363. }
  364. }
  365. r.routes = routes
  366. r.mux = r.Rebind()
  367. r.proxy.Switch(r.mux)
  368. }
  369. r.Unlock()
  370. }
  371. func (r *Router) ReservedFunctions(reserved ...string) {
  372. for _, res := range reserved {
  373. r.reserved[res] = struct{}{}
  374. }
  375. }
  376. func (r *Router) HTTPError(ctx Context) {
  377. errfn := r.errorFunc
  378. if errfn == nil {
  379. errfn = defaultErrorFunc(r)
  380. }
  381. // If the context code is 0 and we're handling an error, this shouldn't
  382. // normally occur.
  383. if ctx.Code() == 0 {
  384. return
  385. }
  386. errfn(ctx)
  387. }
  388. func defaultErrorFunc(router *Router) func(Context) {
  389. return func(ctx Context) {
  390. json := false
  391. if _, err := ctx.Renderer(); err != nil && router.renderer != nil {
  392. ctx.SetRenderer(router.renderer)
  393. }
  394. if e := ctx.Error(); len(e) > 0 {
  395. if len(e) > 24 && e[len(e)-24:] == "connection reset by peer" {
  396. return
  397. }
  398. if len(e) > 11 && e[len(e)-11:] == "broken pipe" {
  399. return
  400. }
  401. }
  402. ctx.WriteHeader(ctx.Code())
  403. ctx.ContentType(TextHTML)
  404. // This is not the ideal solution since we only examine a) the presence of
  405. // accept headers and b) whether they contain anything that looks like JSON.
  406. accept := ctx.Request().Header.Get("accept")
  407. if strings.Contains(accept, "application/json") {
  408. json = true
  409. ctx.ContentType(ApplicationJSON)
  410. }
  411. if strings.Contains(accept, "text/json") {
  412. json = true
  413. ctx.ContentType(ApplicationJSON)
  414. }
  415. if err := httpErrorPage(ctx, json); err != nil {
  416. router.app.Logger().Error("unable to write error response:", err)
  417. }
  418. }
  419. }
  420. // RenderErrorFunc is an error function that uses the Context-specified
  421. // renderer. If the Context has no renderer enabled, or otherwise encounters and
  422. // error, this will return an error result from the default error handling
  423. // function.
  424. //
  425. // This function encapsulated multiple layers of functions to reduce the
  426. // responsibility of the caller.
  427. func RenderErrorFunc(path, filepattern string) func(*Router) func(Context) {
  428. return func(router *Router) func(Context) {
  429. return func(ctx Context) {
  430. if _, err := ctx.Renderer(); err != nil && router.renderer != nil {
  431. ctx.SetRenderer(router.renderer)
  432. }
  433. file := fmt.Sprintf(filepattern, ctx.Code())
  434. err := ctx.Render(filepath.Join(path, file), render.MapValues{})
  435. if err != nil {
  436. defaultErrorFunc(router)
  437. }
  438. }
  439. }
  440. }