mux.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. // Copyright 2011 Andy Balholm. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package icap
  5. import (
  6. "net/http"
  7. "net/url"
  8. "path"
  9. "strings"
  10. )
  11. // ServeMux is an ICAP request multiplexer.
  12. // It matches the URL of each incoming request against a list of registered
  13. // patterns and calls the handler for the pattern that
  14. // most closely matches the URL.
  15. //
  16. // For more details, see the documentation for http.ServeMux
  17. type ServeMux struct {
  18. m map[string]Handler
  19. }
  20. // NewServeMux allocates and returns a new ServeMux.
  21. func NewServeMux() *ServeMux { return &ServeMux{make(map[string]Handler)} }
  22. // DefaultServeMux is the default ServeMux used by Serve.
  23. var DefaultServeMux = NewServeMux()
  24. // Does path match pattern?
  25. func pathMatch(pattern, path string) bool {
  26. if len(pattern) == 0 {
  27. // should not happen
  28. return false
  29. }
  30. n := len(pattern)
  31. if pattern[n-1] != '/' {
  32. return pattern == path
  33. }
  34. return len(path) >= n && path[0:n] == pattern
  35. }
  36. // Return the canonical path for p, eliminating . and .. elements.
  37. func cleanPath(p string) string {
  38. if p == "" {
  39. return "/"
  40. }
  41. if p[0] != '/' {
  42. p = "/" + p
  43. }
  44. np := path.Clean(p)
  45. // path.Clean removes trailing slash except for root;
  46. // put the trailing slash back if necessary.
  47. if p[len(p)-1] == '/' && np != "/" {
  48. np += "/"
  49. }
  50. return np
  51. }
  52. // Find a handler on a handler map given a path string
  53. // Most-specific (longest) pattern wins
  54. func (mux *ServeMux) match(path string) Handler {
  55. var h Handler
  56. var n = 0
  57. for k, v := range mux.m {
  58. if !pathMatch(k, path) {
  59. continue
  60. }
  61. if h == nil || len(k) > n {
  62. n = len(k)
  63. h = v
  64. }
  65. }
  66. return h
  67. }
  68. // ServeICAP dispatches the request to the handler whose
  69. // pattern most closely matches the request URL.
  70. func (mux *ServeMux) ServeICAP(w ResponseWriter, r *Request) {
  71. // Clean path to canonical form and redirect.
  72. if p := cleanPath(r.URL.Path); p != r.URL.Path {
  73. w.Header().Set("Location", p)
  74. w.WriteHeader(http.StatusMovedPermanently, nil, false)
  75. return
  76. }
  77. // Host-specific pattern takes precedence over generic ones
  78. h := mux.match(r.URL.Host + r.URL.Path)
  79. if h == nil {
  80. h = mux.match(r.URL.Path)
  81. }
  82. if h == nil {
  83. h = NotFoundHandler()
  84. }
  85. h.ServeICAP(w, r)
  86. }
  87. // Handle registers the handler for the given pattern.
  88. func (mux *ServeMux) Handle(pattern string, handler Handler) {
  89. if pattern == "" {
  90. panic("icap: invalid pattern " + pattern)
  91. }
  92. mux.m[pattern] = handler
  93. // Helpful behavior:
  94. // If pattern is /tree/, insert permanent redirect for /tree.
  95. n := len(pattern)
  96. if n > 0 && pattern[n-1] == '/' {
  97. mux.m[pattern[0:n-1]] = RedirectHandler(pattern, http.StatusMovedPermanently)
  98. }
  99. }
  100. // HandleFunc registers the handler function for the given pattern.
  101. func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
  102. mux.Handle(pattern, HandlerFunc(handler))
  103. }
  104. // Handle registers the handler for the given pattern
  105. // in the DefaultServeMux.
  106. // The documentation for ServeMux explains how patterns are matched.
  107. func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }
  108. // HandleFunc registers the handler function for the given pattern
  109. // in the DefaultServeMux.
  110. // The documentation for ServeMux explains how patterns are matched.
  111. func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
  112. DefaultServeMux.HandleFunc(pattern, handler)
  113. }
  114. // NotFound replies to the request with an HTTP 404 not found error.
  115. func NotFound(w ResponseWriter, r *Request) {
  116. w.WriteHeader(http.StatusNotFound, nil, false)
  117. }
  118. // NotFoundHandler returns a simple request handler
  119. // that replies to each request with a ``404 page not found'' reply.
  120. func NotFoundHandler() Handler { return HandlerFunc(NotFound) }
  121. // Redirect to a fixed URL
  122. type redirectHandler struct {
  123. url string
  124. code int
  125. }
  126. func (rh *redirectHandler) ServeICAP(w ResponseWriter, r *Request) {
  127. Redirect(w, r, rh.url, rh.code)
  128. }
  129. // RedirectHandler returns a request handler that redirects
  130. // each request it receives to the given url using the given
  131. // status code.
  132. func RedirectHandler(redirectURL string, code int) Handler {
  133. return &redirectHandler{redirectURL, code}
  134. }
  135. // Redirect replies to the request with a redirect to url,
  136. // which may be a path relative to the request path.
  137. func Redirect(w ResponseWriter, r *Request, redirectURL string, code int) {
  138. if u, err := url.Parse(redirectURL); err == nil {
  139. // If url was relative, make absolute by
  140. // combining with request path.
  141. // The browser would probably do this for us,
  142. // but doing it ourselves is more reliable.
  143. oldpath := r.URL.Path
  144. if oldpath == "" { // should not happen, but avoid a crash if it does
  145. oldpath = "/"
  146. }
  147. if u.Scheme == "" {
  148. // no leading icap://server
  149. if redirectURL == "" || redirectURL[0] != '/' {
  150. // make relative path absolute
  151. olddir, _ := path.Split(oldpath)
  152. redirectURL = olddir + redirectURL
  153. }
  154. var query string
  155. if i := strings.Index(redirectURL, "?"); i != -1 {
  156. redirectURL, query = redirectURL[:i], redirectURL[i:]
  157. }
  158. // clean up but preserve trailing slash
  159. trailing := redirectURL[len(redirectURL)-1] == '/'
  160. redirectURL = path.Clean(redirectURL)
  161. if trailing && redirectURL[len(redirectURL)-1] != '/' {
  162. redirectURL += "/"
  163. }
  164. redirectURL += query
  165. }
  166. }
  167. w.Header().Set("Location", redirectURL)
  168. w.WriteHeader(code, nil, false)
  169. }