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.
		
		
		
		
		
			
		
			
				
					
					
						
							542 lines
						
					
					
						
							16 KiB
						
					
					
				
			
		
		
	
	
							542 lines
						
					
					
						
							16 KiB
						
					
					
				| // Copyright 2012 The Gorilla Authors. All rights reserved.
 | |
| // Use of this source code is governed by a BSD-style
 | |
| // license that can be found in the LICENSE file.
 | |
| 
 | |
| package mux
 | |
| 
 | |
| import (
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"net/http"
 | |
| 	"path"
 | |
| 	"regexp"
 | |
| 	"strings"
 | |
| )
 | |
| 
 | |
| // NewRouter returns a new router instance.
 | |
| func NewRouter() *Router {
 | |
| 	return &Router{namedRoutes: make(map[string]*Route), KeepContext: false}
 | |
| }
 | |
| 
 | |
| // Router registers routes to be matched and dispatches a handler.
 | |
| //
 | |
| // It implements the http.Handler interface, so it can be registered to serve
 | |
| // requests:
 | |
| //
 | |
| //     var router = mux.NewRouter()
 | |
| //
 | |
| //     func main() {
 | |
| //         http.Handle("/", router)
 | |
| //     }
 | |
| //
 | |
| // Or, for Google App Engine, register it in a init() function:
 | |
| //
 | |
| //     func init() {
 | |
| //         http.Handle("/", router)
 | |
| //     }
 | |
| //
 | |
| // This will send all incoming requests to the router.
 | |
| type Router struct {
 | |
| 	// Configurable Handler to be used when no route matches.
 | |
| 	NotFoundHandler http.Handler
 | |
| 	// Parent route, if this is a subrouter.
 | |
| 	parent parentRoute
 | |
| 	// Routes to be matched, in order.
 | |
| 	routes []*Route
 | |
| 	// Routes by name for URL building.
 | |
| 	namedRoutes map[string]*Route
 | |
| 	// See Router.StrictSlash(). This defines the flag for new routes.
 | |
| 	strictSlash bool
 | |
| 	// See Router.SkipClean(). This defines the flag for new routes.
 | |
| 	skipClean bool
 | |
| 	// If true, do not clear the request context after handling the request.
 | |
| 	// This has no effect when go1.7+ is used, since the context is stored
 | |
| 	// on the request itself.
 | |
| 	KeepContext bool
 | |
| 	// see Router.UseEncodedPath(). This defines a flag for all routes.
 | |
| 	useEncodedPath bool
 | |
| }
 | |
| 
 | |
| // Match matches registered routes against the request.
 | |
| func (r *Router) Match(req *http.Request, match *RouteMatch) bool {
 | |
| 	for _, route := range r.routes {
 | |
| 		if route.Match(req, match) {
 | |
| 			return true
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Closest match for a router (includes sub-routers)
 | |
| 	if r.NotFoundHandler != nil {
 | |
| 		match.Handler = r.NotFoundHandler
 | |
| 		return true
 | |
| 	}
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| // ServeHTTP dispatches the handler registered in the matched route.
 | |
| //
 | |
| // When there is a match, the route variables can be retrieved calling
 | |
| // mux.Vars(request).
 | |
| func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
 | |
| 	if !r.skipClean {
 | |
| 		path := req.URL.Path
 | |
| 		if r.useEncodedPath {
 | |
| 			path = getPath(req)
 | |
| 		}
 | |
| 		// Clean path to canonical form and redirect.
 | |
| 		if p := cleanPath(path); p != path {
 | |
| 
 | |
| 			// Added 3 lines (Philip Schlump) - It was dropping the query string and #whatever from query.
 | |
| 			// This matches with fix in go 1.2 r.c. 4 for same problem.  Go Issue:
 | |
| 			// http://code.google.com/p/go/issues/detail?id=5252
 | |
| 			url := *req.URL
 | |
| 			url.Path = p
 | |
| 			p = url.String()
 | |
| 
 | |
| 			w.Header().Set("Location", p)
 | |
| 			w.WriteHeader(http.StatusMovedPermanently)
 | |
| 			return
 | |
| 		}
 | |
| 	}
 | |
| 	var match RouteMatch
 | |
| 	var handler http.Handler
 | |
| 	if r.Match(req, &match) {
 | |
| 		handler = match.Handler
 | |
| 		req = setVars(req, match.Vars)
 | |
| 		req = setCurrentRoute(req, match.Route)
 | |
| 	}
 | |
| 	if handler == nil {
 | |
| 		handler = http.NotFoundHandler()
 | |
| 	}
 | |
| 	if !r.KeepContext {
 | |
| 		defer contextClear(req)
 | |
| 	}
 | |
| 	handler.ServeHTTP(w, req)
 | |
| }
 | |
| 
 | |
| // Get returns a route registered with the given name.
 | |
| func (r *Router) Get(name string) *Route {
 | |
| 	return r.getNamedRoutes()[name]
 | |
| }
 | |
| 
 | |
| // GetRoute returns a route registered with the given name. This method
 | |
| // was renamed to Get() and remains here for backwards compatibility.
 | |
| func (r *Router) GetRoute(name string) *Route {
 | |
| 	return r.getNamedRoutes()[name]
 | |
| }
 | |
| 
 | |
| // StrictSlash defines the trailing slash behavior for new routes. The initial
 | |
| // value is false.
 | |
| //
 | |
| // When true, if the route path is "/path/", accessing "/path" will redirect
 | |
| // to the former and vice versa. In other words, your application will always
 | |
| // see the path as specified in the route.
 | |
| //
 | |
| // When false, if the route path is "/path", accessing "/path/" will not match
 | |
| // this route and vice versa.
 | |
| //
 | |
| // Special case: when a route sets a path prefix using the PathPrefix() method,
 | |
| // strict slash is ignored for that route because the redirect behavior can't
 | |
| // be determined from a prefix alone. However, any subrouters created from that
 | |
| // route inherit the original StrictSlash setting.
 | |
| func (r *Router) StrictSlash(value bool) *Router {
 | |
| 	r.strictSlash = value
 | |
| 	return r
 | |
| }
 | |
| 
 | |
| // SkipClean defines the path cleaning behaviour for new routes. The initial
 | |
| // value is false. Users should be careful about which routes are not cleaned
 | |
| //
 | |
| // When true, if the route path is "/path//to", it will remain with the double
 | |
| // slash. This is helpful if you have a route like: /fetch/http://xkcd.com/534/
 | |
| //
 | |
| // When false, the path will be cleaned, so /fetch/http://xkcd.com/534/ will
 | |
| // become /fetch/http/xkcd.com/534
 | |
| func (r *Router) SkipClean(value bool) *Router {
 | |
| 	r.skipClean = value
 | |
| 	return r
 | |
| }
 | |
| 
 | |
| // UseEncodedPath tells the router to match the encoded original path
 | |
| // to the routes.
 | |
| // For eg. "/path/foo%2Fbar/to" will match the path "/path/{var}/to".
 | |
| // This behavior has the drawback of needing to match routes against
 | |
| // r.RequestURI instead of r.URL.Path. Any modifications (such as http.StripPrefix)
 | |
| // to r.URL.Path will not affect routing when this flag is on and thus may
 | |
| // induce unintended behavior.
 | |
| //
 | |
| // If not called, the router will match the unencoded path to the routes.
 | |
| // For eg. "/path/foo%2Fbar/to" will match the path "/path/foo/bar/to"
 | |
| func (r *Router) UseEncodedPath() *Router {
 | |
| 	r.useEncodedPath = true
 | |
| 	return r
 | |
| }
 | |
| 
 | |
| // ----------------------------------------------------------------------------
 | |
| // parentRoute
 | |
| // ----------------------------------------------------------------------------
 | |
| 
 | |
| // getNamedRoutes returns the map where named routes are registered.
 | |
| func (r *Router) getNamedRoutes() map[string]*Route {
 | |
| 	if r.namedRoutes == nil {
 | |
| 		if r.parent != nil {
 | |
| 			r.namedRoutes = r.parent.getNamedRoutes()
 | |
| 		} else {
 | |
| 			r.namedRoutes = make(map[string]*Route)
 | |
| 		}
 | |
| 	}
 | |
| 	return r.namedRoutes
 | |
| }
 | |
| 
 | |
| // getRegexpGroup returns regexp definitions from the parent route, if any.
 | |
| func (r *Router) getRegexpGroup() *routeRegexpGroup {
 | |
| 	if r.parent != nil {
 | |
| 		return r.parent.getRegexpGroup()
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (r *Router) buildVars(m map[string]string) map[string]string {
 | |
| 	if r.parent != nil {
 | |
| 		m = r.parent.buildVars(m)
 | |
| 	}
 | |
| 	return m
 | |
| }
 | |
| 
 | |
| // ----------------------------------------------------------------------------
 | |
| // Route factories
 | |
| // ----------------------------------------------------------------------------
 | |
| 
 | |
| // NewRoute registers an empty route.
 | |
| func (r *Router) NewRoute() *Route {
 | |
| 	route := &Route{parent: r, strictSlash: r.strictSlash, skipClean: r.skipClean, useEncodedPath: r.useEncodedPath}
 | |
| 	r.routes = append(r.routes, route)
 | |
| 	return route
 | |
| }
 | |
| 
 | |
| // Handle registers a new route with a matcher for the URL path.
 | |
| // See Route.Path() and Route.Handler().
 | |
| func (r *Router) Handle(path string, handler http.Handler) *Route {
 | |
| 	return r.NewRoute().Path(path).Handler(handler)
 | |
| }
 | |
| 
 | |
| // HandleFunc registers a new route with a matcher for the URL path.
 | |
| // See Route.Path() and Route.HandlerFunc().
 | |
| func (r *Router) HandleFunc(path string, f func(http.ResponseWriter,
 | |
| 	*http.Request)) *Route {
 | |
| 	return r.NewRoute().Path(path).HandlerFunc(f)
 | |
| }
 | |
| 
 | |
| // Headers registers a new route with a matcher for request header values.
 | |
| // See Route.Headers().
 | |
| func (r *Router) Headers(pairs ...string) *Route {
 | |
| 	return r.NewRoute().Headers(pairs...)
 | |
| }
 | |
| 
 | |
| // Host registers a new route with a matcher for the URL host.
 | |
| // See Route.Host().
 | |
| func (r *Router) Host(tpl string) *Route {
 | |
| 	return r.NewRoute().Host(tpl)
 | |
| }
 | |
| 
 | |
| // MatcherFunc registers a new route with a custom matcher function.
 | |
| // See Route.MatcherFunc().
 | |
| func (r *Router) MatcherFunc(f MatcherFunc) *Route {
 | |
| 	return r.NewRoute().MatcherFunc(f)
 | |
| }
 | |
| 
 | |
| // Methods registers a new route with a matcher for HTTP methods.
 | |
| // See Route.Methods().
 | |
| func (r *Router) Methods(methods ...string) *Route {
 | |
| 	return r.NewRoute().Methods(methods...)
 | |
| }
 | |
| 
 | |
| // Path registers a new route with a matcher for the URL path.
 | |
| // See Route.Path().
 | |
| func (r *Router) Path(tpl string) *Route {
 | |
| 	return r.NewRoute().Path(tpl)
 | |
| }
 | |
| 
 | |
| // PathPrefix registers a new route with a matcher for the URL path prefix.
 | |
| // See Route.PathPrefix().
 | |
| func (r *Router) PathPrefix(tpl string) *Route {
 | |
| 	return r.NewRoute().PathPrefix(tpl)
 | |
| }
 | |
| 
 | |
| // Queries registers a new route with a matcher for URL query values.
 | |
| // See Route.Queries().
 | |
| func (r *Router) Queries(pairs ...string) *Route {
 | |
| 	return r.NewRoute().Queries(pairs...)
 | |
| }
 | |
| 
 | |
| // Schemes registers a new route with a matcher for URL schemes.
 | |
| // See Route.Schemes().
 | |
| func (r *Router) Schemes(schemes ...string) *Route {
 | |
| 	return r.NewRoute().Schemes(schemes...)
 | |
| }
 | |
| 
 | |
| // BuildVarsFunc registers a new route with a custom function for modifying
 | |
| // route variables before building a URL.
 | |
| func (r *Router) BuildVarsFunc(f BuildVarsFunc) *Route {
 | |
| 	return r.NewRoute().BuildVarsFunc(f)
 | |
| }
 | |
| 
 | |
| // Walk walks the router and all its sub-routers, calling walkFn for each route
 | |
| // in the tree. The routes are walked in the order they were added. Sub-routers
 | |
| // are explored depth-first.
 | |
| func (r *Router) Walk(walkFn WalkFunc) error {
 | |
| 	return r.walk(walkFn, []*Route{})
 | |
| }
 | |
| 
 | |
| // SkipRouter is used as a return value from WalkFuncs to indicate that the
 | |
| // router that walk is about to descend down to should be skipped.
 | |
| var SkipRouter = errors.New("skip this router")
 | |
| 
 | |
| // WalkFunc is the type of the function called for each route visited by Walk.
 | |
| // At every invocation, it is given the current route, and the current router,
 | |
| // and a list of ancestor routes that lead to the current route.
 | |
| type WalkFunc func(route *Route, router *Router, ancestors []*Route) error
 | |
| 
 | |
| func (r *Router) walk(walkFn WalkFunc, ancestors []*Route) error {
 | |
| 	for _, t := range r.routes {
 | |
| 		if t.regexp == nil || t.regexp.path == nil || t.regexp.path.template == "" {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		err := walkFn(t, r, ancestors)
 | |
| 		if err == SkipRouter {
 | |
| 			continue
 | |
| 		}
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		for _, sr := range t.matchers {
 | |
| 			if h, ok := sr.(*Router); ok {
 | |
| 				err := h.walk(walkFn, ancestors)
 | |
| 				if err != nil {
 | |
| 					return err
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		if h, ok := t.handler.(*Router); ok {
 | |
| 			ancestors = append(ancestors, t)
 | |
| 			err := h.walk(walkFn, ancestors)
 | |
| 			if err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 			ancestors = ancestors[:len(ancestors)-1]
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // ----------------------------------------------------------------------------
 | |
| // Context
 | |
| // ----------------------------------------------------------------------------
 | |
| 
 | |
| // RouteMatch stores information about a matched route.
 | |
| type RouteMatch struct {
 | |
| 	Route   *Route
 | |
| 	Handler http.Handler
 | |
| 	Vars    map[string]string
 | |
| }
 | |
| 
 | |
| type contextKey int
 | |
| 
 | |
| const (
 | |
| 	varsKey contextKey = iota
 | |
| 	routeKey
 | |
| )
 | |
| 
 | |
| // Vars returns the route variables for the current request, if any.
 | |
| func Vars(r *http.Request) map[string]string {
 | |
| 	if rv := contextGet(r, varsKey); rv != nil {
 | |
| 		return rv.(map[string]string)
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // CurrentRoute returns the matched route for the current request, if any.
 | |
| // This only works when called inside the handler of the matched route
 | |
| // because the matched route is stored in the request context which is cleared
 | |
| // after the handler returns, unless the KeepContext option is set on the
 | |
| // Router.
 | |
| func CurrentRoute(r *http.Request) *Route {
 | |
| 	if rv := contextGet(r, routeKey); rv != nil {
 | |
| 		return rv.(*Route)
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func setVars(r *http.Request, val interface{}) *http.Request {
 | |
| 	return contextSet(r, varsKey, val)
 | |
| }
 | |
| 
 | |
| func setCurrentRoute(r *http.Request, val interface{}) *http.Request {
 | |
| 	return contextSet(r, routeKey, val)
 | |
| }
 | |
| 
 | |
| // ----------------------------------------------------------------------------
 | |
| // Helpers
 | |
| // ----------------------------------------------------------------------------
 | |
| 
 | |
| // getPath returns the escaped path if possible; doing what URL.EscapedPath()
 | |
| // which was added in go1.5 does
 | |
| func getPath(req *http.Request) string {
 | |
| 	if req.RequestURI != "" {
 | |
| 		// Extract the path from RequestURI (which is escaped unlike URL.Path)
 | |
| 		// as detailed here as detailed in https://golang.org/pkg/net/url/#URL
 | |
| 		// for < 1.5 server side workaround
 | |
| 		// http://localhost/path/here?v=1 -> /path/here
 | |
| 		path := req.RequestURI
 | |
| 		path = strings.TrimPrefix(path, req.URL.Scheme+`://`)
 | |
| 		path = strings.TrimPrefix(path, req.URL.Host)
 | |
| 		if i := strings.LastIndex(path, "?"); i > -1 {
 | |
| 			path = path[:i]
 | |
| 		}
 | |
| 		if i := strings.LastIndex(path, "#"); i > -1 {
 | |
| 			path = path[:i]
 | |
| 		}
 | |
| 		return path
 | |
| 	}
 | |
| 	return req.URL.Path
 | |
| }
 | |
| 
 | |
| // cleanPath returns the canonical path for p, eliminating . and .. elements.
 | |
| // Borrowed from the net/http package.
 | |
| func cleanPath(p string) string {
 | |
| 	if p == "" {
 | |
| 		return "/"
 | |
| 	}
 | |
| 	if p[0] != '/' {
 | |
| 		p = "/" + p
 | |
| 	}
 | |
| 	np := path.Clean(p)
 | |
| 	// path.Clean removes trailing slash except for root;
 | |
| 	// put the trailing slash back if necessary.
 | |
| 	if p[len(p)-1] == '/' && np != "/" {
 | |
| 		np += "/"
 | |
| 	}
 | |
| 
 | |
| 	return np
 | |
| }
 | |
| 
 | |
| // uniqueVars returns an error if two slices contain duplicated strings.
 | |
| func uniqueVars(s1, s2 []string) error {
 | |
| 	for _, v1 := range s1 {
 | |
| 		for _, v2 := range s2 {
 | |
| 			if v1 == v2 {
 | |
| 				return fmt.Errorf("mux: duplicated route variable %q", v2)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // checkPairs returns the count of strings passed in, and an error if
 | |
| // the count is not an even number.
 | |
| func checkPairs(pairs ...string) (int, error) {
 | |
| 	length := len(pairs)
 | |
| 	if length%2 != 0 {
 | |
| 		return length, fmt.Errorf(
 | |
| 			"mux: number of parameters must be multiple of 2, got %v", pairs)
 | |
| 	}
 | |
| 	return length, nil
 | |
| }
 | |
| 
 | |
| // mapFromPairsToString converts variadic string parameters to a
 | |
| // string to string map.
 | |
| func mapFromPairsToString(pairs ...string) (map[string]string, error) {
 | |
| 	length, err := checkPairs(pairs...)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	m := make(map[string]string, length/2)
 | |
| 	for i := 0; i < length; i += 2 {
 | |
| 		m[pairs[i]] = pairs[i+1]
 | |
| 	}
 | |
| 	return m, nil
 | |
| }
 | |
| 
 | |
| // mapFromPairsToRegex converts variadic string paramers to a
 | |
| // string to regex map.
 | |
| func mapFromPairsToRegex(pairs ...string) (map[string]*regexp.Regexp, error) {
 | |
| 	length, err := checkPairs(pairs...)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	m := make(map[string]*regexp.Regexp, length/2)
 | |
| 	for i := 0; i < length; i += 2 {
 | |
| 		regex, err := regexp.Compile(pairs[i+1])
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		m[pairs[i]] = regex
 | |
| 	}
 | |
| 	return m, nil
 | |
| }
 | |
| 
 | |
| // matchInArray returns true if the given string value is in the array.
 | |
| func matchInArray(arr []string, value string) bool {
 | |
| 	for _, v := range arr {
 | |
| 		if v == value {
 | |
| 			return true
 | |
| 		}
 | |
| 	}
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| // matchMapWithString returns true if the given key/value pairs exist in a given map.
 | |
| func matchMapWithString(toCheck map[string]string, toMatch map[string][]string, canonicalKey bool) bool {
 | |
| 	for k, v := range toCheck {
 | |
| 		// Check if key exists.
 | |
| 		if canonicalKey {
 | |
| 			k = http.CanonicalHeaderKey(k)
 | |
| 		}
 | |
| 		if values := toMatch[k]; values == nil {
 | |
| 			return false
 | |
| 		} else if v != "" {
 | |
| 			// If value was defined as an empty string we only check that the
 | |
| 			// key exists. Otherwise we also check for equality.
 | |
| 			valueExists := false
 | |
| 			for _, value := range values {
 | |
| 				if v == value {
 | |
| 					valueExists = true
 | |
| 					break
 | |
| 				}
 | |
| 			}
 | |
| 			if !valueExists {
 | |
| 				return false
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return true
 | |
| }
 | |
| 
 | |
| // matchMapWithRegex returns true if the given key/value pairs exist in a given map compiled against
 | |
| // the given regex
 | |
| func matchMapWithRegex(toCheck map[string]*regexp.Regexp, toMatch map[string][]string, canonicalKey bool) bool {
 | |
| 	for k, v := range toCheck {
 | |
| 		// Check if key exists.
 | |
| 		if canonicalKey {
 | |
| 			k = http.CanonicalHeaderKey(k)
 | |
| 		}
 | |
| 		if values := toMatch[k]; values == nil {
 | |
| 			return false
 | |
| 		} else if v != nil {
 | |
| 			// If value was defined as an empty string we only check that the
 | |
| 			// key exists. Otherwise we also check for equality.
 | |
| 			valueExists := false
 | |
| 			for _, value := range values {
 | |
| 				if v.MatchString(value) {
 | |
| 					valueExists = true
 | |
| 					break
 | |
| 				}
 | |
| 			}
 | |
| 			if !valueExists {
 | |
| 				return false
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return true
 | |
| }
 | |
| 
 |