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.

185 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.CalculatedHostname(),
  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. u.Scheme = b.mapper.Config.CalculatedProtocol()
  116. } else {
  117. u.Scheme = b.Scheme
  118. }
  119. if b.Host != "" {
  120. u.Host = b.Host
  121. }
  122. }
  123. // If NoProtocolLinks was set manually (or automatically), strip the scheme.
  124. if b.mapper.Config.NoProtocolLinks {
  125. u.Scheme = ""
  126. }
  127. if len(b.query) > 0 {
  128. u.RawQuery = b.query.Encode()
  129. }
  130. u.Path = b.path
  131. if b.asset != "" {
  132. u.Path = pth.Join(u.Path, b.asset)
  133. }
  134. return u.String()
  135. }
  136. // Param attaches a parameter to the internal parameters map. Parameters that
  137. // are named in the URL pathspec will be expanded as path components; others
  138. // will be treated as query string parameters.
  139. func (b *URLBuilder) Param(name, value string) *URLBuilder {
  140. b.query.Add(name, value)
  141. return b
  142. }
  143. // Params overwrites the internal parameters map with the one specified.
  144. // Parameters that are named in the URL pathspec will be expanded as path
  145. // components; others will be treated as query string parameters.
  146. func (b *URLBuilder) Params(params url.Values) *URLBuilder {
  147. b.query = params
  148. return b
  149. }
  150. func (b *URLBuilder) SetQuery(query string) error {
  151. q, err := url.ParseQuery(query)
  152. if err != nil {
  153. return err
  154. }
  155. b.query = q
  156. return nil
  157. }
  158. func (b *URLBuilder) Asset(asset string) {
  159. b.asset = asset
  160. }