// 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 }