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
3.8KB

  1. package capstan
  2. import (
  3. "net/url"
  4. pth "path"
  5. "sync"
  6. "git.destrealm.org/go/capstan/config"
  7. "git.destrealm.org/go/logging"
  8. )
  9. type URLMapper struct {
  10. Config *config.ServerConfig
  11. Routes map[string]*Route
  12. Domains map[string]*URLMapper
  13. Static string
  14. sync.RWMutex
  15. logger *logging.Log
  16. }
  17. // NewURLMapper returns a new URLMapper configured to use the specified config.
  18. // We use a pointer to config.Config as some changes to URL behavior may be set
  19. // dynamically depending on how the application is running. For example,
  20. // enabling just HTTP or TLS will switch the protocol scheme between "http" and
  21. // "https," respectively, or enabling both will remove the scheme entirely and
  22. // use "relative" protocols (e.g. "//example.org/some/path").
  23. func NewURLMapper(conf *config.ServerConfig) *URLMapper {
  24. return &URLMapper{
  25. Config: conf,
  26. Routes: make(map[string]*Route),
  27. Domains: make(map[string]*URLMapper),
  28. logger: logging.MustInheritLogger("urlmapper", "main"),
  29. }
  30. }
  31. func (u *URLMapper) For(name string) *URLBuilder {
  32. route, ok := u.Routes[name]
  33. if !ok {
  34. if name == "static" && u.Static != "" {
  35. return &URLBuilder{
  36. path: u.Static,
  37. query: url.Values{},
  38. mapper: u,
  39. }
  40. }
  41. u.logger.Info("URLMapper.For() called on nonexistent endpoint:", name)
  42. return &URLBuilder{mapper: u}
  43. }
  44. return &URLBuilder{
  45. path: route.FullPath(),
  46. query: url.Values{},
  47. mapper: u,
  48. }
  49. }
  50. func (u *URLMapper) Has(route *Route) bool {
  51. u.RLock()
  52. defer u.RUnlock()
  53. _, ok := u.Routes[route.ForName()]
  54. return ok
  55. }
  56. func (u *URLMapper) Map(route *Route) {
  57. u.Lock()
  58. defer u.Unlock()
  59. u.Routes[route.ForName()] = route
  60. }
  61. func (u *URLMapper) Remove(route *Route) {
  62. u.Lock()
  63. delete(u.Routes, route.ForName())
  64. u.Unlock()
  65. }
  66. type URLBuilder struct {
  67. External bool
  68. Scheme string
  69. Host string
  70. path string
  71. asset string
  72. query url.Values
  73. mapper *URLMapper
  74. }
  75. func (b *URLBuilder) Encode() string {
  76. var open bool
  77. var skip bool
  78. var name string
  79. var s1 int
  80. var s2 int
  81. for i, s := range b.path {
  82. if s == '{' {
  83. open = true
  84. s1 = i
  85. continue
  86. }
  87. if s == '}' {
  88. open = false
  89. skip = false
  90. s2 = i
  91. if val := b.query.Get(name); val != "" {
  92. b.path = b.path[:s1] + val + b.path[s2+1:]
  93. b.query.Del(name)
  94. }
  95. name = ""
  96. continue
  97. }
  98. if open && s == ':' {
  99. skip = true
  100. continue
  101. }
  102. if open && !skip {
  103. name += string(s)
  104. }
  105. }
  106. u := url.URL{
  107. Host: b.mapper.Config.FullHost,
  108. }
  109. if !b.External {
  110. u.Host = ""
  111. u.Scheme = ""
  112. } else {
  113. // Override scheme only if we're forcing external links.
  114. if b.Scheme == "" {
  115. if b.mapper.Config.Protocol != "" {
  116. u.Scheme = b.mapper.Config.Protocol
  117. } else {
  118. u.Scheme = "http"
  119. }
  120. } else {
  121. u.Scheme = b.Scheme
  122. }
  123. if b.Host != "" {
  124. u.Host = b.Host
  125. }
  126. }
  127. // If NoProtocolLinks was set manually (or automatically), strip the scheme.
  128. if b.mapper.Config.NoProtocolLinks {
  129. u.Scheme = ""
  130. }
  131. if len(b.query) > 0 {
  132. u.RawQuery = b.query.Encode()
  133. }
  134. u.Path = b.path
  135. if b.asset != "" {
  136. u.Path = pth.Join(u.Path, b.asset)
  137. }
  138. return u.String()
  139. }
  140. // Param attaches a parameter to the internal parameters map. Parameters that
  141. // are named in the URL pathspec will be expanded as path components; others
  142. // will be treated as query string parameters.
  143. func (b *URLBuilder) Param(name, value string) *URLBuilder {
  144. b.query.Add(name, value)
  145. return b
  146. }
  147. // Params overwrites the internal parameters map with the one specified.
  148. // Parameters that are named in the URL pathspec will be expanded as path
  149. // components; others will be treated as query string parameters.
  150. func (b *URLBuilder) Params(params url.Values) *URLBuilder {
  151. b.query = params
  152. return b
  153. }
  154. func (b *URLBuilder) SetQuery(query string) error {
  155. q, err := url.ParseQuery(query)
  156. if err != nil {
  157. return err
  158. }
  159. b.query = q
  160. return nil
  161. }
  162. func (b *URLBuilder) Asset(asset string) {
  163. b.asset = asset
  164. }