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.

509 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())
  157. router.dependencies = app.Dependencies()
  158. if app.Config().BasePath != "" {
  159. router.basePath = app.Config().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().IdleConnectionTimeout == 0 {
  168. app.Config().IdleConnectionTimeout = 10
  169. }
  170. if app.Config().HeaderTimeout == 0 {
  171. app.Config().HeaderTimeout = 60
  172. }
  173. if app.Config().ShutdownTimeout == 0 {
  174. app.Config().ShutdownTimeout = 10
  175. }
  176. if app.Config().MaxRequestDuration == 0 {
  177. app.Config().MaxRequestDuration = 30
  178. }
  179. if app.Config().MaxHeaderLength == 0 {
  180. app.Config().MaxHeaderLength = 1 << 20
  181. }
  182. router.proxy = newProxy(&proxyOptions{
  183. ListenAddr: app.Config().ListenAddress,
  184. Hostname: app.Config().Hostname,
  185. EnforcePort: app.Config().EnforcePortInHost,
  186. EnforceHost: app.Config().EnforceHostname,
  187. MaxRequestDuration: app.Config().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. func (r *Router) ServeHTTP(w http.ResponseWriter, rq *http.Request) {
  241. r.proxy.ServeHTTP(w, rq)
  242. }
  243. func (r *Router) SetupMuxer() {
  244. r.proxy.Switch(r.mux)
  245. }
  246. // URLs retrieves the Router's configured URLMapper.
  247. func (r *Router) URLs() *URLMapper {
  248. return r.urls
  249. }
  250. // SetRender configures a global render.Renderer for all endpoints handled by
  251. // this router. This may be overridden by renderer specified via the
  252. // BaseController options for any given route. If this is never set, the
  253. // Context.Render() becomes a noop and returns the noop implementation of
  254. // Renderer.
  255. func (r *Router) SetRenderer(renderer render.Renderer) {
  256. r.renderer = renderer
  257. }
  258. // AfterResponseFuncc configures a function to call after the response has been
  259. // handled. Response data will have already been dispatched by the time this
  260. // function is called; consequently, care should be taken not to manipulate the
  261. // response.
  262. func (r *Router) AfterResponseFunc(fn func(Context, error) error) {
  263. r.afterResponse = append(r.afterResponse, fn)
  264. }
  265. // BeforeResponseFunc configures a function to call before the response is about
  266. // to be handled. This function must accept the route as its second argument.
  267. // Errors returned by this function will, depending on their nature, prevent the
  268. // route handler from being called.
  269. func (r *Router) BeforeResponseFunc(fn func(Context, *Route) error) {
  270. r.beforeResponse = append(r.beforeResponse, fn)
  271. }
  272. // Group creates and returns a new router group.
  273. func (r *Router) Group(path string) *RouterGroup {
  274. if _, ok := r.routerGroups[path]; ok {
  275. panic("cannot bind group: path in use:" + path)
  276. }
  277. group := NewRouterGroup(path, r)
  278. r.routerGroups[path] = group
  279. return group
  280. }
  281. // Middleware configures Router-global middleware for all routes bound to this
  282. // Router.
  283. func (r *Router) Middleware(middleware ...func(http.Handler) http.Handler) *Router {
  284. r.middleware = append(r.middleware, middleware...)
  285. r.mux.Use(middleware...)
  286. return r
  287. }
  288. // Rebind() incrementally rebinds all routes associated with this Router. This
  289. // is useful when remapping a route's endpoint. A new muxer is returned with all
  290. // the bindings set. This function should be used with the proxy Switch method.
  291. //
  292. // When a Rebind is called, the routes are not immediately rebound. Instead, a
  293. // new muxer is created with the same configuration already used by the existing
  294. // muxer and all routes associated with this Router are bound, individually, to
  295. // this muxer. Only when the routes have successfully been bound will the muxer
  296. // be returned.
  297. //
  298. // FIXME: This needs to work across subrouters as well.
  299. func (r *Router) Rebind() chi.Router {
  300. r.mux = chi.NewRouter()
  301. r.mux.Use(r.middleware...)
  302. for _, route := range r.routes {
  303. bindRoute(r, route)
  304. }
  305. return r.mux
  306. }
  307. // ReplacePath swaps a route endpoint for the route associated with `from` to
  308. // the new endpoint `to`. Rebind is triggered if this call is successful.
  309. //
  310. // This method also switches the proxy handler to the one returned by Rebind.
  311. func (r *Router) ReplacePath(from, to string) {
  312. routes := make([]*Route, 0)
  313. cpath := r.ParseRoute(from).route
  314. tpath := r.ParseRoute(to).route
  315. if route, ok := r.pathmap[from]; ok {
  316. delete(r.pathmap, from)
  317. r.pathmap[to] = route
  318. for _, rt := range r.routes {
  319. if rt.Path != cpath {
  320. routes = append(routes, rt)
  321. } else {
  322. rt.Path = tpath
  323. routes = append(routes, rt)
  324. }
  325. }
  326. }
  327. r.Lock()
  328. r.routes = routes
  329. r.mux = r.Rebind()
  330. if group, ok := r.routerGroups[from]; ok {
  331. if _, ok := r.routerGroups[to]; ok {
  332. panic("unable to rebind group to path: path exists: " + to)
  333. }
  334. group.Rebase(to)
  335. group.RebindSelf()
  336. }
  337. delete(r.routerGroups, from)
  338. r.proxy.Switch(r.mux)
  339. r.Unlock()
  340. }
  341. // Unmount removes the route associated with `path` from the router. This calls
  342. // Rebind if successful.
  343. //
  344. // This method also switches the proxy handler to the one returned by Rebind.
  345. func (r *Router) Unmount(path string) {
  346. routes := make([]*Route, 0)
  347. cpath := r.ParseRoute(path).route
  348. r.Lock()
  349. if route, ok := r.pathmap[path]; ok {
  350. delete(r.cnames, route.ForName())
  351. delete(r.pathmap, path)
  352. for _, rt := range r.routes {
  353. if rt.Path != cpath {
  354. routes = append(routes, rt)
  355. }
  356. }
  357. r.routes = routes
  358. r.mux = r.Rebind()
  359. r.proxy.Switch(r.mux)
  360. }
  361. r.Unlock()
  362. }
  363. func (r *Router) ReservedFunctions(reserved ...string) {
  364. for _, res := range reserved {
  365. r.reserved[res] = struct{}{}
  366. }
  367. }
  368. func (r *Router) HTTPError(ctx Context) {
  369. errfn := r.errorFunc
  370. if errfn == nil {
  371. errfn = defaultErrorFunc(r)
  372. }
  373. // If the context code is 0 and we're handling an error, this shouldn't
  374. // normally occur.
  375. if ctx.Code() == 0 {
  376. return
  377. }
  378. errfn(ctx)
  379. }
  380. func defaultErrorFunc(router *Router) func(Context) {
  381. return func(ctx Context) {
  382. json := false
  383. if _, err := ctx.Renderer(); err != nil && router.renderer != nil {
  384. ctx.SetRenderer(router.renderer)
  385. }
  386. if e := ctx.Error(); len(e) > 0 {
  387. if len(e) > 24 && e[len(e)-24:] == "connection reset by peer" {
  388. return
  389. }
  390. if len(e) > 11 && e[len(e)-11:] == "broken pipe" {
  391. return
  392. }
  393. }
  394. ctx.WriteHeader(ctx.Code())
  395. ctx.ContentType(TextHTML)
  396. // This is not the ideal solution since we only examine a) the presence of
  397. // accept headers and b) whether they contain anything that looks like JSON.
  398. accept := ctx.Request().Header.Get("accept")
  399. if strings.Contains(accept, "application/json") {
  400. json = true
  401. ctx.ContentType(ApplicationJSON)
  402. }
  403. if strings.Contains(accept, "text/json") {
  404. json = true
  405. ctx.ContentType(ApplicationJSON)
  406. }
  407. if err := httpErrorPage(ctx, json); err != nil {
  408. router.app.Logger().Error("unable to write error response:", err)
  409. }
  410. }
  411. }
  412. // RenderErrorFunc is an error function that uses the Context-specified
  413. // renderer. If the Context has no renderer enabled, or otherwise encounters and
  414. // error, this will return an error result from the default error handling
  415. // function.
  416. //
  417. // This function encapsulated multiple layers of functions to reduce the
  418. // responsibility of the caller.
  419. func RenderErrorFunc(path, filepattern string) func(*Router) func(Context) {
  420. return func(router *Router) func(Context) {
  421. return func(ctx Context) {
  422. if _, err := ctx.Renderer(); err != nil && router.renderer != nil {
  423. ctx.SetRenderer(router.renderer)
  424. }
  425. file := fmt.Sprintf(filepattern, ctx.Code())
  426. err := ctx.Render(filepath.Join(path, file), render.MapValues{})
  427. if err != nil {
  428. defaultErrorFunc(router)
  429. }
  430. }
  431. }
  432. }