pax_global_header00006660000000000000000000000064121670653060014520gustar00rootroot0000000000000052 comment=e31d5506a93845565070697b353cacb189b63ac2 golang-mux-dev-0.0~git20130701/000077500000000000000000000000001216706530600157315ustar00rootroot00000000000000golang-mux-dev-0.0~git20130701/LICENSE000066400000000000000000000027041216706530600167410ustar00rootroot00000000000000Copyright (c) 2012 Rodrigo Moraes. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. golang-mux-dev-0.0~git20130701/README.md000066400000000000000000000002071216706530600172070ustar00rootroot00000000000000mux === gorilla/mux is a powerful URL router and dispatcher. Read the full documentation here: http://www.gorillatoolkit.org/pkg/mux golang-mux-dev-0.0~git20130701/bench_test.go000066400000000000000000000007601216706530600204010ustar00rootroot00000000000000// 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 ( "net/http" "testing" ) func BenchmarkMux(b *testing.B) { router := new(Router) handler := func(w http.ResponseWriter, r *http.Request) {} router.HandleFunc("/v1/{v1}", handler) request, _ := http.NewRequest("GET", "/v1/anything", nil) for i := 0; i < b.N; i++ { router.ServeHTTP(nil, request) } } golang-mux-dev-0.0~git20130701/doc.go000066400000000000000000000153001216706530600170240ustar00rootroot00000000000000// 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 gorilla/mux implements a request router and dispatcher. The name mux stands for "HTTP request multiplexer". Like the standard http.ServeMux, mux.Router matches incoming requests against a list of registered routes and calls a handler for the route that matches the URL or other conditions. The main features are: * Requests can be matched based on URL host, path, path prefix, schemes, header and query values, HTTP methods or using custom matchers. * URL hosts and paths can have variables with an optional regular expression. * Registered URLs can be built, or "reversed", which helps maintaining references to resources. * Routes can be used as subrouters: nested routes are only tested if the parent route matches. This is useful to define groups of routes that share common conditions like a host, a path prefix or other repeated attributes. As a bonus, this optimizes request matching. * It implements the http.Handler interface so it is compatible with the standard http.ServeMux. Let's start registering a couple of URL paths and handlers: func main() { r := mux.NewRouter() r.HandleFunc("/", HomeHandler) r.HandleFunc("/products", ProductsHandler) r.HandleFunc("/articles", ArticlesHandler) http.Handle("/", r) } Here we register three routes mapping URL paths to handlers. This is equivalent to how http.HandleFunc() works: if an incoming request URL matches one of the paths, the corresponding handler is called passing (http.ResponseWriter, *http.Request) as parameters. Paths can have variables. They are defined using the format {name} or {name:pattern}. If a regular expression pattern is not defined, the matched variable will be anything until the next slash. For example: r := mux.NewRouter() r.HandleFunc("/products/{key}", ProductHandler) r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler) r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler) The names are used to create a map of route variables which can be retrieved calling mux.Vars(): vars := mux.Vars(request) category := vars["category"] And this is all you need to know about the basic usage. More advanced options are explained below. Routes can also be restricted to a domain or subdomain. Just define a host pattern to be matched. They can also have variables: r := mux.NewRouter() // Only matches if domain is "www.domain.com". r.Host("www.domain.com") // Matches a dynamic subdomain. r.Host("{subdomain:[a-z]+}.domain.com") There are several other matchers that can be added. To match path prefixes: r.PathPrefix("/products/") ...or HTTP methods: r.Methods("GET", "POST") ...or URL schemes: r.Schemes("https") ...or header values: r.Headers("X-Requested-With", "XMLHttpRequest") ...or query values: r.Queries("key", "value") ...or to use a custom matcher function: r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool { return r.ProtoMajor == 0 }) ...and finally, it is possible to combine several matchers in a single route: r.HandleFunc("/products", ProductsHandler). Host("www.domain.com"). Methods("GET"). Schemes("http") Setting the same matching conditions again and again can be boring, so we have a way to group several routes that share the same requirements. We call it "subrouting". For example, let's say we have several URLs that should only match when the host is "www.domain.com". Create a route for that host and get a "subrouter" from it: r := mux.NewRouter() s := r.Host("www.domain.com").Subrouter() Then register routes in the subrouter: s.HandleFunc("/products/", ProductsHandler) s.HandleFunc("/products/{key}", ProductHandler) s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler) The three URL paths we registered above will only be tested if the domain is "www.domain.com", because the subrouter is tested first. This is not only convenient, but also optimizes request matching. You can create subrouters combining any attribute matchers accepted by a route. Subrouters can be used to create domain or path "namespaces": you define subrouters in a central place and then parts of the app can register its paths relatively to a given subrouter. There's one more thing about subroutes. When a subrouter has a path prefix, the inner routes use it as base for their paths: r := mux.NewRouter() s := r.PathPrefix("/products").Subrouter() // "/products/" s.HandleFunc("/", ProductsHandler) // "/products/{key}/" s.HandleFunc("/{key}/", ProductHandler) // "/products/{key}/details" s.HandleFunc("/{key}/details"), ProductDetailsHandler) Now let's see how to build registered URLs. Routes can be named. All routes that define a name can have their URLs built, or "reversed". We define a name calling Name() on a route. For example: r := mux.NewRouter() r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler). Name("article") To build a URL, get the route and call the URL() method, passing a sequence of key/value pairs for the route variables. For the previous route, we would do: url, err := r.Get("article").URL("category", "technology", "id", "42") ...and the result will be a url.URL with the following path: "/articles/technology/42" This also works for host variables: r := mux.NewRouter() r.Host("{subdomain}.domain.com"). Path("/articles/{category}/{id:[0-9]+}"). HandlerFunc(ArticleHandler). Name("article") // url.String() will be "http://news.domain.com/articles/technology/42" url, err := r.Get("article").URL("subdomain", "news", "category", "technology", "id", "42") All variables defined in the route are required, and their values must conform to the corresponding patterns. These requirements guarantee that a generated URL will always match a registered route -- the only exception is for explicitly defined "build-only" routes which never match. There's also a way to build only the URL host or path for a route: use the methods URLHost() or URLPath() instead. For the previous route, we would do: // "http://news.domain.com/" host, err := r.Get("article").URLHost("subdomain", "news") // "/articles/technology/42" path, err := r.Get("article").URLPath("category", "technology", "id", "42") And if you use subrouters, host and path defined separately can be built as well: r := mux.NewRouter() s := r.Host("{subdomain}.domain.com").Subrouter() s.Path("/articles/{category}/{id:[0-9]+}"). HandlerFunc(ArticleHandler). Name("article") // "http://news.domain.com/articles/technology/42" url, err := r.Get("article").URL("subdomain", "news", "category", "technology", "id", "42") */ package mux golang-mux-dev-0.0~git20130701/mux.go000066400000000000000000000216601216706530600170760ustar00rootroot00000000000000// 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 ( "fmt" "net/http" "path" "github.com/gorilla/context" ) // NewRouter returns a new router instance. func NewRouter() *Router { return &Router{namedRoutes: make(map[string]*Route)} } // 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 } // 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 } } 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) { // Clean path to canonical form and redirect. if p := cleanPath(req.URL.Path); p != req.URL.Path { 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 setVars(req, match.Vars) setCurrentRoute(req, match.Route) } if handler == nil { if r.NotFoundHandler == nil { r.NotFoundHandler = http.NotFoundHandler() } handler = r.NotFoundHandler } defer context.Clear(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 slash behavior for new routes. // // When true, if the route path is "/path/", accessing "/path" will redirect // to the former and vice versa. // // Special case: when a route sets a path prefix, strict slash is // automatically set to false for that route because the redirect behavior // can't be determined for prefixes. func (r *Router) StrictSlash(value bool) *Router { r.strictSlash = value 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 } // ---------------------------------------------------------------------------- // Route factories // ---------------------------------------------------------------------------- // NewRoute registers an empty route. func (r *Router) NewRoute() *Route { route := &Route{parent: r, strictSlash: r.strictSlash} 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...) } // ---------------------------------------------------------------------------- // 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 := context.Get(r, varsKey); rv != nil { return rv.(map[string]string) } return nil } // CurrentRoute returns the matched route for the current request, if any. func CurrentRoute(r *http.Request) *Route { if rv := context.Get(r, routeKey); rv != nil { return rv.(*Route) } return nil } func setVars(r *http.Request, val interface{}) { context.Set(r, varsKey, val) } func setCurrentRoute(r *http.Request, val interface{}) { context.Set(r, routeKey, val) } // ---------------------------------------------------------------------------- // Helpers // ---------------------------------------------------------------------------- // 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 } // mapFromPairs converts variadic string parameters to a string map. func mapFromPairs(pairs ...string) (map[string]string, error) { length := len(pairs) if length%2 != 0 { return nil, fmt.Errorf( "mux: number of parameters must be multiple of 2, got %v", pairs) } m := make(map[string]string, length/2) for i := 0; i < length; i += 2 { m[pairs[i]] = pairs[i+1] } 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 } // matchMap returns true if the given key/value pairs exist in a given map. func matchMap(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 } golang-mux-dev-0.0~git20130701/mux_test.go000066400000000000000000000522751216706530600201430ustar00rootroot00000000000000// 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 ( "fmt" "net/http" "testing" ) type routeTest struct { title string // title of the test route *Route // the route being tested request *http.Request // a request to test the route vars map[string]string // the expected vars of the match host string // the expected host of the match path string // the expected path of the match shouldMatch bool // whether the request is expected to match the route at all } func TestHost(t *testing.T) { // newRequestHost a new request with a method, url, and host header newRequestHost := func(method, url, host string) *http.Request { req, err := http.NewRequest(method, url, nil) if err != nil { panic(err) } req.Host = host return req } tests := []routeTest{ { title: "Host route match", route: new(Route).Host("aaa.bbb.ccc"), request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"), vars: map[string]string{}, host: "aaa.bbb.ccc", path: "", shouldMatch: true, }, { title: "Host route, wrong host in request URL", route: new(Route).Host("aaa.bbb.ccc"), request: newRequest("GET", "http://aaa.222.ccc/111/222/333"), vars: map[string]string{}, host: "aaa.bbb.ccc", path: "", shouldMatch: false, }, { title: "Host route with port, match", route: new(Route).Host("aaa.bbb.ccc:1234"), request: newRequest("GET", "http://aaa.bbb.ccc:1234/111/222/333"), vars: map[string]string{}, host: "aaa.bbb.ccc:1234", path: "", shouldMatch: true, }, { title: "Host route with port, wrong port in request URL", route: new(Route).Host("aaa.bbb.ccc:1234"), request: newRequest("GET", "http://aaa.bbb.ccc:9999/111/222/333"), vars: map[string]string{}, host: "aaa.bbb.ccc:1234", path: "", shouldMatch: false, }, { title: "Host route, match with host in request header", route: new(Route).Host("aaa.bbb.ccc"), request: newRequestHost("GET", "/111/222/333", "aaa.bbb.ccc"), vars: map[string]string{}, host: "aaa.bbb.ccc", path: "", shouldMatch: true, }, { title: "Host route, wrong host in request header", route: new(Route).Host("aaa.bbb.ccc"), request: newRequestHost("GET", "/111/222/333", "aaa.222.ccc"), vars: map[string]string{}, host: "aaa.bbb.ccc", path: "", shouldMatch: false, }, // BUG {new(Route).Host("aaa.bbb.ccc:1234"), newRequestHost("GET", "/111/222/333", "aaa.bbb.ccc:1234"), map[string]string{}, "aaa.bbb.ccc:1234", "", true}, { title: "Host route with port, wrong host in request header", route: new(Route).Host("aaa.bbb.ccc:1234"), request: newRequestHost("GET", "/111/222/333", "aaa.bbb.ccc:9999"), vars: map[string]string{}, host: "aaa.bbb.ccc:1234", path: "", shouldMatch: false, }, { title: "Host route with pattern, match", route: new(Route).Host("aaa.{v1:[a-z]{3}}.ccc"), request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"), vars: map[string]string{"v1": "bbb"}, host: "aaa.bbb.ccc", path: "", shouldMatch: true, }, { title: "Host route with pattern, wrong host in request URL", route: new(Route).Host("aaa.{v1:[a-z]{3}}.ccc"), request: newRequest("GET", "http://aaa.222.ccc/111/222/333"), vars: map[string]string{"v1": "bbb"}, host: "aaa.bbb.ccc", path: "", shouldMatch: false, }, { title: "Host route with multiple patterns, match", route: new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}"), request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"), vars: map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc"}, host: "aaa.bbb.ccc", path: "", shouldMatch: true, }, { title: "Host route with multiple patterns, wrong host in request URL", route: new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}"), request: newRequest("GET", "http://aaa.222.ccc/111/222/333"), vars: map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc"}, host: "aaa.bbb.ccc", path: "", shouldMatch: false, }, } for _, test := range tests { testRoute(t, test) } } func TestPath(t *testing.T) { tests := []routeTest{ { title: "Path route, match", route: new(Route).Path("/111/222/333"), request: newRequest("GET", "http://localhost/111/222/333"), vars: map[string]string{}, host: "", path: "/111/222/333", shouldMatch: true, }, { title: "Path route, wrong path in request in request URL", route: new(Route).Path("/111/222/333"), request: newRequest("GET", "http://localhost/1/2/3"), vars: map[string]string{}, host: "", path: "/111/222/333", shouldMatch: false, }, { title: "Path route with pattern, match", route: new(Route).Path("/111/{v1:[0-9]{3}}/333"), request: newRequest("GET", "http://localhost/111/222/333"), vars: map[string]string{"v1": "222"}, host: "", path: "/111/222/333", shouldMatch: true, }, { title: "Path route with pattern, URL in request does not match", route: new(Route).Path("/111/{v1:[0-9]{3}}/333"), request: newRequest("GET", "http://localhost/111/aaa/333"), vars: map[string]string{"v1": "222"}, host: "", path: "/111/222/333", shouldMatch: false, }, { title: "Path route with multiple patterns, match", route: new(Route).Path("/{v1:[0-9]{3}}/{v2:[0-9]{3}}/{v3:[0-9]{3}}"), request: newRequest("GET", "http://localhost/111/222/333"), vars: map[string]string{"v1": "111", "v2": "222", "v3": "333"}, host: "", path: "/111/222/333", shouldMatch: true, }, { title: "Path route with multiple patterns, URL in request does not match", route: new(Route).Path("/{v1:[0-9]{3}}/{v2:[0-9]{3}}/{v3:[0-9]{3}}"), request: newRequest("GET", "http://localhost/111/aaa/333"), vars: map[string]string{"v1": "111", "v2": "222", "v3": "333"}, host: "", path: "/111/222/333", shouldMatch: false, }, } for _, test := range tests { testRoute(t, test) } } func TestPathPrefix(t *testing.T) { tests := []routeTest{ { title: "PathPrefix route, match", route: new(Route).PathPrefix("/111"), request: newRequest("GET", "http://localhost/111/222/333"), vars: map[string]string{}, host: "", path: "/111", shouldMatch: true, }, { title: "PathPrefix route, URL prefix in request does not match", route: new(Route).PathPrefix("/111"), request: newRequest("GET", "http://localhost/1/2/3"), vars: map[string]string{}, host: "", path: "/111", shouldMatch: false, }, { title: "PathPrefix route with pattern, match", route: new(Route).PathPrefix("/111/{v1:[0-9]{3}}"), request: newRequest("GET", "http://localhost/111/222/333"), vars: map[string]string{"v1": "222"}, host: "", path: "/111/222", shouldMatch: true, }, { title: "PathPrefix route with pattern, URL prefix in request does not match", route: new(Route).PathPrefix("/111/{v1:[0-9]{3}}"), request: newRequest("GET", "http://localhost/111/aaa/333"), vars: map[string]string{"v1": "222"}, host: "", path: "/111/222", shouldMatch: false, }, { title: "PathPrefix route with multiple patterns, match", route: new(Route).PathPrefix("/{v1:[0-9]{3}}/{v2:[0-9]{3}}"), request: newRequest("GET", "http://localhost/111/222/333"), vars: map[string]string{"v1": "111", "v2": "222"}, host: "", path: "/111/222", shouldMatch: true, }, { title: "PathPrefix route with multiple patterns, URL prefix in request does not match", route: new(Route).PathPrefix("/{v1:[0-9]{3}}/{v2:[0-9]{3}}"), request: newRequest("GET", "http://localhost/111/aaa/333"), vars: map[string]string{"v1": "111", "v2": "222"}, host: "", path: "/111/222", shouldMatch: false, }, } for _, test := range tests { testRoute(t, test) } } func TestHostPath(t *testing.T) { tests := []routeTest{ { title: "Host and Path route, match", route: new(Route).Host("aaa.bbb.ccc").Path("/111/222/333"), request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"), vars: map[string]string{}, host: "", path: "", shouldMatch: true, }, { title: "Host and Path route, wrong host in request URL", route: new(Route).Host("aaa.bbb.ccc").Path("/111/222/333"), request: newRequest("GET", "http://aaa.222.ccc/111/222/333"), vars: map[string]string{}, host: "", path: "", shouldMatch: false, }, { title: "Host and Path route with pattern, match", route: new(Route).Host("aaa.{v1:[a-z]{3}}.ccc").Path("/111/{v2:[0-9]{3}}/333"), request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"), vars: map[string]string{"v1": "bbb", "v2": "222"}, host: "aaa.bbb.ccc", path: "/111/222/333", shouldMatch: true, }, { title: "Host and Path route with pattern, URL in request does not match", route: new(Route).Host("aaa.{v1:[a-z]{3}}.ccc").Path("/111/{v2:[0-9]{3}}/333"), request: newRequest("GET", "http://aaa.222.ccc/111/222/333"), vars: map[string]string{"v1": "bbb", "v2": "222"}, host: "aaa.bbb.ccc", path: "/111/222/333", shouldMatch: false, }, { title: "Host and Path route with multiple patterns, match", route: new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}").Path("/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}"), request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"), vars: map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc", "v4": "111", "v5": "222", "v6": "333"}, host: "aaa.bbb.ccc", path: "/111/222/333", shouldMatch: true, }, { title: "Host and Path route with multiple patterns, URL in request does not match", route: new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}").Path("/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}"), request: newRequest("GET", "http://aaa.222.ccc/111/222/333"), vars: map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc", "v4": "111", "v5": "222", "v6": "333"}, host: "aaa.bbb.ccc", path: "/111/222/333", shouldMatch: false, }, } for _, test := range tests { testRoute(t, test) } } func TestHeaders(t *testing.T) { // newRequestHeaders creates a new request with a method, url, and headers newRequestHeaders := func(method, url string, headers map[string]string) *http.Request { req, err := http.NewRequest(method, url, nil) if err != nil { panic(err) } for k, v := range headers { req.Header.Add(k, v) } return req } tests := []routeTest{ { title: "Headers route, match", route: new(Route).Headers("foo", "bar", "baz", "ding"), request: newRequestHeaders("GET", "http://localhost", map[string]string{"foo": "bar", "baz": "ding"}), vars: map[string]string{}, host: "", path: "", shouldMatch: true, }, { title: "Headers route, bad header values", route: new(Route).Headers("foo", "bar", "baz", "ding"), request: newRequestHeaders("GET", "http://localhost", map[string]string{"foo": "bar", "baz": "dong"}), vars: map[string]string{}, host: "", path: "", shouldMatch: false, }, } for _, test := range tests { testRoute(t, test) } } func TestMethods(t *testing.T) { tests := []routeTest{ { title: "Methods route, match GET", route: new(Route).Methods("GET", "POST"), request: newRequest("GET", "http://localhost"), vars: map[string]string{}, host: "", path: "", shouldMatch: true, }, { title: "Methods route, match POST", route: new(Route).Methods("GET", "POST"), request: newRequest("POST", "http://localhost"), vars: map[string]string{}, host: "", path: "", shouldMatch: true, }, { title: "Methods route, bad method", route: new(Route).Methods("GET", "POST"), request: newRequest("PUT", "http://localhost"), vars: map[string]string{}, host: "", path: "", shouldMatch: false, }, } for _, test := range tests { testRoute(t, test) } } func TestQueries(t *testing.T) { tests := []routeTest{ { title: "Queries route, match", route: new(Route).Queries("foo", "bar", "baz", "ding"), request: newRequest("GET", "http://localhost?foo=bar&baz=ding"), vars: map[string]string{}, host: "", path: "", shouldMatch: true, }, { title: "Queries route, bad query", route: new(Route).Queries("foo", "bar", "baz", "ding"), request: newRequest("GET", "http://localhost?foo=bar&baz=dong"), vars: map[string]string{}, host: "", path: "", shouldMatch: false, }, } for _, test := range tests { testRoute(t, test) } } func TestSchemes(t *testing.T) { tests := []routeTest{ // Schemes { title: "Schemes route, match https", route: new(Route).Schemes("https", "ftp"), request: newRequest("GET", "https://localhost"), vars: map[string]string{}, host: "", path: "", shouldMatch: true, }, { title: "Schemes route, match ftp", route: new(Route).Schemes("https", "ftp"), request: newRequest("GET", "ftp://localhost"), vars: map[string]string{}, host: "", path: "", shouldMatch: true, }, { title: "Schemes route, bad scheme", route: new(Route).Schemes("https", "ftp"), request: newRequest("GET", "http://localhost"), vars: map[string]string{}, host: "", path: "", shouldMatch: false, }, } for _, test := range tests { testRoute(t, test) } } func TestMatcherFunc(t *testing.T) { m := func(r *http.Request, m *RouteMatch) bool { if r.URL.Host == "aaa.bbb.ccc" { return true } return false } tests := []routeTest{ { title: "MatchFunc route, match", route: new(Route).MatcherFunc(m), request: newRequest("GET", "http://aaa.bbb.ccc"), vars: map[string]string{}, host: "", path: "", shouldMatch: true, }, { title: "MatchFunc route, non-match", route: new(Route).MatcherFunc(m), request: newRequest("GET", "http://aaa.222.ccc"), vars: map[string]string{}, host: "", path: "", shouldMatch: false, }, } for _, test := range tests { testRoute(t, test) } } func TestSubRouter(t *testing.T) { subrouter1 := new(Route).Host("{v1:[a-z]+}.google.com").Subrouter() subrouter2 := new(Route).PathPrefix("/foo/{v1}").Subrouter() tests := []routeTest{ { route: subrouter1.Path("/{v2:[a-z]+}"), request: newRequest("GET", "http://aaa.google.com/bbb"), vars: map[string]string{"v1": "aaa", "v2": "bbb"}, host: "aaa.google.com", path: "/bbb", shouldMatch: true, }, { route: subrouter1.Path("/{v2:[a-z]+}"), request: newRequest("GET", "http://111.google.com/111"), vars: map[string]string{"v1": "aaa", "v2": "bbb"}, host: "aaa.google.com", path: "/bbb", shouldMatch: false, }, { route: subrouter2.Path("/baz/{v2}"), request: newRequest("GET", "http://localhost/foo/bar/baz/ding"), vars: map[string]string{"v1": "bar", "v2": "ding"}, host: "", path: "/foo/bar/baz/ding", shouldMatch: true, }, { route: subrouter2.Path("/baz/{v2}"), request: newRequest("GET", "http://localhost/foo/bar"), vars: map[string]string{"v1": "bar", "v2": "ding"}, host: "", path: "/foo/bar/baz/ding", shouldMatch: false, }, } for _, test := range tests { testRoute(t, test) } } func TestNamedRoutes(t *testing.T) { r1 := NewRouter() r1.NewRoute().Name("a") r1.NewRoute().Name("b") r1.NewRoute().Name("c") r2 := r1.NewRoute().Subrouter() r2.NewRoute().Name("d") r2.NewRoute().Name("e") r2.NewRoute().Name("f") r3 := r2.NewRoute().Subrouter() r3.NewRoute().Name("g") r3.NewRoute().Name("h") r3.NewRoute().Name("i") if r1.namedRoutes == nil || len(r1.namedRoutes) != 9 { t.Errorf("Expected 9 named routes, got %v", r1.namedRoutes) } else if r1.Get("i") == nil { t.Errorf("Subroute name not registered") } } func TestStrictSlash(t *testing.T) { var r *Router var req *http.Request var route *Route var match *RouteMatch var matched bool // StrictSlash should be ignored for path prefix. // So we register a route ending in slash but it doesn't attempt to add // the slash for a path not ending in slash. r = NewRouter() r.StrictSlash(true) route = r.NewRoute().PathPrefix("/static/") req, _ = http.NewRequest("GET", "http://localhost/static/logo.png", nil) match = new(RouteMatch) matched = r.Match(req, match) if !matched { t.Errorf("Should match request %q -- %v", req.URL.Path, getRouteTemplate(route)) } if match.Handler != nil { t.Errorf("Should not redirect") } } // ---------------------------------------------------------------------------- // Helpers // ---------------------------------------------------------------------------- func getRouteTemplate(route *Route) string { host, path := "none", "none" if route.regexp != nil { if route.regexp.host != nil { host = route.regexp.host.template } if route.regexp.path != nil { path = route.regexp.path.template } } return fmt.Sprintf("Host: %v, Path: %v", host, path) } func testRoute(t *testing.T, test routeTest) { request := test.request route := test.route vars := test.vars shouldMatch := test.shouldMatch host := test.host path := test.path url := test.host + test.path var match RouteMatch ok := route.Match(request, &match) if ok != shouldMatch { msg := "Should match" if !shouldMatch { msg = "Should not match" } t.Errorf("(%v) %v:\nRoute: %#v\nRequest: %#v\nVars: %v\n", test.title, msg, route, request, vars) return } if shouldMatch { if test.vars != nil && !stringMapEqual(test.vars, match.Vars) { t.Errorf("(%v) Vars not equal: expected %v, got %v", test.title, vars, match.Vars) return } if host != "" { u, _ := test.route.URLHost(mapToPairs(match.Vars)...) if host != u.Host { t.Errorf("(%v) URLHost not equal: expected %v, got %v -- %v", test.title, host, u.Host, getRouteTemplate(route)) return } } if path != "" { u, _ := route.URLPath(mapToPairs(match.Vars)...) if path != u.Path { t.Errorf("(%v) URLPath not equal: expected %v, got %v -- %v", test.title, path, u.Path, getRouteTemplate(route)) return } } if url != "" { u, _ := route.URL(mapToPairs(match.Vars)...) if url != u.Host+u.Path { t.Errorf("(%v) URL not equal: expected %v, got %v -- %v", test.title, url, u.Host+u.Path, getRouteTemplate(route)) return } } } } // https://plus.google.com/101022900381697718949/posts/eWy6DjFJ6uW func TestSubrouterHeader(t *testing.T) { expected := "func1 response" func1 := func(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, expected) } func2 := func(http.ResponseWriter, *http.Request) {} r := NewRouter() s := r.Headers("SomeSpecialHeader", "").Subrouter() s.HandleFunc("/", func1).Name("func1") r.HandleFunc("/", func2).Name("func2") req, _ := http.NewRequest("GET", "http://localhost/", nil) req.Header.Add("SomeSpecialHeader", "foo") match := new(RouteMatch) matched := r.Match(req, match) if !matched { t.Errorf("Should match request") } if match.Route.GetName() != "func1" { t.Errorf("Expecting func1 handler, got %s", match.Route.GetName()) } resp := NewRecorder() match.Handler.ServeHTTP(resp, req) if resp.Body.String() != expected { t.Errorf("Expecting %q", expected) } } // mapToPairs converts a string map to a slice of string pairs func mapToPairs(m map[string]string) []string { var i int p := make([]string, len(m)*2) for k, v := range m { p[i] = k p[i+1] = v i += 2 } return p } // stringMapEqual checks the equality of two string maps func stringMapEqual(m1, m2 map[string]string) bool { nil1 := m1 == nil nil2 := m2 == nil if nil1 != nil2 || len(m1) != len(m2) { return false } for k, v := range m1 { if v != m2[k] { return false } } return true } // newRequest is a helper function to create a new request with a method and url func newRequest(method, url string) *http.Request { req, err := http.NewRequest(method, url, nil) if err != nil { panic(err) } return req } golang-mux-dev-0.0~git20130701/old_test.go000066400000000000000000000446761216706530600201160ustar00rootroot00000000000000// Old tests ported to Go1. This is a mess. Want to drop it one day. // Copyright 2011 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 ( "bytes" "net/http" "testing" ) // ---------------------------------------------------------------------------- // ResponseRecorder // ---------------------------------------------------------------------------- // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // ResponseRecorder is an implementation of http.ResponseWriter that // records its mutations for later inspection in tests. type ResponseRecorder struct { Code int // the HTTP response code from WriteHeader HeaderMap http.Header // the HTTP response headers Body *bytes.Buffer // if non-nil, the bytes.Buffer to append written data to Flushed bool } // NewRecorder returns an initialized ResponseRecorder. func NewRecorder() *ResponseRecorder { return &ResponseRecorder{ HeaderMap: make(http.Header), Body: new(bytes.Buffer), } } // DefaultRemoteAddr is the default remote address to return in RemoteAddr if // an explicit DefaultRemoteAddr isn't set on ResponseRecorder. const DefaultRemoteAddr = "1.2.3.4" // Header returns the response headers. func (rw *ResponseRecorder) Header() http.Header { return rw.HeaderMap } // Write always succeeds and writes to rw.Body, if not nil. func (rw *ResponseRecorder) Write(buf []byte) (int, error) { if rw.Body != nil { rw.Body.Write(buf) } if rw.Code == 0 { rw.Code = http.StatusOK } return len(buf), nil } // WriteHeader sets rw.Code. func (rw *ResponseRecorder) WriteHeader(code int) { rw.Code = code } // Flush sets rw.Flushed to true. func (rw *ResponseRecorder) Flush() { rw.Flushed = true } // ---------------------------------------------------------------------------- func TestRouteMatchers(t *testing.T) { var scheme, host, path, query, method string var headers map[string]string var resultVars map[bool]map[string]string router := NewRouter() router.NewRoute().Host("{var1}.google.com"). Path("/{var2:[a-z]+}/{var3:[0-9]+}"). Queries("foo", "bar"). Methods("GET"). Schemes("https"). Headers("x-requested-with", "XMLHttpRequest") router.NewRoute().Host("www.{var4}.com"). PathPrefix("/foo/{var5:[a-z]+}/{var6:[0-9]+}"). Queries("baz", "ding"). Methods("POST"). Schemes("http"). Headers("Content-Type", "application/json") reset := func() { // Everything match. scheme = "https" host = "www.google.com" path = "/product/42" query = "?foo=bar" method = "GET" headers = map[string]string{"X-Requested-With": "XMLHttpRequest"} resultVars = map[bool]map[string]string{ true: map[string]string{"var1": "www", "var2": "product", "var3": "42"}, false: map[string]string{}, } } reset2 := func() { // Everything match. scheme = "http" host = "www.google.com" path = "/foo/product/42/path/that/is/ignored" query = "?baz=ding" method = "POST" headers = map[string]string{"Content-Type": "application/json"} resultVars = map[bool]map[string]string{ true: map[string]string{"var4": "google", "var5": "product", "var6": "42"}, false: map[string]string{}, } } match := func(shouldMatch bool) { url := scheme + "://" + host + path + query request, _ := http.NewRequest(method, url, nil) for key, value := range headers { request.Header.Add(key, value) } var routeMatch RouteMatch matched := router.Match(request, &routeMatch) if matched != shouldMatch { // Need better messages. :) if matched { t.Errorf("Should match.") } else { t.Errorf("Should not match.") } } if matched { currentRoute := routeMatch.Route if currentRoute == nil { t.Errorf("Expected a current route.") } vars := routeMatch.Vars expectedVars := resultVars[shouldMatch] if len(vars) != len(expectedVars) { t.Errorf("Expected vars: %v Got: %v.", expectedVars, vars) } for name, value := range vars { if expectedVars[name] != value { t.Errorf("Expected vars: %v Got: %v.", expectedVars, vars) } } } } // 1st route -------------------------------------------------------------- // Everything match. reset() match(true) // Scheme doesn't match. reset() scheme = "http" match(false) // Host doesn't match. reset() host = "www.mygoogle.com" match(false) // Path doesn't match. reset() path = "/product/notdigits" match(false) // Query doesn't match. reset() query = "?foo=baz" match(false) // Method doesn't match. reset() method = "POST" match(false) // Header doesn't match. reset() headers = map[string]string{} match(false) // Everything match, again. reset() match(true) // 2nd route -------------------------------------------------------------- // Everything match. reset2() match(true) // Scheme doesn't match. reset2() scheme = "https" match(false) // Host doesn't match. reset2() host = "sub.google.com" match(false) // Path doesn't match. reset2() path = "/bar/product/42" match(false) // Query doesn't match. reset2() query = "?foo=baz" match(false) // Method doesn't match. reset2() method = "GET" match(false) // Header doesn't match. reset2() headers = map[string]string{} match(false) // Everything match, again. reset2() match(true) } type headerMatcherTest struct { matcher headerMatcher headers map[string]string result bool } var headerMatcherTests = []headerMatcherTest{ { matcher: headerMatcher(map[string]string{"x-requested-with": "XMLHttpRequest"}), headers: map[string]string{"X-Requested-With": "XMLHttpRequest"}, result: true, }, { matcher: headerMatcher(map[string]string{"x-requested-with": ""}), headers: map[string]string{"X-Requested-With": "anything"}, result: true, }, { matcher: headerMatcher(map[string]string{"x-requested-with": "XMLHttpRequest"}), headers: map[string]string{}, result: false, }, } type hostMatcherTest struct { matcher *Route url string vars map[string]string result bool } var hostMatcherTests = []hostMatcherTest{ { matcher: NewRouter().NewRoute().Host("{foo:[a-z][a-z][a-z]}.{bar:[a-z][a-z][a-z]}.{baz:[a-z][a-z][a-z]}"), url: "http://abc.def.ghi/", vars: map[string]string{"foo": "abc", "bar": "def", "baz": "ghi"}, result: true, }, { matcher: NewRouter().NewRoute().Host("{foo:[a-z][a-z][a-z]}.{bar:[a-z][a-z][a-z]}.{baz:[a-z][a-z][a-z]}"), url: "http://a.b.c/", vars: map[string]string{"foo": "abc", "bar": "def", "baz": "ghi"}, result: false, }, } type methodMatcherTest struct { matcher methodMatcher method string result bool } var methodMatcherTests = []methodMatcherTest{ { matcher: methodMatcher([]string{"GET", "POST", "PUT"}), method: "GET", result: true, }, { matcher: methodMatcher([]string{"GET", "POST", "PUT"}), method: "POST", result: true, }, { matcher: methodMatcher([]string{"GET", "POST", "PUT"}), method: "PUT", result: true, }, { matcher: methodMatcher([]string{"GET", "POST", "PUT"}), method: "DELETE", result: false, }, } type pathMatcherTest struct { matcher *Route url string vars map[string]string result bool } var pathMatcherTests = []pathMatcherTest{ { matcher: NewRouter().NewRoute().Path("/{foo:[0-9][0-9][0-9]}/{bar:[0-9][0-9][0-9]}/{baz:[0-9][0-9][0-9]}"), url: "http://localhost:8080/123/456/789", vars: map[string]string{"foo": "123", "bar": "456", "baz": "789"}, result: true, }, { matcher: NewRouter().NewRoute().Path("/{foo:[0-9][0-9][0-9]}/{bar:[0-9][0-9][0-9]}/{baz:[0-9][0-9][0-9]}"), url: "http://localhost:8080/1/2/3", vars: map[string]string{"foo": "123", "bar": "456", "baz": "789"}, result: false, }, } type queryMatcherTest struct { matcher queryMatcher url string result bool } var queryMatcherTests = []queryMatcherTest{ { matcher: queryMatcher(map[string]string{"foo": "bar", "baz": "ding"}), url: "http://localhost:8080/?foo=bar&baz=ding", result: true, }, { matcher: queryMatcher(map[string]string{"foo": "", "baz": ""}), url: "http://localhost:8080/?foo=anything&baz=anything", result: true, }, { matcher: queryMatcher(map[string]string{"foo": "ding", "baz": "bar"}), url: "http://localhost:8080/?foo=bar&baz=ding", result: false, }, { matcher: queryMatcher(map[string]string{"bar": "foo", "ding": "baz"}), url: "http://localhost:8080/?foo=bar&baz=ding", result: false, }, } type schemeMatcherTest struct { matcher schemeMatcher url string result bool } var schemeMatcherTests = []schemeMatcherTest{ { matcher: schemeMatcher([]string{"http", "https"}), url: "http://localhost:8080/", result: true, }, { matcher: schemeMatcher([]string{"http", "https"}), url: "https://localhost:8080/", result: true, }, { matcher: schemeMatcher([]string{"https"}), url: "http://localhost:8080/", result: false, }, { matcher: schemeMatcher([]string{"http"}), url: "https://localhost:8080/", result: false, }, } type urlBuildingTest struct { route *Route vars []string url string } var urlBuildingTests = []urlBuildingTest{ { route: new(Route).Host("foo.domain.com"), vars: []string{}, url: "http://foo.domain.com", }, { route: new(Route).Host("{subdomain}.domain.com"), vars: []string{"subdomain", "bar"}, url: "http://bar.domain.com", }, { route: new(Route).Host("foo.domain.com").Path("/articles"), vars: []string{}, url: "http://foo.domain.com/articles", }, { route: new(Route).Path("/articles"), vars: []string{}, url: "/articles", }, { route: new(Route).Path("/articles/{category}/{id:[0-9]+}"), vars: []string{"category", "technology", "id", "42"}, url: "/articles/technology/42", }, { route: new(Route).Host("{subdomain}.domain.com").Path("/articles/{category}/{id:[0-9]+}"), vars: []string{"subdomain", "foo", "category", "technology", "id", "42"}, url: "http://foo.domain.com/articles/technology/42", }, } func TestHeaderMatcher(t *testing.T) { for _, v := range headerMatcherTests { request, _ := http.NewRequest("GET", "http://localhost:8080/", nil) for key, value := range v.headers { request.Header.Add(key, value) } var routeMatch RouteMatch result := v.matcher.Match(request, &routeMatch) if result != v.result { if v.result { t.Errorf("%#v: should match %v.", v.matcher, request.Header) } else { t.Errorf("%#v: should not match %v.", v.matcher, request.Header) } } } } func TestHostMatcher(t *testing.T) { for _, v := range hostMatcherTests { request, _ := http.NewRequest("GET", v.url, nil) var routeMatch RouteMatch result := v.matcher.Match(request, &routeMatch) vars := routeMatch.Vars if result != v.result { if v.result { t.Errorf("%#v: should match %v.", v.matcher, v.url) } else { t.Errorf("%#v: should not match %v.", v.matcher, v.url) } } if result { if len(vars) != len(v.vars) { t.Errorf("%#v: vars length should be %v, got %v.", v.matcher, len(v.vars), len(vars)) } for name, value := range vars { if v.vars[name] != value { t.Errorf("%#v: expected value %v for key %v, got %v.", v.matcher, v.vars[name], name, value) } } } else { if len(vars) != 0 { t.Errorf("%#v: vars length should be 0, got %v.", v.matcher, len(vars)) } } } } func TestMethodMatcher(t *testing.T) { for _, v := range methodMatcherTests { request, _ := http.NewRequest(v.method, "http://localhost:8080/", nil) var routeMatch RouteMatch result := v.matcher.Match(request, &routeMatch) if result != v.result { if v.result { t.Errorf("%#v: should match %v.", v.matcher, v.method) } else { t.Errorf("%#v: should not match %v.", v.matcher, v.method) } } } } func TestPathMatcher(t *testing.T) { for _, v := range pathMatcherTests { request, _ := http.NewRequest("GET", v.url, nil) var routeMatch RouteMatch result := v.matcher.Match(request, &routeMatch) vars := routeMatch.Vars if result != v.result { if v.result { t.Errorf("%#v: should match %v.", v.matcher, v.url) } else { t.Errorf("%#v: should not match %v.", v.matcher, v.url) } } if result { if len(vars) != len(v.vars) { t.Errorf("%#v: vars length should be %v, got %v.", v.matcher, len(v.vars), len(vars)) } for name, value := range vars { if v.vars[name] != value { t.Errorf("%#v: expected value %v for key %v, got %v.", v.matcher, v.vars[name], name, value) } } } else { if len(vars) != 0 { t.Errorf("%#v: vars length should be 0, got %v.", v.matcher, len(vars)) } } } } func TestQueryMatcher(t *testing.T) { for _, v := range queryMatcherTests { request, _ := http.NewRequest("GET", v.url, nil) var routeMatch RouteMatch result := v.matcher.Match(request, &routeMatch) if result != v.result { if v.result { t.Errorf("%#v: should match %v.", v.matcher, v.url) } else { t.Errorf("%#v: should not match %v.", v.matcher, v.url) } } } } func TestSchemeMatcher(t *testing.T) { for _, v := range queryMatcherTests { request, _ := http.NewRequest("GET", v.url, nil) var routeMatch RouteMatch result := v.matcher.Match(request, &routeMatch) if result != v.result { if v.result { t.Errorf("%#v: should match %v.", v.matcher, v.url) } else { t.Errorf("%#v: should not match %v.", v.matcher, v.url) } } } } func TestUrlBuilding(t *testing.T) { for _, v := range urlBuildingTests { u, _ := v.route.URL(v.vars...) url := u.String() if url != v.url { t.Errorf("expected %v, got %v", v.url, url) /* reversePath := "" reverseHost := "" if v.route.pathTemplate != nil { reversePath = v.route.pathTemplate.Reverse } if v.route.hostTemplate != nil { reverseHost = v.route.hostTemplate.Reverse } t.Errorf("%#v:\nexpected: %q\ngot: %q\nreverse path: %q\nreverse host: %q", v.route, v.url, url, reversePath, reverseHost) */ } } ArticleHandler := func(w http.ResponseWriter, r *http.Request) { } router := NewRouter() router.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).Name("article") url, _ := router.Get("article").URL("category", "technology", "id", "42") expected := "/articles/technology/42" if url.String() != expected { t.Errorf("Expected %v, got %v", expected, url.String()) } } func TestMatchedRouteName(t *testing.T) { routeName := "stock" router := NewRouter() route := router.NewRoute().Path("/products/").Name(routeName) url := "http://www.domain.com/products/" request, _ := http.NewRequest("GET", url, nil) var rv RouteMatch ok := router.Match(request, &rv) if !ok || rv.Route != route { t.Errorf("Expected same route, got %+v.", rv.Route) } retName := rv.Route.GetName() if retName != routeName { t.Errorf("Expected %q, got %q.", routeName, retName) } } func TestSubRouting(t *testing.T) { // Example from docs. router := NewRouter() subrouter := router.NewRoute().Host("www.domain.com").Subrouter() route := subrouter.NewRoute().Path("/products/").Name("products") url := "http://www.domain.com/products/" request, _ := http.NewRequest("GET", url, nil) var rv RouteMatch ok := router.Match(request, &rv) if !ok || rv.Route != route { t.Errorf("Expected same route, got %+v.", rv.Route) } u, _ := router.Get("products").URL() builtUrl := u.String() // Yay, subroute aware of the domain when building! if builtUrl != url { t.Errorf("Expected %q, got %q.", url, builtUrl) } } func TestVariableNames(t *testing.T) { route := new(Route).Host("{arg1}.domain.com").Path("/{arg1}/{arg2:[0-9]+}") if route.err == nil { t.Errorf("Expected error for duplicated variable names") } } func TestRedirectSlash(t *testing.T) { var route *Route var routeMatch RouteMatch r := NewRouter() r.StrictSlash(false) route = r.NewRoute() if route.strictSlash != false { t.Errorf("Expected false redirectSlash.") } r.StrictSlash(true) route = r.NewRoute() if route.strictSlash != true { t.Errorf("Expected true redirectSlash.") } route = new(Route) route.strictSlash = true route.Path("/{arg1}/{arg2:[0-9]+}/") request, _ := http.NewRequest("GET", "http://localhost/foo/123", nil) routeMatch = RouteMatch{} _ = route.Match(request, &routeMatch) vars := routeMatch.Vars if vars["arg1"] != "foo" { t.Errorf("Expected foo.") } if vars["arg2"] != "123" { t.Errorf("Expected 123.") } rsp := NewRecorder() routeMatch.Handler.ServeHTTP(rsp, request) if rsp.HeaderMap.Get("Location") != "http://localhost/foo/123/" { t.Errorf("Expected redirect header.") } route = new(Route) route.strictSlash = true route.Path("/{arg1}/{arg2:[0-9]+}") request, _ = http.NewRequest("GET", "http://localhost/foo/123/", nil) routeMatch = RouteMatch{} _ = route.Match(request, &routeMatch) vars = routeMatch.Vars if vars["arg1"] != "foo" { t.Errorf("Expected foo.") } if vars["arg2"] != "123" { t.Errorf("Expected 123.") } rsp = NewRecorder() routeMatch.Handler.ServeHTTP(rsp, request) if rsp.HeaderMap.Get("Location") != "http://localhost/foo/123" { t.Errorf("Expected redirect header.") } } // Test for the new regexp library, still not available in stable Go. func TestNewRegexp(t *testing.T) { var p *routeRegexp var matches []string tests := map[string]map[string][]string{ "/{foo:a{2}}": { "/a": nil, "/aa": {"aa"}, "/aaa": nil, "/aaaa": nil, }, "/{foo:a{2,}}": { "/a": nil, "/aa": {"aa"}, "/aaa": {"aaa"}, "/aaaa": {"aaaa"}, }, "/{foo:a{2,3}}": { "/a": nil, "/aa": {"aa"}, "/aaa": {"aaa"}, "/aaaa": nil, }, "/{foo:[a-z]{3}}/{bar:[a-z]{2}}": { "/a": nil, "/ab": nil, "/abc": nil, "/abcd": nil, "/abc/ab": {"abc", "ab"}, "/abc/abc": nil, "/abcd/ab": nil, }, `/{foo:\w{3,}}/{bar:\d{2,}}`: { "/a": nil, "/ab": nil, "/abc": nil, "/abc/1": nil, "/abc/12": {"abc", "12"}, "/abcd/12": {"abcd", "12"}, "/abcd/123": {"abcd", "123"}, }, } for pattern, paths := range tests { p, _ = newRouteRegexp(pattern, false, false, false) for path, result := range paths { matches = p.regexp.FindStringSubmatch(path) if result == nil { if matches != nil { t.Errorf("%v should not match %v.", pattern, path) } } else { if len(matches) != len(result)+1 { t.Errorf("Expected %v matches, got %v.", len(result)+1, len(matches)) } else { for k, v := range result { if matches[k+1] != v { t.Errorf("Expected %v, got %v.", v, matches[k+1]) } } } } } } } golang-mux-dev-0.0~git20130701/regexp.go000066400000000000000000000146431216706530600175620ustar00rootroot00000000000000// 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 ( "bytes" "fmt" "net/http" "net/url" "regexp" "strings" ) // newRouteRegexp parses a route template and returns a routeRegexp, // used to match a host or path. // // It will extract named variables, assemble a regexp to be matched, create // a "reverse" template to build URLs and compile regexps to validate variable // values used in URL building. // // Previously we accepted only Python-like identifiers for variable // names ([a-zA-Z_][a-zA-Z0-9_]*), but currently the only restriction is that // name and pattern can't be empty, and names can't contain a colon. func newRouteRegexp(tpl string, matchHost, matchPrefix, strictSlash bool) (*routeRegexp, error) { // Check if it is well-formed. idxs, errBraces := braceIndices(tpl) if errBraces != nil { return nil, errBraces } // Backup the original. template := tpl // Now let's parse it. defaultPattern := "[^/]+" if matchHost { defaultPattern = "[^.]+" matchPrefix, strictSlash = false, false } if matchPrefix { strictSlash = false } // Set a flag for strictSlash. endSlash := false if strictSlash && strings.HasSuffix(tpl, "/") { tpl = tpl[:len(tpl)-1] endSlash = true } varsN := make([]string, len(idxs)/2) varsR := make([]*regexp.Regexp, len(idxs)/2) pattern := bytes.NewBufferString("^") reverse := bytes.NewBufferString("") var end int var err error for i := 0; i < len(idxs); i += 2 { // Set all values we are interested in. raw := tpl[end:idxs[i]] end = idxs[i+1] parts := strings.SplitN(tpl[idxs[i]+1:end-1], ":", 2) name := parts[0] patt := defaultPattern if len(parts) == 2 { patt = parts[1] } // Name or pattern can't be empty. if name == "" || patt == "" { return nil, fmt.Errorf("mux: missing name or pattern in %q", tpl[idxs[i]:end]) } // Build the regexp pattern. fmt.Fprintf(pattern, "%s(%s)", regexp.QuoteMeta(raw), patt) // Build the reverse template. fmt.Fprintf(reverse, "%s%%s", raw) // Append variable name and compiled pattern. varsN[i/2] = name varsR[i/2], err = regexp.Compile(fmt.Sprintf("^%s$", patt)) if err != nil { return nil, err } } // Add the remaining. raw := tpl[end:] pattern.WriteString(regexp.QuoteMeta(raw)) if strictSlash { pattern.WriteString("[/]?") } if !matchPrefix { pattern.WriteByte('$') } reverse.WriteString(raw) if endSlash { reverse.WriteByte('/') } // Compile full regexp. reg, errCompile := regexp.Compile(pattern.String()) if errCompile != nil { return nil, errCompile } // Done! return &routeRegexp{ template: template, matchHost: matchHost, regexp: reg, reverse: reverse.String(), varsN: varsN, varsR: varsR, }, nil } // routeRegexp stores a regexp to match a host or path and information to // collect and validate route variables. type routeRegexp struct { // The unmodified template. template string // True for host match, false for path match. matchHost bool // Expanded regexp. regexp *regexp.Regexp // Reverse template. reverse string // Variable names. varsN []string // Variable regexps (validators). varsR []*regexp.Regexp } // Match matches the regexp against the URL host or path. func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool { if !r.matchHost { return r.regexp.MatchString(req.URL.Path) } return r.regexp.MatchString(getHost(req)) } // url builds a URL part using the given values. func (r *routeRegexp) url(pairs ...string) (string, error) { values, err := mapFromPairs(pairs...) if err != nil { return "", err } urlValues := make([]interface{}, len(r.varsN)) for k, v := range r.varsN { value, ok := values[v] if !ok { return "", fmt.Errorf("mux: missing route variable %q", v) } urlValues[k] = value } rv := fmt.Sprintf(r.reverse, urlValues...) if !r.regexp.MatchString(rv) { // The URL is checked against the full regexp, instead of checking // individual variables. This is faster but to provide a good error // message, we check individual regexps if the URL doesn't match. for k, v := range r.varsN { if !r.varsR[k].MatchString(values[v]) { return "", fmt.Errorf( "mux: variable %q doesn't match, expected %q", values[v], r.varsR[k].String()) } } } return rv, nil } // braceIndices returns the first level curly brace indices from a string. // It returns an error in case of unbalanced braces. func braceIndices(s string) ([]int, error) { var level, idx int idxs := make([]int, 0) for i := 0; i < len(s); i++ { switch s[i] { case '{': if level++; level == 1 { idx = i } case '}': if level--; level == 0 { idxs = append(idxs, idx, i+1) } else if level < 0 { return nil, fmt.Errorf("mux: unbalanced braces in %q", s) } } } if level != 0 { return nil, fmt.Errorf("mux: unbalanced braces in %q", s) } return idxs, nil } // ---------------------------------------------------------------------------- // routeRegexpGroup // ---------------------------------------------------------------------------- // routeRegexpGroup groups the route matchers that carry variables. type routeRegexpGroup struct { host *routeRegexp path *routeRegexp } // setMatch extracts the variables from the URL once a route matches. func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route) { // Store host variables. if v.host != nil { hostVars := v.host.regexp.FindStringSubmatch(getHost(req)) if hostVars != nil { for k, v := range v.host.varsN { m.Vars[v] = hostVars[k+1] } } } // Store path variables. if v.path != nil { pathVars := v.path.regexp.FindStringSubmatch(req.URL.Path) if pathVars != nil { for k, v := range v.path.varsN { m.Vars[v] = pathVars[k+1] } // Check if we should redirect. if r.strictSlash { p1 := strings.HasSuffix(req.URL.Path, "/") p2 := strings.HasSuffix(v.path.template, "/") if p1 != p2 { u, _ := url.Parse(req.URL.String()) if p1 { u.Path = u.Path[:len(u.Path)-1] } else { u.Path += "/" } m.Handler = http.RedirectHandler(u.String(), 301) } } } } } // getHost tries its best to return the request host. func getHost(r *http.Request) string { if !r.URL.IsAbs() { host := r.Host // Slice off any port information. if i := strings.Index(host, ":"); i != -1 { host = host[:i] } return host } return r.URL.Host } golang-mux-dev-0.0~git20130701/route.go000066400000000000000000000334541216706530600174270ustar00rootroot00000000000000// 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" "net/url" "strings" ) // Route stores information to match a request and build URLs. type Route struct { // Parent where the route was registered (a Router). parent parentRoute // Request handler for the route. handler http.Handler // List of matchers. matchers []matcher // Manager for the variables from host and path. regexp *routeRegexpGroup // If true, when the path pattern is "/path/", accessing "/path" will // redirect to the former and vice versa. strictSlash bool // If true, this route never matches: it is only used to build URLs. buildOnly bool // The name used to build URLs. name string // Error resulted from building a route. err error } // Match matches the route against the request. func (r *Route) Match(req *http.Request, match *RouteMatch) bool { if r.buildOnly || r.err != nil { return false } // Match everything. for _, m := range r.matchers { if matched := m.Match(req, match); !matched { return false } } // Yay, we have a match. Let's collect some info about it. if match.Route == nil { match.Route = r } if match.Handler == nil { match.Handler = r.handler } if match.Vars == nil { match.Vars = make(map[string]string) } // Set variables. if r.regexp != nil { r.regexp.setMatch(req, match, r) } return true } // ---------------------------------------------------------------------------- // Route attributes // ---------------------------------------------------------------------------- // GetError returns an error resulted from building the route, if any. func (r *Route) GetError() error { return r.err } // BuildOnly sets the route to never match: it is only used to build URLs. func (r *Route) BuildOnly() *Route { r.buildOnly = true return r } // Handler -------------------------------------------------------------------- // Handler sets a handler for the route. func (r *Route) Handler(handler http.Handler) *Route { if r.err == nil { r.handler = handler } return r } // HandlerFunc sets a handler function for the route. func (r *Route) HandlerFunc(f func(http.ResponseWriter, *http.Request)) *Route { return r.Handler(http.HandlerFunc(f)) } // GetHandler returns the handler for the route, if any. func (r *Route) GetHandler() http.Handler { return r.handler } // Name ----------------------------------------------------------------------- // Name sets the name for the route, used to build URLs. // If the name was registered already it will be overwritten. func (r *Route) Name(name string) *Route { if r.name != "" { r.err = fmt.Errorf("mux: route already has name %q, can't set %q", r.name, name) } if r.err == nil { r.name = name r.getNamedRoutes()[name] = r } return r } // GetName returns the name for the route, if any. func (r *Route) GetName() string { return r.name } // ---------------------------------------------------------------------------- // Matchers // ---------------------------------------------------------------------------- // matcher types try to match a request. type matcher interface { Match(*http.Request, *RouteMatch) bool } // addMatcher adds a matcher to the route. func (r *Route) addMatcher(m matcher) *Route { if r.err == nil { r.matchers = append(r.matchers, m) } return r } // addRegexpMatcher adds a host or path matcher and builder to a route. func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix bool) error { if r.err != nil { return r.err } r.regexp = r.getRegexpGroup() if !matchHost { if len(tpl) == 0 || tpl[0] != '/' { return fmt.Errorf("mux: path must start with a slash, got %q", tpl) } if r.regexp.path != nil { tpl = strings.TrimRight(r.regexp.path.template, "/") + tpl } } rr, err := newRouteRegexp(tpl, matchHost, matchPrefix, r.strictSlash) if err != nil { return err } if matchHost { if r.regexp.path != nil { if err = uniqueVars(rr.varsN, r.regexp.path.varsN); err != nil { return err } } r.regexp.host = rr } else { if r.regexp.host != nil { if err = uniqueVars(rr.varsN, r.regexp.host.varsN); err != nil { return err } } r.regexp.path = rr } r.addMatcher(rr) return nil } // Headers -------------------------------------------------------------------- // headerMatcher matches the request against header values. type headerMatcher map[string]string func (m headerMatcher) Match(r *http.Request, match *RouteMatch) bool { return matchMap(m, r.Header, true) } // Headers adds a matcher for request header values. // It accepts a sequence of key/value pairs to be matched. For example: // // r := mux.NewRouter() // r.Headers("Content-Type", "application/json", // "X-Requested-With", "XMLHttpRequest") // // The above route will only match if both request header values match. // // It the value is an empty string, it will match any value if the key is set. func (r *Route) Headers(pairs ...string) *Route { if r.err == nil { var headers map[string]string headers, r.err = mapFromPairs(pairs...) return r.addMatcher(headerMatcher(headers)) } return r } // Host ----------------------------------------------------------------------- // Host adds a matcher for the URL host. // It accepts a template with zero or more URL variables enclosed by {}. // Variables can define an optional regexp pattern to me matched: // // - {name} matches anything until the next dot. // // - {name:pattern} matches the given regexp pattern. // // For example: // // r := mux.NewRouter() // r.Host("www.domain.com") // r.Host("{subdomain}.domain.com") // r.Host("{subdomain:[a-z]+}.domain.com") // // Variable names must be unique in a given route. They can be retrieved // calling mux.Vars(request). func (r *Route) Host(tpl string) *Route { r.err = r.addRegexpMatcher(tpl, true, false) return r } // MatcherFunc ---------------------------------------------------------------- // MatcherFunc is the function signature used by custom matchers. type MatcherFunc func(*http.Request, *RouteMatch) bool func (m MatcherFunc) Match(r *http.Request, match *RouteMatch) bool { return m(r, match) } // MatcherFunc adds a custom function to be used as request matcher. func (r *Route) MatcherFunc(f MatcherFunc) *Route { return r.addMatcher(f) } // Methods -------------------------------------------------------------------- // methodMatcher matches the request against HTTP methods. type methodMatcher []string func (m methodMatcher) Match(r *http.Request, match *RouteMatch) bool { return matchInArray(m, r.Method) } // Methods adds a matcher for HTTP methods. // It accepts a sequence of one or more methods to be matched, e.g.: // "GET", "POST", "PUT". func (r *Route) Methods(methods ...string) *Route { for k, v := range methods { methods[k] = strings.ToUpper(v) } return r.addMatcher(methodMatcher(methods)) } // Path ----------------------------------------------------------------------- // Path adds a matcher for the URL path. // It accepts a template with zero or more URL variables enclosed by {}. // Variables can define an optional regexp pattern to me matched: // // - {name} matches anything until the next slash. // // - {name:pattern} matches the given regexp pattern. // // For example: // // r := mux.NewRouter() // r.Path("/products/").Handler(ProductsHandler) // r.Path("/products/{key}").Handler(ProductsHandler) // r.Path("/articles/{category}/{id:[0-9]+}"). // Handler(ArticleHandler) // // Variable names must be unique in a given route. They can be retrieved // calling mux.Vars(request). func (r *Route) Path(tpl string) *Route { r.err = r.addRegexpMatcher(tpl, false, false) return r } // PathPrefix ----------------------------------------------------------------- // PathPrefix adds a matcher for the URL path prefix. func (r *Route) PathPrefix(tpl string) *Route { r.strictSlash = false r.err = r.addRegexpMatcher(tpl, false, true) return r } // Query ---------------------------------------------------------------------- // queryMatcher matches the request against URL queries. type queryMatcher map[string]string func (m queryMatcher) Match(r *http.Request, match *RouteMatch) bool { return matchMap(m, r.URL.Query(), false) } // Queries adds a matcher for URL query values. // It accepts a sequence of key/value pairs. For example: // // r := mux.NewRouter() // r.Queries("foo", "bar", "baz", "ding") // // The above route will only match if the URL contains the defined queries // values, e.g.: ?foo=bar&baz=ding. // // It the value is an empty string, it will match any value if the key is set. func (r *Route) Queries(pairs ...string) *Route { if r.err == nil { var queries map[string]string queries, r.err = mapFromPairs(pairs...) return r.addMatcher(queryMatcher(queries)) } return r } // Schemes -------------------------------------------------------------------- // schemeMatcher matches the request against URL schemes. type schemeMatcher []string func (m schemeMatcher) Match(r *http.Request, match *RouteMatch) bool { return matchInArray(m, r.URL.Scheme) } // Schemes adds a matcher for URL schemes. // It accepts a sequence schemes to be matched, e.g.: "http", "https". func (r *Route) Schemes(schemes ...string) *Route { for k, v := range schemes { schemes[k] = strings.ToLower(v) } return r.addMatcher(schemeMatcher(schemes)) } // Subrouter ------------------------------------------------------------------ // Subrouter creates a subrouter for the route. // // It will test the inner routes only if the parent route matched. For example: // // r := mux.NewRouter() // s := r.Host("www.domain.com").Subrouter() // s.HandleFunc("/products/", ProductsHandler) // s.HandleFunc("/products/{key}", ProductHandler) // s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler) // // Here, the routes registered in the subrouter won't be tested if the host // doesn't match. func (r *Route) Subrouter() *Router { router := &Router{parent: r, strictSlash: r.strictSlash} r.addMatcher(router) return router } // ---------------------------------------------------------------------------- // URL building // ---------------------------------------------------------------------------- // URL builds a URL for the route. // // It accepts a sequence of key/value pairs for the route variables. For // example, given this route: // // r := mux.NewRouter() // r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler). // Name("article") // // ...a URL for it can be built using: // // url, err := r.Get("article").URL("category", "technology", "id", "42") // // ...which will return an url.URL with the following path: // // "/articles/technology/42" // // This also works for host variables: // // r := mux.NewRouter() // r.Host("{subdomain}.domain.com"). // HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler). // Name("article") // // // url.String() will be "http://news.domain.com/articles/technology/42" // url, err := r.Get("article").URL("subdomain", "news", // "category", "technology", // "id", "42") // // All variables defined in the route are required, and their values must // conform to the corresponding patterns. func (r *Route) URL(pairs ...string) (*url.URL, error) { if r.err != nil { return nil, r.err } if r.regexp == nil { return nil, errors.New("mux: route doesn't have a host or path") } var scheme, host, path string var err error if r.regexp.host != nil { // Set a default scheme. scheme = "http" if host, err = r.regexp.host.url(pairs...); err != nil { return nil, err } } if r.regexp.path != nil { if path, err = r.regexp.path.url(pairs...); err != nil { return nil, err } } return &url.URL{ Scheme: scheme, Host: host, Path: path, }, nil } // URLHost builds the host part of the URL for a route. See Route.URL(). // // The route must have a host defined. func (r *Route) URLHost(pairs ...string) (*url.URL, error) { if r.err != nil { return nil, r.err } if r.regexp == nil || r.regexp.host == nil { return nil, errors.New("mux: route doesn't have a host") } host, err := r.regexp.host.url(pairs...) if err != nil { return nil, err } return &url.URL{ Scheme: "http", Host: host, }, nil } // URLPath builds the path part of the URL for a route. See Route.URL(). // // The route must have a path defined. func (r *Route) URLPath(pairs ...string) (*url.URL, error) { if r.err != nil { return nil, r.err } if r.regexp == nil || r.regexp.path == nil { return nil, errors.New("mux: route doesn't have a path") } path, err := r.regexp.path.url(pairs...) if err != nil { return nil, err } return &url.URL{ Path: path, }, nil } // ---------------------------------------------------------------------------- // parentRoute // ---------------------------------------------------------------------------- // parentRoute allows routes to know about parent host and path definitions. type parentRoute interface { getNamedRoutes() map[string]*Route getRegexpGroup() *routeRegexpGroup } // getNamedRoutes returns the map where named routes are registered. func (r *Route) getNamedRoutes() map[string]*Route { if r.parent == nil { // During tests router is not always set. r.parent = NewRouter() } return r.parent.getNamedRoutes() } // getRegexpGroup returns regexp definitions from this route. func (r *Route) getRegexpGroup() *routeRegexpGroup { if r.regexp == nil { if r.parent == nil { // During tests router is not always set. r.parent = NewRouter() } regexp := r.parent.getRegexpGroup() if regexp == nil { r.regexp = new(routeRegexpGroup) } else { // Copy. r.regexp = &routeRegexpGroup{ host: regexp.host, path: regexp.path, } } } return r.regexp }