|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310 |
- // Package pat implements a simple URL pattern muxer
- package pat
-
- import (
- "net/http"
- "net/url"
- "strings"
- )
-
- // PatternServeMux is an HTTP request multiplexer. It matches the URL of each
- // incoming request against a list of registered patterns with their associated
- // methods and calls the handler for the pattern that most closely matches the
- // URL.
- //
- // Pattern matching attempts each pattern in the order in which they were
- // registered.
- //
- // Patterns may contain literals or captures. Capture names start with a colon
- // and consist of letters A-Z, a-z, _, and 0-9. The rest of the pattern
- // matches literally. The portion of the URL matching each name ends with an
- // occurrence of the character in the pattern immediately following the name,
- // or a /, whichever comes first. It is possible for a name to match the empty
- // string.
- //
- // Example pattern with one capture:
- // /hello/:name
- // Will match:
- // /hello/blake
- // /hello/keith
- // Will not match:
- // /hello/blake/
- // /hello/blake/foo
- // /foo
- // /foo/bar
- //
- // Example 2:
- // /hello/:name/
- // Will match:
- // /hello/blake/
- // /hello/keith/foo
- // /hello/blake
- // /hello/keith
- // Will not match:
- // /foo
- // /foo/bar
- //
- // A pattern ending with a slash will add an implicit redirect for its non-slash
- // version. For example: Get("/foo/", handler) also registers
- // Get("/foo", handler) as a redirect. You may override it by registering
- // Get("/foo", anotherhandler) before the slash version.
- //
- // Retrieve the capture from the r.URL.Query().Get(":name") in a handler (note
- // the colon). If a capture name appears more than once, the additional values
- // are appended to the previous values (see
- // http://golang.org/pkg/net/url/#Values)
- //
- // A trivial example server is:
- //
- // package main
- //
- // import (
- // "io"
- // "net/http"
- // "github.com/bmizerany/pat"
- // "log"
- // )
- //
- // // hello world, the web server
- // func HelloServer(w http.ResponseWriter, req *http.Request) {
- // io.WriteString(w, "hello, "+req.URL.Query().Get(":name")+"!\n")
- // }
- //
- // func main() {
- // m := pat.New()
- // m.Get("/hello/:name", http.HandlerFunc(HelloServer))
- //
- // // Register this pat with the default serve mux so that other packages
- // // may also be exported. (i.e. /debug/pprof/*)
- // http.Handle("/", m)
- // err := http.ListenAndServe(":12345", nil)
- // if err != nil {
- // log.Fatal("ListenAndServe: ", err)
- // }
- // }
- //
- // When "Method Not Allowed":
- //
- // Pat knows what methods are allowed given a pattern and a URI. For
- // convenience, PatternServeMux will add the Allow header for requests that
- // match a pattern for a method other than the method requested and set the
- // Status to "405 Method Not Allowed".
- //
- // If the NotFound handler is set, then it is used whenever the pattern doesn't
- // match the request path for the current method (and the Allow header is not
- // altered).
- type PatternServeMux struct {
- // NotFound, if set, is used whenever the request doesn't match any
- // pattern for its method. NotFound should be set before serving any
- // requests.
- NotFound http.Handler
- handlers map[string][]*patHandler
- }
-
- // New returns a new PatternServeMux.
- func New() *PatternServeMux {
- return &PatternServeMux{handlers: make(map[string][]*patHandler)}
- }
-
- // ServeHTTP matches r.URL.Path against its routing table using the rules
- // described above.
- func (p *PatternServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- for _, ph := range p.handlers[r.Method] {
- if params, ok := ph.try(r.URL.Path); ok {
- if len(params) > 0 && !ph.redirect {
- r.URL.RawQuery = url.Values(params).Encode() + "&" + r.URL.RawQuery
- }
- ph.ServeHTTP(w, r)
- return
- }
- }
-
- if p.NotFound != nil {
- p.NotFound.ServeHTTP(w, r)
- return
- }
-
- allowed := make([]string, 0, len(p.handlers))
- for meth, handlers := range p.handlers {
- if meth == r.Method {
- continue
- }
-
- for _, ph := range handlers {
- if _, ok := ph.try(r.URL.Path); ok {
- allowed = append(allowed, meth)
- }
- }
- }
-
- if len(allowed) == 0 {
- http.NotFound(w, r)
- return
- }
-
- w.Header().Add("Allow", strings.Join(allowed, ", "))
- http.Error(w, "Method Not Allowed", 405)
- }
-
- // Head will register a pattern with a handler for HEAD requests.
- func (p *PatternServeMux) Head(pat string, h http.Handler) {
- p.Add("HEAD", pat, h)
- }
-
- // Get will register a pattern with a handler for GET requests.
- // It also registers pat for HEAD requests. If this needs to be overridden, use
- // Head before Get with pat.
- func (p *PatternServeMux) Get(pat string, h http.Handler) {
- p.Add("HEAD", pat, h)
- p.Add("GET", pat, h)
- }
-
- // Post will register a pattern with a handler for POST requests.
- func (p *PatternServeMux) Post(pat string, h http.Handler) {
- p.Add("POST", pat, h)
- }
-
- // Put will register a pattern with a handler for PUT requests.
- func (p *PatternServeMux) Put(pat string, h http.Handler) {
- p.Add("PUT", pat, h)
- }
-
- // Del will register a pattern with a handler for DELETE requests.
- func (p *PatternServeMux) Del(pat string, h http.Handler) {
- p.Add("DELETE", pat, h)
- }
-
- // Options will register a pattern with a handler for OPTIONS requests.
- func (p *PatternServeMux) Options(pat string, h http.Handler) {
- p.Add("OPTIONS", pat, h)
- }
-
- // Patch will register a pattern with a handler for PATCH requests.
- func (p *PatternServeMux) Patch(pat string, h http.Handler) {
- p.Add("PATCH", pat, h)
- }
-
- // Add will register a pattern with a handler for meth requests.
- func (p *PatternServeMux) Add(meth, pat string, h http.Handler) {
- p.add(meth, pat, h, false)
- }
-
- func (p *PatternServeMux) add(meth, pat string, h http.Handler, redirect bool) {
- handlers := p.handlers[meth]
- for _, p1 := range handlers {
- if p1.pat == pat {
- return // found existing pattern; do nothing
- }
- }
- handler := &patHandler{
- pat: pat,
- Handler: h,
- redirect: redirect,
- }
- p.handlers[meth] = append(handlers, handler)
-
- n := len(pat)
- if n > 0 && pat[n-1] == '/' {
- p.add(meth, pat[:n-1], http.HandlerFunc(addSlashRedirect), true)
- }
- }
-
- func addSlashRedirect(w http.ResponseWriter, r *http.Request) {
- u := *r.URL
- u.Path += "/"
- http.Redirect(w, r, u.String(), http.StatusMovedPermanently)
- }
-
- // Tail returns the trailing string in path after the final slash for a pat ending with a slash.
- //
- // Examples:
- //
- // Tail("/hello/:title/", "/hello/mr/mizerany") == "mizerany"
- // Tail("/:a/", "/x/y/z") == "y/z"
- //
- func Tail(pat, path string) string {
- var i, j int
- for i < len(path) {
- switch {
- case j >= len(pat):
- if pat[len(pat)-1] == '/' {
- return path[i:]
- }
- return ""
- case pat[j] == ':':
- var nextc byte
- _, nextc, j = match(pat, isAlnum, j+1)
- _, _, i = match(path, matchPart(nextc), i)
- case path[i] == pat[j]:
- i++
- j++
- default:
- return ""
- }
- }
- return ""
- }
-
- type patHandler struct {
- pat string
- http.Handler
- redirect bool
- }
-
- func (ph *patHandler) try(path string) (url.Values, bool) {
- p := make(url.Values)
- var i, j int
- for i < len(path) {
- switch {
- case j >= len(ph.pat):
- if ph.pat != "/" && len(ph.pat) > 0 && ph.pat[len(ph.pat)-1] == '/' {
- return p, true
- }
- return nil, false
- case ph.pat[j] == ':':
- var name, val string
- var nextc byte
- name, nextc, j = match(ph.pat, isAlnum, j+1)
- val, _, i = match(path, matchPart(nextc), i)
- p.Add(":"+name, val)
- case path[i] == ph.pat[j]:
- i++
- j++
- default:
- return nil, false
- }
- }
- if j != len(ph.pat) {
- return nil, false
- }
- return p, true
- }
-
- func matchPart(b byte) func(byte) bool {
- return func(c byte) bool {
- return c != b && c != '/'
- }
- }
-
- func match(s string, f func(byte) bool, i int) (matched string, next byte, j int) {
- j = i
- for j < len(s) && f(s[j]) {
- j++
- }
- if j < len(s) {
- next = s[j]
- }
- return s[i:j], next, j
- }
-
- func isAlpha(ch byte) bool {
- return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_'
- }
-
- func isDigit(ch byte) bool {
- return '0' <= ch && ch <= '9'
- }
-
- func isAlnum(ch byte) bool {
- return isAlpha(ch) || isDigit(ch)
- }
|