| @ -0,0 +1,27 @@ | |||
| Copyright (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. | |||
| @ -0,0 +1,21 @@ | |||
| // 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) | |||
| } | |||
| } | |||
| @ -0,0 +1,197 @@ | |||
| // 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(myFunc) | |||
| ...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 | |||
| @ -0,0 +1,335 @@ | |||
| // 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 matched := route.Match(req, match); matched { | |||
| 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 matched := r.Match(req, &match); matched { | |||
| 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 | |||
| } | |||
| @ -0,0 +1,417 @@ | |||
| // 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" | |||
| ) | |||
| func TestRoute(t *testing.T) { | |||
| var route *Route | |||
| var request *http.Request | |||
| var vars map[string]string | |||
| var host, path, url string | |||
| // Setup an id so we can see which test failed. :) | |||
| var idValue int | |||
| id := func() int { | |||
| idValue++ | |||
| return idValue | |||
| } | |||
| // Host ------------------------------------------------------------------- | |||
| route = new(Route).Host("aaa.bbb.ccc") | |||
| request, _ = http.NewRequest("GET", "http://aaa.bbb.ccc/111/222/333", nil) | |||
| vars = map[string]string{} | |||
| host = "aaa.bbb.ccc" | |||
| path = "" | |||
| url = host + path | |||
| testRoute(t, id(), true, route, request, vars, host, path, url) | |||
| // Non-match for the same config. | |||
| request, _ = http.NewRequest("GET", "http://aaa.222.ccc/111/222/333", nil) | |||
| testRoute(t, id(), false, route, request, vars, host, path, url) | |||
| route = new(Route).Host("aaa.{v1:[a-z]{3}}.ccc") | |||
| request, _ = http.NewRequest("GET", "http://aaa.bbb.ccc/111/222/333", nil) | |||
| vars = map[string]string{"v1": "bbb"} | |||
| host = "aaa.bbb.ccc" | |||
| path = "" | |||
| url = host + path | |||
| testRoute(t, id(), true, route, request, vars, host, path, url) | |||
| // Non-match for the same config. | |||
| request, _ = http.NewRequest("GET", "http://aaa.222.ccc/111/222/333", nil) | |||
| testRoute(t, id(), false, route, request, vars, host, path, url) | |||
| route = new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}") | |||
| request, _ = http.NewRequest("GET", "http://aaa.bbb.ccc/111/222/333", nil) | |||
| vars = map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc"} | |||
| host = "aaa.bbb.ccc" | |||
| path = "" | |||
| url = host + path | |||
| testRoute(t, id(), true, route, request, vars, host, path, url) | |||
| // Non-match for the same config. | |||
| request, _ = http.NewRequest("GET", "http://aaa.222.ccc/111/222/333", nil) | |||
| testRoute(t, id(), false, route, request, vars, host, path, url) | |||
| // Path ------------------------------------------------------------------- | |||
| route = new(Route).Path("/111/222/333") | |||
| request, _ = http.NewRequest("GET", "http://localhost/111/222/333", nil) | |||
| vars = map[string]string{} | |||
| host = "" | |||
| path = "/111/222/333" | |||
| url = host + path | |||
| testRoute(t, id(), true, route, request, vars, host, path, url) | |||
| // Non-match for the same config. | |||
| request, _ = http.NewRequest("GET", "http://localhost/1/2/3", nil) | |||
| testRoute(t, id(), false, route, request, vars, host, path, url) | |||
| route = new(Route).Path("/111/{v1:[0-9]{3}}/333") | |||
| request, _ = http.NewRequest("GET", "http://localhost/111/222/333", nil) | |||
| vars = map[string]string{"v1": "222"} | |||
| host = "" | |||
| path = "/111/222/333" | |||
| url = host + path | |||
| testRoute(t, id(), true, route, request, vars, host, path, url) | |||
| // Non-match for the same config. | |||
| request, _ = http.NewRequest("GET", "http://localhost/111/aaa/333", nil) | |||
| testRoute(t, id(), false, route, request, vars, host, path, url) | |||
| route = new(Route).Path("/{v1:[0-9]{3}}/{v2:[0-9]{3}}/{v3:[0-9]{3}}") | |||
| request, _ = http.NewRequest("GET", "http://localhost/111/222/333", nil) | |||
| vars = map[string]string{"v1": "111", "v2": "222", "v3": "333"} | |||
| host = "" | |||
| path = "/111/222/333" | |||
| url = host + path | |||
| testRoute(t, id(), true, route, request, vars, host, path, url) | |||
| // Non-match for the same config. | |||
| request, _ = http.NewRequest("GET", "http://localhost/111/aaa/333", nil) | |||
| testRoute(t, id(), false, route, request, vars, host, path, url) | |||
| // PathPrefix ------------------------------------------------------------- | |||
| route = new(Route).PathPrefix("/111") | |||
| request, _ = http.NewRequest("GET", "http://localhost/111/222/333", nil) | |||
| vars = map[string]string{} | |||
| host = "" | |||
| path = "/111" | |||
| url = host + path | |||
| testRoute(t, id(), true, route, request, vars, host, path, url) | |||
| // Non-match for the same config. | |||
| request, _ = http.NewRequest("GET", "http://localhost/1/2/3", nil) | |||
| testRoute(t, id(), false, route, request, vars, host, path, url) | |||
| route = new(Route).PathPrefix("/111/{v1:[0-9]{3}}") | |||
| request, _ = http.NewRequest("GET", "http://localhost/111/222/333", nil) | |||
| vars = map[string]string{"v1": "222"} | |||
| host = "" | |||
| path = "/111/222" | |||
| url = host + path | |||
| testRoute(t, id(), true, route, request, vars, host, path, url) | |||
| // Non-match for the same config. | |||
| request, _ = http.NewRequest("GET", "http://localhost/111/aaa/333", nil) | |||
| testRoute(t, id(), false, route, request, vars, host, path, url) | |||
| route = new(Route).PathPrefix("/{v1:[0-9]{3}}/{v2:[0-9]{3}}") | |||
| request, _ = http.NewRequest("GET", "http://localhost/111/222/333", nil) | |||
| vars = map[string]string{"v1": "111", "v2": "222"} | |||
| host = "" | |||
| path = "/111/222" | |||
| url = host + path | |||
| testRoute(t, id(), true, route, request, vars, host, path, url) | |||
| // Non-match for the same config. | |||
| request, _ = http.NewRequest("GET", "http://localhost/111/aaa/333", nil) | |||
| testRoute(t, id(), false, route, request, vars, host, path, url) | |||
| // Host + Path ------------------------------------------------------------ | |||
| route = new(Route).Host("aaa.bbb.ccc").Path("/111/222/333") | |||
| request, _ = http.NewRequest("GET", "http://aaa.bbb.ccc/111/222/333", nil) | |||
| vars = map[string]string{} | |||
| host = "" | |||
| path = "" | |||
| url = host + path | |||
| testRoute(t, id(), true, route, request, vars, host, path, url) | |||
| // Non-match for the same config. | |||
| request, _ = http.NewRequest("GET", "http://aaa.222.ccc/111/222/333", nil) | |||
| testRoute(t, id(), false, route, request, vars, host, path, url) | |||
| route = new(Route).Host("aaa.{v1:[a-z]{3}}.ccc").Path("/111/{v2:[0-9]{3}}/333") | |||
| request, _ = http.NewRequest("GET", "http://aaa.bbb.ccc/111/222/333", nil) | |||
| vars = map[string]string{"v1": "bbb", "v2": "222"} | |||
| host = "aaa.bbb.ccc" | |||
| path = "/111/222/333" | |||
| url = host + path | |||
| testRoute(t, id(), true, route, request, vars, host, path, url) | |||
| // Non-match for the same config. | |||
| request, _ = http.NewRequest("GET", "http://aaa.222.ccc/111/222/333", nil) | |||
| testRoute(t, id(), false, route, request, vars, host, path, url) | |||
| 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, _ = http.NewRequest("GET", "http://aaa.bbb.ccc/111/222/333", nil) | |||
| vars = map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc", "v4": "111", "v5": "222", "v6": "333"} | |||
| host = "aaa.bbb.ccc" | |||
| path = "/111/222/333" | |||
| url = host + path | |||
| testRoute(t, id(), true, route, request, vars, host, path, url) | |||
| // Non-match for the same config. | |||
| request, _ = http.NewRequest("GET", "http://aaa.222.ccc/111/222/333", nil) | |||
| testRoute(t, id(), false, route, request, vars, host, path, url) | |||
| // Headers ---------------------------------------------------------------- | |||
| route = new(Route).Headers("foo", "bar", "baz", "ding") | |||
| request, _ = http.NewRequest("GET", "http://localhost", nil) | |||
| request.Header.Add("foo", "bar") | |||
| request.Header.Add("baz", "ding") | |||
| vars = map[string]string{} | |||
| host = "" | |||
| path = "" | |||
| url = host + path | |||
| testRoute(t, id(), true, route, request, vars, host, path, url) | |||
| // Non-match for the same config. | |||
| request, _ = http.NewRequest("GET", "http://localhost", nil) | |||
| request.Header.Add("foo", "bar") | |||
| request.Header.Add("baz", "dong") | |||
| testRoute(t, id(), false, route, request, vars, host, path, url) | |||
| // Methods ---------------------------------------------------------------- | |||
| route = new(Route).Methods("GET", "POST") | |||
| request, _ = http.NewRequest("GET", "http://localhost", nil) | |||
| vars = map[string]string{} | |||
| host = "" | |||
| path = "" | |||
| url = host + path | |||
| testRoute(t, id(), true, route, request, vars, host, path, url) | |||
| request, _ = http.NewRequest("POST", "http://localhost", nil) | |||
| testRoute(t, id(), true, route, request, vars, host, path, url) | |||
| // Non-match for the same config. | |||
| request, _ = http.NewRequest("PUT", "http://localhost", nil) | |||
| testRoute(t, id(), false, route, request, vars, host, path, url) | |||
| // Queries ---------------------------------------------------------------- | |||
| route = new(Route).Queries("foo", "bar", "baz", "ding") | |||
| request, _ = http.NewRequest("GET", "http://localhost?foo=bar&baz=ding", nil) | |||
| vars = map[string]string{} | |||
| host = "" | |||
| path = "" | |||
| url = host + path | |||
| testRoute(t, id(), true, route, request, vars, host, path, url) | |||
| // Non-match for the same config. | |||
| request, _ = http.NewRequest("GET", "http://localhost?foo=bar&baz=dong", nil) | |||
| testRoute(t, id(), false, route, request, vars, host, path, url) | |||
| // Schemes ---------------------------------------------------------------- | |||
| route = new(Route).Schemes("https", "ftp") | |||
| request, _ = http.NewRequest("GET", "https://localhost", nil) | |||
| vars = map[string]string{} | |||
| host = "" | |||
| path = "" | |||
| url = host + path | |||
| testRoute(t, id(), true, route, request, vars, host, path, url) | |||
| request, _ = http.NewRequest("GET", "ftp://localhost", nil) | |||
| testRoute(t, id(), true, route, request, vars, host, path, url) | |||
| // Non-match for the same config. | |||
| request, _ = http.NewRequest("GET", "http://localhost", nil) | |||
| testRoute(t, id(), false, route, request, vars, host, path, url) | |||
| // Custom ----------------------------------------------------------------- | |||
| m := func(r *http.Request, m *RouteMatch) bool { | |||
| if r.URL.Host == "aaa.bbb.ccc" { | |||
| return true | |||
| } | |||
| return false | |||
| } | |||
| route = new(Route).MatcherFunc(m) | |||
| request, _ = http.NewRequest("GET", "http://aaa.bbb.ccc", nil) | |||
| vars = map[string]string{} | |||
| host = "" | |||
| path = "" | |||
| url = host + path | |||
| testRoute(t, id(), true, route, request, vars, host, path, url) | |||
| // Non-match for the same config. | |||
| request, _ = http.NewRequest("GET", "http://aaa.ccc.bbb", nil) | |||
| testRoute(t, id(), false, route, request, vars, host, path, url) | |||
| } | |||
| func TestSubRouter(t *testing.T) { | |||
| var route *Route | |||
| var request *http.Request | |||
| var vars map[string]string | |||
| var host, path, url string | |||
| subrouter := new(Route).Host("{v1:[a-z]+}.google.com").Subrouter() | |||
| // Setup an id so we can see which test failed. :) | |||
| var idValue int | |||
| id := func() int { | |||
| idValue++ | |||
| return idValue | |||
| } | |||
| // ------------------------------------------------------------------------ | |||
| route = subrouter.Path("/{v2:[a-z]+}") | |||
| request, _ = http.NewRequest("GET", "http://aaa.google.com/bbb", nil) | |||
| vars = map[string]string{"v1": "aaa", "v2": "bbb"} | |||
| host = "aaa.google.com" | |||
| path = "/bbb" | |||
| url = host + path | |||
| testRoute(t, id(), true, route, request, vars, host, path, url) | |||
| // Non-match for the same config. | |||
| request, _ = http.NewRequest("GET", "http://111.google.com/111", nil) | |||
| testRoute(t, id(), false, route, request, vars, host, path, url) | |||
| // ------------------------------------------------------------------------ | |||
| subrouter = new(Route).PathPrefix("/foo/{v1}").Subrouter() | |||
| route = subrouter.Path("/baz/{v2}") | |||
| request, _ = http.NewRequest("GET", "http://localhost/foo/bar/baz/ding", nil) | |||
| vars = map[string]string{"v1": "bar", "v2": "ding"} | |||
| host = "" | |||
| path = "/foo/bar/baz/ding" | |||
| url = host + path | |||
| testRoute(t, id(), true, route, request, vars, host, path, url) | |||
| // Non-match for the same config. | |||
| request, _ = http.NewRequest("GET", "http://localhost/foo/bar", nil) | |||
| testRoute(t, id(), false, route, request, vars, host, path, url) | |||
| } | |||
| 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") | |||
| } | |||
| } | |||
| // ---------------------------------------------------------------------------- | |||
| // 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, id int, shouldMatch bool, route *Route, | |||
| request *http.Request, vars map[string]string, host, path, url string) { | |||
| 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", id, msg, route, request, vars) | |||
| return | |||
| } | |||
| if shouldMatch { | |||
| if vars != nil && !stringMapEqual(vars, match.Vars) { | |||
| t.Errorf("(%v) Vars not equal: expected %v, got %v", id, vars, match.Vars) | |||
| return | |||
| } | |||
| if host != "" { | |||
| u, _ := route.URLHost(mapToPairs(match.Vars)...) | |||
| if host != u.Host { | |||
| t.Errorf("(%v) URLHost not equal: expected %v, got %v -- %v", id, 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", id, 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", id, url, u.Host+u.Path, getRouteTemplate(route)) | |||
| return | |||
| } | |||
| } | |||
| } | |||
| } | |||
| 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") | |||
| } | |||
| } | |||
| 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 | |||
| } | |||
| 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 | |||
| } | |||
| @ -0,0 +1,758 @@ | |||
| // 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]) | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @ -0,0 +1,247 @@ | |||
| // 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 | |||
| } | |||
| @ -0,0 +1,499 @@ | |||
| // 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 | |||
| } | |||