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.
 
 
 

311 lines
7.8 KiB

  1. // Package pat implements a simple URL pattern muxer
  2. package pat
  3. import (
  4. "net/http"
  5. "net/url"
  6. "strings"
  7. )
  8. // PatternServeMux is an HTTP request multiplexer. It matches the URL of each
  9. // incoming request against a list of registered patterns with their associated
  10. // methods and calls the handler for the pattern that most closely matches the
  11. // URL.
  12. //
  13. // Pattern matching attempts each pattern in the order in which they were
  14. // registered.
  15. //
  16. // Patterns may contain literals or captures. Capture names start with a colon
  17. // and consist of letters A-Z, a-z, _, and 0-9. The rest of the pattern
  18. // matches literally. The portion of the URL matching each name ends with an
  19. // occurrence of the character in the pattern immediately following the name,
  20. // or a /, whichever comes first. It is possible for a name to match the empty
  21. // string.
  22. //
  23. // Example pattern with one capture:
  24. // /hello/:name
  25. // Will match:
  26. // /hello/blake
  27. // /hello/keith
  28. // Will not match:
  29. // /hello/blake/
  30. // /hello/blake/foo
  31. // /foo
  32. // /foo/bar
  33. //
  34. // Example 2:
  35. // /hello/:name/
  36. // Will match:
  37. // /hello/blake/
  38. // /hello/keith/foo
  39. // /hello/blake
  40. // /hello/keith
  41. // Will not match:
  42. // /foo
  43. // /foo/bar
  44. //
  45. // A pattern ending with a slash will add an implicit redirect for its non-slash
  46. // version. For example: Get("/foo/", handler) also registers
  47. // Get("/foo", handler) as a redirect. You may override it by registering
  48. // Get("/foo", anotherhandler) before the slash version.
  49. //
  50. // Retrieve the capture from the r.URL.Query().Get(":name") in a handler (note
  51. // the colon). If a capture name appears more than once, the additional values
  52. // are appended to the previous values (see
  53. // http://golang.org/pkg/net/url/#Values)
  54. //
  55. // A trivial example server is:
  56. //
  57. // package main
  58. //
  59. // import (
  60. // "io"
  61. // "net/http"
  62. // "github.com/bmizerany/pat"
  63. // "log"
  64. // )
  65. //
  66. // // hello world, the web server
  67. // func HelloServer(w http.ResponseWriter, req *http.Request) {
  68. // io.WriteString(w, "hello, "+req.URL.Query().Get(":name")+"!\n")
  69. // }
  70. //
  71. // func main() {
  72. // m := pat.New()
  73. // m.Get("/hello/:name", http.HandlerFunc(HelloServer))
  74. //
  75. // // Register this pat with the default serve mux so that other packages
  76. // // may also be exported. (i.e. /debug/pprof/*)
  77. // http.Handle("/", m)
  78. // err := http.ListenAndServe(":12345", nil)
  79. // if err != nil {
  80. // log.Fatal("ListenAndServe: ", err)
  81. // }
  82. // }
  83. //
  84. // When "Method Not Allowed":
  85. //
  86. // Pat knows what methods are allowed given a pattern and a URI. For
  87. // convenience, PatternServeMux will add the Allow header for requests that
  88. // match a pattern for a method other than the method requested and set the
  89. // Status to "405 Method Not Allowed".
  90. //
  91. // If the NotFound handler is set, then it is used whenever the pattern doesn't
  92. // match the request path for the current method (and the Allow header is not
  93. // altered).
  94. type PatternServeMux struct {
  95. // NotFound, if set, is used whenever the request doesn't match any
  96. // pattern for its method. NotFound should be set before serving any
  97. // requests.
  98. NotFound http.Handler
  99. handlers map[string][]*patHandler
  100. }
  101. // New returns a new PatternServeMux.
  102. func New() *PatternServeMux {
  103. return &PatternServeMux{handlers: make(map[string][]*patHandler)}
  104. }
  105. // ServeHTTP matches r.URL.Path against its routing table using the rules
  106. // described above.
  107. func (p *PatternServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  108. for _, ph := range p.handlers[r.Method] {
  109. if params, ok := ph.try(r.URL.Path); ok {
  110. if len(params) > 0 && !ph.redirect {
  111. r.URL.RawQuery = url.Values(params).Encode() + "&" + r.URL.RawQuery
  112. }
  113. ph.ServeHTTP(w, r)
  114. return
  115. }
  116. }
  117. if p.NotFound != nil {
  118. p.NotFound.ServeHTTP(w, r)
  119. return
  120. }
  121. allowed := make([]string, 0, len(p.handlers))
  122. for meth, handlers := range p.handlers {
  123. if meth == r.Method {
  124. continue
  125. }
  126. for _, ph := range handlers {
  127. if _, ok := ph.try(r.URL.Path); ok {
  128. allowed = append(allowed, meth)
  129. }
  130. }
  131. }
  132. if len(allowed) == 0 {
  133. http.NotFound(w, r)
  134. return
  135. }
  136. w.Header().Add("Allow", strings.Join(allowed, ", "))
  137. http.Error(w, "Method Not Allowed", 405)
  138. }
  139. // Head will register a pattern with a handler for HEAD requests.
  140. func (p *PatternServeMux) Head(pat string, h http.Handler) {
  141. p.Add("HEAD", pat, h)
  142. }
  143. // Get will register a pattern with a handler for GET requests.
  144. // It also registers pat for HEAD requests. If this needs to be overridden, use
  145. // Head before Get with pat.
  146. func (p *PatternServeMux) Get(pat string, h http.Handler) {
  147. p.Add("HEAD", pat, h)
  148. p.Add("GET", pat, h)
  149. }
  150. // Post will register a pattern with a handler for POST requests.
  151. func (p *PatternServeMux) Post(pat string, h http.Handler) {
  152. p.Add("POST", pat, h)
  153. }
  154. // Put will register a pattern with a handler for PUT requests.
  155. func (p *PatternServeMux) Put(pat string, h http.Handler) {
  156. p.Add("PUT", pat, h)
  157. }
  158. // Del will register a pattern with a handler for DELETE requests.
  159. func (p *PatternServeMux) Del(pat string, h http.Handler) {
  160. p.Add("DELETE", pat, h)
  161. }
  162. // Options will register a pattern with a handler for OPTIONS requests.
  163. func (p *PatternServeMux) Options(pat string, h http.Handler) {
  164. p.Add("OPTIONS", pat, h)
  165. }
  166. // Patch will register a pattern with a handler for PATCH requests.
  167. func (p *PatternServeMux) Patch(pat string, h http.Handler) {
  168. p.Add("PATCH", pat, h)
  169. }
  170. // Add will register a pattern with a handler for meth requests.
  171. func (p *PatternServeMux) Add(meth, pat string, h http.Handler) {
  172. p.add(meth, pat, h, false)
  173. }
  174. func (p *PatternServeMux) add(meth, pat string, h http.Handler, redirect bool) {
  175. handlers := p.handlers[meth]
  176. for _, p1 := range handlers {
  177. if p1.pat == pat {
  178. return // found existing pattern; do nothing
  179. }
  180. }
  181. handler := &patHandler{
  182. pat: pat,
  183. Handler: h,
  184. redirect: redirect,
  185. }
  186. p.handlers[meth] = append(handlers, handler)
  187. n := len(pat)
  188. if n > 0 && pat[n-1] == '/' {
  189. p.add(meth, pat[:n-1], http.HandlerFunc(addSlashRedirect), true)
  190. }
  191. }
  192. func addSlashRedirect(w http.ResponseWriter, r *http.Request) {
  193. u := *r.URL
  194. u.Path += "/"
  195. http.Redirect(w, r, u.String(), http.StatusMovedPermanently)
  196. }
  197. // Tail returns the trailing string in path after the final slash for a pat ending with a slash.
  198. //
  199. // Examples:
  200. //
  201. // Tail("/hello/:title/", "/hello/mr/mizerany") == "mizerany"
  202. // Tail("/:a/", "/x/y/z") == "y/z"
  203. //
  204. func Tail(pat, path string) string {
  205. var i, j int
  206. for i < len(path) {
  207. switch {
  208. case j >= len(pat):
  209. if pat[len(pat)-1] == '/' {
  210. return path[i:]
  211. }
  212. return ""
  213. case pat[j] == ':':
  214. var nextc byte
  215. _, nextc, j = match(pat, isAlnum, j+1)
  216. _, _, i = match(path, matchPart(nextc), i)
  217. case path[i] == pat[j]:
  218. i++
  219. j++
  220. default:
  221. return ""
  222. }
  223. }
  224. return ""
  225. }
  226. type patHandler struct {
  227. pat string
  228. http.Handler
  229. redirect bool
  230. }
  231. func (ph *patHandler) try(path string) (url.Values, bool) {
  232. p := make(url.Values)
  233. var i, j int
  234. for i < len(path) {
  235. switch {
  236. case j >= len(ph.pat):
  237. if ph.pat != "/" && len(ph.pat) > 0 && ph.pat[len(ph.pat)-1] == '/' {
  238. return p, true
  239. }
  240. return nil, false
  241. case ph.pat[j] == ':':
  242. var name, val string
  243. var nextc byte
  244. name, nextc, j = match(ph.pat, isAlnum, j+1)
  245. val, _, i = match(path, matchPart(nextc), i)
  246. p.Add(":"+name, val)
  247. case path[i] == ph.pat[j]:
  248. i++
  249. j++
  250. default:
  251. return nil, false
  252. }
  253. }
  254. if j != len(ph.pat) {
  255. return nil, false
  256. }
  257. return p, true
  258. }
  259. func matchPart(b byte) func(byte) bool {
  260. return func(c byte) bool {
  261. return c != b && c != '/'
  262. }
  263. }
  264. func match(s string, f func(byte) bool, i int) (matched string, next byte, j int) {
  265. j = i
  266. for j < len(s) && f(s[j]) {
  267. j++
  268. }
  269. if j < len(s) {
  270. next = s[j]
  271. }
  272. return s[i:j], next, j
  273. }
  274. func isAlpha(ch byte) bool {
  275. return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_'
  276. }
  277. func isDigit(ch byte) bool {
  278. return '0' <= ch && ch <= '9'
  279. }
  280. func isAlnum(ch byte) bool {
  281. return isAlpha(ch) || isDigit(ch)
  282. }