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.

189 lines
4.4KB

  1. package capstan
  2. import (
  3. "fmt"
  4. "net"
  5. "net/http"
  6. "path"
  7. "strconv"
  8. "strings"
  9. "git.destrealm.org/go/capstan/mappers"
  10. "git.destrealm.org/go/logging"
  11. "github.com/go-chi/chi"
  12. )
  13. // DependencyMapper is a convenience function for creating new dependency
  14. // mappers.
  15. func DependencyMapper() *mappers.DependencyMapper {
  16. return mappers.NewDependencyMapper()
  17. }
  18. // Private method for binding individual routers to the backend Chi muxer.
  19. // Routes may be bound multiple times, depending on the configuration of their
  20. // trailing slash handlers, and some bindings may redirect to other routes.
  21. func bindRoute(router *Router, route *Route) {
  22. var mux chi.Router
  23. if len(route.Middleware) > 0 {
  24. mux = router.Mux().With(route.Middleware...)
  25. } else {
  26. mux = router.Mux()
  27. }
  28. route.Path = path.Join(router.basePath, route.Path)
  29. path := route.Path
  30. if path == "/" {
  31. mux.Method(route.Method, route.Path, route)
  32. return
  33. }
  34. switch {
  35. case route.OptionalSlash && route.SlashIsDefault:
  36. if !strings.HasSuffix(route.Path, "/") {
  37. route.Path = route.Path + "/"
  38. }
  39. if strings.HasSuffix(path, "/") {
  40. path = path[:len(path)-1]
  41. }
  42. mux.Method(route.Method, route.Path, route)
  43. mux.Method(route.Method, path,
  44. redirectRouteHandler(route))
  45. return
  46. case route.OptionalSlash && !route.SlashIsDefault:
  47. if strings.HasSuffix(route.Path, "/") {
  48. route.Path = route.Path[:len(route.Path)-1]
  49. }
  50. if !strings.HasSuffix(path, "/") {
  51. path = path + "/"
  52. }
  53. mux.Method(route.Method, route.Path, route)
  54. mux.Method(route.Method, path,
  55. redirectRouteHandler(route))
  56. return
  57. case route.MandatorySlash:
  58. if !strings.HasSuffix(path, "/") {
  59. path = path + "/"
  60. }
  61. mux.Method(route.Method, path, route)
  62. return
  63. case route.MandatoryNoSlash:
  64. if strings.HasSuffix(path, "/") {
  65. path = path[:len(path)-1]
  66. }
  67. mux.Method(route.Method, path, route)
  68. return
  69. default:
  70. // TODO: Remove me or panic as this shouldn't be triggered. Possibly
  71. // consider unit tests to trigger this path.
  72. mux.Method(route.Method, route.Path, route)
  73. }
  74. }
  75. // Utility helper for determining if addr contains a privileged port, i.e. one
  76. // that is < 1024.
  77. func isPrivPort(addr string) bool {
  78. _, p, err := net.SplitHostPort(addr)
  79. if err != nil {
  80. return false
  81. }
  82. if port, err := strconv.Atoi(p); err == nil {
  83. return port < 1024
  84. }
  85. return false
  86. }
  87. // Redirect handler for routes that redirect based on their trailing slash
  88. // configuration.
  89. func redirectRouteHandler(route *Route) http.HandlerFunc {
  90. return func(w http.ResponseWriter, r *http.Request) {
  91. switch {
  92. case route.OptionalSlash && route.SlashIsDefault:
  93. r.URL.Host = ""
  94. r.URL.Scheme = ""
  95. url := r.URL.String()
  96. if url[len(url)-1:] != "/" {
  97. url = url + "/"
  98. }
  99. w.Header().Add("location", url)
  100. w.WriteHeader(301)
  101. case route.OptionalSlash && !route.SlashIsDefault:
  102. r.URL.Host = ""
  103. r.URL.Scheme = ""
  104. url := r.URL.String()
  105. if url[len(url)-1:] == "/" {
  106. url = url[:len(url)-1]
  107. }
  108. w.Header().Add("location", url)
  109. w.WriteHeader(301)
  110. }
  111. }
  112. }
  113. // URLForGen returns a URLFor generator for each configured renderer. This will
  114. // be attached to the global renderer first, if configured, and then to each
  115. // route-specific view renderer if supplied.
  116. func URLForGen(router *Router, external bool) func(string, ...string) string {
  117. logger := logging.MustInheritLogger("urlfor", "main")
  118. return func(name string, params ...string) string {
  119. builder := router.urls.For(name)
  120. err := builder.SetQuery(strings.Join(params, "&"))
  121. if err != nil {
  122. logger.Warning("URLFor query builder returned error:", err)
  123. return ""
  124. }
  125. if external {
  126. builder.External = true
  127. }
  128. s := builder.Encode()
  129. if err != nil {
  130. logger.Warning("URLFor encoding failed with error:", err)
  131. return ""
  132. }
  133. return s
  134. }
  135. }
  136. func httpErrorPage(ctx Context, json bool) error {
  137. code := 500
  138. text := http.StatusText(code)
  139. if ctx.Code() >= 400 {
  140. code = ctx.Code()
  141. text = http.StatusText(code)
  142. }
  143. msg, ok := httpErrorDesc[code]
  144. if !ok {
  145. msg = ""
  146. }
  147. if !json {
  148. tpl := `<!doctype>
  149. <html>
  150. <head><title>%d - %s</title></head>
  151. <body>
  152. <h1>%d - %s</h1>
  153. <p>%s</p>
  154. </body>
  155. </html>`
  156. _, err := ctx.Write([]byte(fmt.Sprintf(tpl, code, text, code, text, msg)))
  157. return err
  158. }
  159. return ctx.WriteJSON(struct {
  160. Code int `json:"code"`
  161. Message string `json:"message"`
  162. //Error string `json:"error,omitempty"`
  163. }{
  164. Code: code,
  165. Message: msg,
  166. //Error: ctx.Error(),
  167. })
  168. }