vendor: update gorilla/mux to be compatible with Go 1.7
Signed-off-by: Stephen J Day <stephen.day@docker.com>master
							parent
							
								
									1f0a9dbca0
								
							
						
					
					
						commit
						818ba4babf
					
				|  | @ -13,7 +13,7 @@ github.com/go-ini/ini 2ba15ac2dc9cdf88c110ec2dc0ced7fa45f5678c | ||||||
| github.com/golang/protobuf 8d92cf5fc15a4382f8964b08e1f42a75c0591aa3 | github.com/golang/protobuf 8d92cf5fc15a4382f8964b08e1f42a75c0591aa3 | ||||||
| github.com/gorilla/context 14f550f51af52180c2eefed15e5fd18d63c0a64a | github.com/gorilla/context 14f550f51af52180c2eefed15e5fd18d63c0a64a | ||||||
| github.com/gorilla/handlers 60c7bfde3e33c201519a200a4507a158cc03a17b | github.com/gorilla/handlers 60c7bfde3e33c201519a200a4507a158cc03a17b | ||||||
| github.com/gorilla/mux e444e69cbd2e2e3e0749a2f3c717cec491552bbf | github.com/gorilla/mux 599cba5e7b6137d46ddf58fb1765f5d928e69604 | ||||||
| github.com/inconshreveable/mousetrap 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 | github.com/inconshreveable/mousetrap 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 | ||||||
| github.com/jmespath/go-jmespath bd40a432e4c76585ef6b72d3fd96fb9b6dc7b68d | github.com/jmespath/go-jmespath bd40a432e4c76585ef6b72d3fd96fb9b6dc7b68d | ||||||
| github.com/miekg/dns 271c58e0c14f552178ea321a545ff9af38930f39 | github.com/miekg/dns 271c58e0c14f552178ea321a545ff9af38930f39 | ||||||
|  |  | ||||||
|  | @ -1,7 +1,340 @@ | ||||||
| mux | gorilla/mux | ||||||
| === | === | ||||||
| [](https://travis-ci.org/gorilla/mux) | [](https://godoc.org/github.com/gorilla/mux) | ||||||
|  | [](https://travis-ci.org/gorilla/mux) | ||||||
|  | [](https://sourcegraph.com/github.com/gorilla/mux?badge) | ||||||
| 
 | 
 | ||||||
| gorilla/mux is a powerful URL router and dispatcher. |  | ||||||
| 
 | 
 | ||||||
| Read the full documentation here: http://www.gorillatoolkit.org/pkg/mux | http://www.gorillatoolkit.org/pkg/mux | ||||||
|  | 
 | ||||||
|  | Package `gorilla/mux` implements a request router and dispatcher for matching incoming requests to | ||||||
|  | their respective handler. | ||||||
|  | 
 | ||||||
|  | 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: | ||||||
|  | 
 | ||||||
|  | * It implements the `http.Handler` interface so it is compatible with the standard `http.ServeMux`. | ||||||
|  | * 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. | ||||||
|  | 
 | ||||||
|  | --- | ||||||
|  | 
 | ||||||
|  | * [Install](#install) | ||||||
|  | * [Examples](#examples) | ||||||
|  | * [Matching Routes](#matching-routes) | ||||||
|  | * [Listing Routes](#listing-routes) | ||||||
|  | * [Static Files](#static-files) | ||||||
|  | * [Registered URLs](#registered-urls) | ||||||
|  | * [Full Example](#full-example) | ||||||
|  | 
 | ||||||
|  | --- | ||||||
|  | 
 | ||||||
|  | ## Install | ||||||
|  | 
 | ||||||
|  | With a [correctly configured](https://golang.org/doc/install#testing) Go toolchain: | ||||||
|  | 
 | ||||||
|  | ```sh | ||||||
|  | go get -u github.com/gorilla/mux | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ## Examples | ||||||
|  | 
 | ||||||
|  | Let's start registering a couple of URL paths and handlers: | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  | 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: | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  | 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()`: | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  | func ArticlesCategoryHandler(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 	vars := mux.Vars(r) | ||||||
|  | 	w.WriteHeader(http.StatusOK) | ||||||
|  | 	fmt.Fprintf(w, "Category: %v\n", vars["category"]) | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | And this is all you need to know about the basic usage. More advanced options are explained below. | ||||||
|  | 
 | ||||||
|  | ### Matching Routes | ||||||
|  | 
 | ||||||
|  | Routes can also be restricted to a domain or subdomain. Just define a host pattern to be matched. They can also have variables: | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  | r := mux.NewRouter() | ||||||
|  | // Only matches if domain is "www.example.com". | ||||||
|  | r.Host("www.example.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: | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  | r.PathPrefix("/products/") | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ...or HTTP methods: | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  | r.Methods("GET", "POST") | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ...or URL schemes: | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  | r.Schemes("https") | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ...or header values: | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  | r.Headers("X-Requested-With", "XMLHttpRequest") | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ...or query values: | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  | r.Queries("key", "value") | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ...or to use a custom matcher function: | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  | 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: | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  | r.HandleFunc("/products", ProductsHandler). | ||||||
|  |   Host("www.example.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.example.com`. Create a route for that host and get a "subrouter" from it: | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  | r := mux.NewRouter() | ||||||
|  | s := r.Host("www.example.com").Subrouter() | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Then register routes in the subrouter: | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  | 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.example.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: | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  | 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) | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ### Listing Routes | ||||||
|  | 
 | ||||||
|  | Routes on a mux can be listed using the Router.Walk method—useful for generating documentation: | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  | package main | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  |     "fmt" | ||||||
|  |     "net/http" | ||||||
|  | 
 | ||||||
|  |     "github.com/gorilla/mux" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func handler(w http.ResponseWriter, r *http.Request) { | ||||||
|  |     return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func main() { | ||||||
|  |     r := mux.NewRouter() | ||||||
|  |     r.HandleFunc("/", handler) | ||||||
|  |     r.HandleFunc("/products", handler) | ||||||
|  |     r.HandleFunc("/articles", handler) | ||||||
|  |     r.HandleFunc("/articles/{id}", handler) | ||||||
|  |     r.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error { | ||||||
|  |         t, err := route.GetPathTemplate() | ||||||
|  |         if err != nil { | ||||||
|  |             return err | ||||||
|  |         } | ||||||
|  |         fmt.Println(t) | ||||||
|  |         return nil | ||||||
|  |     }) | ||||||
|  |     http.Handle("/", r) | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ### Static Files | ||||||
|  | 
 | ||||||
|  | Note that the path provided to `PathPrefix()` represents a "wildcard": calling | ||||||
|  | `PathPrefix("/static/").Handler(...)` means that the handler will be passed any | ||||||
|  | request that matches "/static/*". This makes it easy to serve static files with mux: | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  | func main() { | ||||||
|  | 	var dir string | ||||||
|  | 
 | ||||||
|  | 	flag.StringVar(&dir, "dir", ".", "the directory to serve files from. Defaults to the current dir") | ||||||
|  | 	flag.Parse() | ||||||
|  | 	r := mux.NewRouter() | ||||||
|  | 
 | ||||||
|  | 	// This will serve files under http://localhost:8000/static/<filename> | ||||||
|  | 	r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(dir)))) | ||||||
|  | 
 | ||||||
|  | 	srv := &http.Server{ | ||||||
|  | 		Handler:      r, | ||||||
|  | 		Addr:         "127.0.0.1:8000", | ||||||
|  | 		// Good practice: enforce timeouts for servers you create! | ||||||
|  | 		WriteTimeout: 15 * time.Second, | ||||||
|  | 		ReadTimeout:  15 * time.Second, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	log.Fatal(srv.ListenAndServe()) | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ### Registered URLs | ||||||
|  | 
 | ||||||
|  | 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: | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  | 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: | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  | 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: | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  | 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. | ||||||
|  | 
 | ||||||
|  | Regex support also exists for matching Headers within a route. For example, we could do: | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  | r.HeadersRegexp("Content-Type", "application/(text|json)") | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ...and the route will match both requests with a Content-Type of `application/json` as well as `application/text` | ||||||
|  | 
 | ||||||
|  | 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: | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  | // "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: | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  | 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") | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ## Full Example | ||||||
|  | 
 | ||||||
|  | Here's a complete, runnable example of a small `mux` based server: | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  | package main | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"net/http" | ||||||
|  | 	"log" | ||||||
|  | 	"github.com/gorilla/mux" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func YourHandler(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 	w.Write([]byte("Gorilla!\n")) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func main() { | ||||||
|  | 	r := mux.NewRouter() | ||||||
|  | 	// Routes consist of a path and a handler function. | ||||||
|  | 	r.HandleFunc("/", YourHandler) | ||||||
|  | 
 | ||||||
|  | 	// Bind to a port and pass our router in | ||||||
|  | 	log.Fatal(http.ListenAndServe(":8000", r)) | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ## License | ||||||
|  | 
 | ||||||
|  | BSD licensed. See the LICENSE file for details. | ||||||
|  |  | ||||||
|  | @ -0,0 +1,26 @@ | ||||||
|  | // +build !go1.7
 | ||||||
|  | 
 | ||||||
|  | package mux | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"net/http" | ||||||
|  | 
 | ||||||
|  | 	"github.com/gorilla/context" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func contextGet(r *http.Request, key interface{}) interface{} { | ||||||
|  | 	return context.Get(r, key) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func contextSet(r *http.Request, key, val interface{}) *http.Request { | ||||||
|  | 	if val == nil { | ||||||
|  | 		return r | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	context.Set(r, key, val) | ||||||
|  | 	return r | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func contextClear(r *http.Request) { | ||||||
|  | 	context.Clear(r) | ||||||
|  | } | ||||||
|  | @ -0,0 +1,24 @@ | ||||||
|  | // +build go1.7
 | ||||||
|  | 
 | ||||||
|  | package mux | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"net/http" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func contextGet(r *http.Request, key interface{}) interface{} { | ||||||
|  | 	return r.Context().Value(key) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func contextSet(r *http.Request, key, val interface{}) *http.Request { | ||||||
|  | 	if val == nil { | ||||||
|  | 		return r | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return r.WithContext(context.WithValue(r.Context(), key, val)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func contextClear(r *http.Request) { | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | @ -3,7 +3,7 @@ | ||||||
| // license that can be found in the LICENSE file.
 | // license that can be found in the LICENSE file.
 | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
| Package gorilla/mux implements a request router and dispatcher. | Package mux implements a request router and dispatcher. | ||||||
| 
 | 
 | ||||||
| The name mux stands for "HTTP request multiplexer". Like the standard | The name mux stands for "HTTP request multiplexer". Like the standard | ||||||
| http.ServeMux, mux.Router matches incoming requests against a list of | http.ServeMux, mux.Router matches incoming requests against a list of | ||||||
|  | @ -47,12 +47,21 @@ variable will be anything until the next slash. For example: | ||||||
| 	r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler) | 	r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler) | ||||||
| 	r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler) | 	r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler) | ||||||
| 
 | 
 | ||||||
|  | Groups can be used inside patterns, as long as they are non-capturing (?:re). For example: | ||||||
|  | 
 | ||||||
|  | 	r.HandleFunc("/articles/{category}/{sort:(?:asc|desc|new)}", ArticlesCategoryHandler) | ||||||
|  | 
 | ||||||
| The names are used to create a map of route variables which can be retrieved | The names are used to create a map of route variables which can be retrieved | ||||||
| calling mux.Vars(): | calling mux.Vars(): | ||||||
| 
 | 
 | ||||||
| 	vars := mux.Vars(request) | 	vars := mux.Vars(request) | ||||||
| 	category := vars["category"] | 	category := vars["category"] | ||||||
| 
 | 
 | ||||||
|  | Note that if any capturing groups are present, mux will panic() during parsing. To prevent | ||||||
|  | this, convert any capturing groups to non-capturing, e.g. change "/{sort:(asc|desc)}" to | ||||||
|  | "/{sort:(?:asc|desc)}". This is a change from prior versions which behaved unpredictably | ||||||
|  | when capturing groups were present. | ||||||
|  | 
 | ||||||
| And this is all you need to know about the basic usage. More advanced options | And this is all you need to know about the basic usage. More advanced options | ||||||
| are explained below. | are explained below. | ||||||
| 
 | 
 | ||||||
|  | @ -60,8 +69,8 @@ Routes can also be restricted to a domain or subdomain. Just define a host | ||||||
| pattern to be matched. They can also have variables: | pattern to be matched. They can also have variables: | ||||||
| 
 | 
 | ||||||
| 	r := mux.NewRouter() | 	r := mux.NewRouter() | ||||||
| 	// Only matches if domain is "www.domain.com".
 | 	// Only matches if domain is "www.example.com".
 | ||||||
| 	r.Host("www.domain.com") | 	r.Host("www.example.com") | ||||||
| 	// Matches a dynamic subdomain.
 | 	// Matches a dynamic subdomain.
 | ||||||
| 	r.Host("{subdomain:[a-z]+}.domain.com") | 	r.Host("{subdomain:[a-z]+}.domain.com") | ||||||
| 
 | 
 | ||||||
|  | @ -89,12 +98,12 @@ There are several other matchers that can be added. To match path prefixes: | ||||||
| 
 | 
 | ||||||
| 	r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool { | 	r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool { | ||||||
| 		return r.ProtoMajor == 0 | 		return r.ProtoMajor == 0 | ||||||
|     }) | 	}) | ||||||
| 
 | 
 | ||||||
| ...and finally, it is possible to combine several matchers in a single route: | ...and finally, it is possible to combine several matchers in a single route: | ||||||
| 
 | 
 | ||||||
| 	r.HandleFunc("/products", ProductsHandler). | 	r.HandleFunc("/products", ProductsHandler). | ||||||
| 	  Host("www.domain.com"). | 	  Host("www.example.com"). | ||||||
| 	  Methods("GET"). | 	  Methods("GET"). | ||||||
| 	  Schemes("http") | 	  Schemes("http") | ||||||
| 
 | 
 | ||||||
|  | @ -103,11 +112,11 @@ a way to group several routes that share the same requirements. | ||||||
| We call it "subrouting". | We call it "subrouting". | ||||||
| 
 | 
 | ||||||
| For example, let's say we have several URLs that should only match when the | 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" | host is "www.example.com". Create a route for that host and get a "subrouter" | ||||||
| from it: | from it: | ||||||
| 
 | 
 | ||||||
| 	r := mux.NewRouter() | 	r := mux.NewRouter() | ||||||
| 	s := r.Host("www.domain.com").Subrouter() | 	s := r.Host("www.example.com").Subrouter() | ||||||
| 
 | 
 | ||||||
| Then register routes in the subrouter: | Then register routes in the subrouter: | ||||||
| 
 | 
 | ||||||
|  | @ -116,7 +125,7 @@ Then register routes in the subrouter: | ||||||
| 	s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler) | 	s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler) | ||||||
| 
 | 
 | ||||||
| The three URL paths we registered above will only be tested if the domain is | 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 | "www.example.com", because the subrouter is tested first. This is not | ||||||
| only convenient, but also optimizes request matching. You can create | only convenient, but also optimizes request matching. You can create | ||||||
| subrouters combining any attribute matchers accepted by a route. | subrouters combining any attribute matchers accepted by a route. | ||||||
| 
 | 
 | ||||||
|  | @ -136,6 +145,31 @@ the inner routes use it as base for their paths: | ||||||
| 	// "/products/{key}/details"
 | 	// "/products/{key}/details"
 | ||||||
| 	s.HandleFunc("/{key}/details", ProductDetailsHandler) | 	s.HandleFunc("/{key}/details", ProductDetailsHandler) | ||||||
| 
 | 
 | ||||||
|  | Note that the path provided to PathPrefix() represents a "wildcard": calling | ||||||
|  | PathPrefix("/static/").Handler(...) means that the handler will be passed any | ||||||
|  | request that matches "/static/*". This makes it easy to serve static files with mux: | ||||||
|  | 
 | ||||||
|  | 	func main() { | ||||||
|  | 		var dir string | ||||||
|  | 
 | ||||||
|  | 		flag.StringVar(&dir, "dir", ".", "the directory to serve files from. Defaults to the current dir") | ||||||
|  | 		flag.Parse() | ||||||
|  | 		r := mux.NewRouter() | ||||||
|  | 
 | ||||||
|  | 		// This will serve files under http://localhost:8000/static/<filename>
 | ||||||
|  | 		r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(dir)))) | ||||||
|  | 
 | ||||||
|  | 		srv := &http.Server{ | ||||||
|  | 			Handler:      r, | ||||||
|  | 			Addr:         "127.0.0.1:8000", | ||||||
|  | 			// Good practice: enforce timeouts for servers you create!
 | ||||||
|  | 			WriteTimeout: 15 * time.Second, | ||||||
|  | 			ReadTimeout:  15 * time.Second, | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		log.Fatal(srv.ListenAndServe()) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| Now let's see how to build registered URLs. | Now let's see how to build registered URLs. | ||||||
| 
 | 
 | ||||||
| Routes can be named. All routes that define a name can have their URLs built, | Routes can be named. All routes that define a name can have their URLs built, | ||||||
|  | @ -164,14 +198,21 @@ This also works for host variables: | ||||||
| 
 | 
 | ||||||
| 	// url.String() will be "http://news.domain.com/articles/technology/42"
 | 	// url.String() will be "http://news.domain.com/articles/technology/42"
 | ||||||
| 	url, err := r.Get("article").URL("subdomain", "news", | 	url, err := r.Get("article").URL("subdomain", "news", | ||||||
| 									 "category", "technology", | 	                                 "category", "technology", | ||||||
| 									 "id", "42") | 	                                 "id", "42") | ||||||
| 
 | 
 | ||||||
| All variables defined in the route are required, and their values must | All variables defined in the route are required, and their values must | ||||||
| conform to the corresponding patterns. These requirements guarantee that a | conform to the corresponding patterns. These requirements guarantee that a | ||||||
| generated URL will always match a registered route -- the only exception is | generated URL will always match a registered route -- the only exception is | ||||||
| for explicitly defined "build-only" routes which never match. | for explicitly defined "build-only" routes which never match. | ||||||
| 
 | 
 | ||||||
|  | Regex support also exists for matching Headers within a route. For example, we could do: | ||||||
|  | 
 | ||||||
|  | 	r.HeadersRegexp("Content-Type", "application/(text|json)") | ||||||
|  | 
 | ||||||
|  | ...and the route will match both requests with a Content-Type of `application/json` as well as | ||||||
|  | `application/text` | ||||||
|  | 
 | ||||||
| There's also a way to build only the URL host or path for a route: | 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, | use the methods URLHost() or URLPath() instead. For the previous route, | ||||||
| we would do: | we would do: | ||||||
|  | @ -193,7 +234,7 @@ as well: | ||||||
| 
 | 
 | ||||||
| 	// "http://news.domain.com/articles/technology/42"
 | 	// "http://news.domain.com/articles/technology/42"
 | ||||||
| 	url, err := r.Get("article").URL("subdomain", "news", | 	url, err := r.Get("article").URL("subdomain", "news", | ||||||
| 									 "category", "technology", | 	                                 "category", "technology", | ||||||
| 									 "id", "42") | 	                                 "id", "42") | ||||||
| */ | */ | ||||||
| package mux | package mux | ||||||
|  |  | ||||||
|  | @ -5,11 +5,12 @@ | ||||||
| package mux | package mux | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"path" | 	"path" | ||||||
| 
 | 	"regexp" | ||||||
| 	"github.com/gorilla/context" | 	"strings" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // NewRouter returns a new router instance.
 | // NewRouter returns a new router instance.
 | ||||||
|  | @ -46,8 +47,14 @@ type Router struct { | ||||||
| 	namedRoutes map[string]*Route | 	namedRoutes map[string]*Route | ||||||
| 	// See Router.StrictSlash(). This defines the flag for new routes.
 | 	// See Router.StrictSlash(). This defines the flag for new routes.
 | ||||||
| 	strictSlash bool | 	strictSlash bool | ||||||
| 	// If true, do not clear the request context after handling the request
 | 	// See Router.SkipClean(). This defines the flag for new routes.
 | ||||||
|  | 	skipClean bool | ||||||
|  | 	// If true, do not clear the request context after handling the request.
 | ||||||
|  | 	// This has no effect when go1.7+ is used, since the context is stored
 | ||||||
|  | 	// on the request itself.
 | ||||||
| 	KeepContext bool | 	KeepContext bool | ||||||
|  | 	// see Router.UseEncodedPath(). This defines a flag for all routes.
 | ||||||
|  | 	useEncodedPath bool | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Match matches registered routes against the request.
 | // Match matches registered routes against the request.
 | ||||||
|  | @ -57,6 +64,12 @@ func (r *Router) Match(req *http.Request, match *RouteMatch) bool { | ||||||
| 			return true | 			return true | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	// Closest match for a router (includes sub-routers)
 | ||||||
|  | 	if r.NotFoundHandler != nil { | ||||||
|  | 		match.Handler = r.NotFoundHandler | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
| 	return false | 	return false | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -65,35 +78,38 @@ func (r *Router) Match(req *http.Request, match *RouteMatch) bool { | ||||||
| // When there is a match, the route variables can be retrieved calling
 | // When there is a match, the route variables can be retrieved calling
 | ||||||
| // mux.Vars(request).
 | // mux.Vars(request).
 | ||||||
| func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { | func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { | ||||||
| 	// Clean path to canonical form and redirect.
 | 	if !r.skipClean { | ||||||
| 	if p := cleanPath(req.URL.Path); p != req.URL.Path { | 		path := req.URL.Path | ||||||
|  | 		if r.useEncodedPath { | ||||||
|  | 			path = getPath(req) | ||||||
|  | 		} | ||||||
|  | 		// Clean path to canonical form and redirect.
 | ||||||
|  | 		if p := cleanPath(path); p != path { | ||||||
| 
 | 
 | ||||||
| 		// Added 3 lines (Philip Schlump) - It was droping the query string and #whatever from query.
 | 			// Added 3 lines (Philip Schlump) - It was dropping the query string and #whatever from query.
 | ||||||
| 		// This matches with fix in go 1.2 r.c. 4 for same problem.  Go Issue:
 | 			// This matches with fix in go 1.2 r.c. 4 for same problem.  Go Issue:
 | ||||||
| 		// http://code.google.com/p/go/issues/detail?id=5252
 | 			// http://code.google.com/p/go/issues/detail?id=5252
 | ||||||
| 		url := *req.URL | 			url := *req.URL | ||||||
| 		url.Path = p | 			url.Path = p | ||||||
| 		p = url.String() | 			p = url.String() | ||||||
| 
 | 
 | ||||||
| 		w.Header().Set("Location", p) | 			w.Header().Set("Location", p) | ||||||
| 		w.WriteHeader(http.StatusMovedPermanently) | 			w.WriteHeader(http.StatusMovedPermanently) | ||||||
| 		return | 			return | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| 	var match RouteMatch | 	var match RouteMatch | ||||||
| 	var handler http.Handler | 	var handler http.Handler | ||||||
| 	if r.Match(req, &match) { | 	if r.Match(req, &match) { | ||||||
| 		handler = match.Handler | 		handler = match.Handler | ||||||
| 		setVars(req, match.Vars) | 		req = setVars(req, match.Vars) | ||||||
| 		setCurrentRoute(req, match.Route) | 		req = setCurrentRoute(req, match.Route) | ||||||
| 	} | 	} | ||||||
| 	if handler == nil { | 	if handler == nil { | ||||||
| 		handler = r.NotFoundHandler | 		handler = http.NotFoundHandler() | ||||||
| 		if handler == nil { |  | ||||||
| 			handler = http.NotFoundHandler() |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| 	if !r.KeepContext { | 	if !r.KeepContext { | ||||||
| 		defer context.Clear(req) | 		defer contextClear(req) | ||||||
| 	} | 	} | ||||||
| 	handler.ServeHTTP(w, req) | 	handler.ServeHTTP(w, req) | ||||||
| } | } | ||||||
|  | @ -128,6 +144,34 @@ func (r *Router) StrictSlash(value bool) *Router { | ||||||
| 	return r | 	return r | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // SkipClean defines the path cleaning behaviour for new routes. The initial
 | ||||||
|  | // value is false. Users should be careful about which routes are not cleaned
 | ||||||
|  | //
 | ||||||
|  | // When true, if the route path is "/path//to", it will remain with the double
 | ||||||
|  | // slash. This is helpful if you have a route like: /fetch/http://xkcd.com/534/
 | ||||||
|  | //
 | ||||||
|  | // When false, the path will be cleaned, so /fetch/http://xkcd.com/534/ will
 | ||||||
|  | // become /fetch/http/xkcd.com/534
 | ||||||
|  | func (r *Router) SkipClean(value bool) *Router { | ||||||
|  | 	r.skipClean = value | ||||||
|  | 	return r | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // UseEncodedPath tells the router to match the encoded original path
 | ||||||
|  | // to the routes.
 | ||||||
|  | // For eg. "/path/foo%2Fbar/to" will match the path "/path/{var}/to".
 | ||||||
|  | // This behavior has the drawback of needing to match routes against
 | ||||||
|  | // r.RequestURI instead of r.URL.Path. Any modifications (such as http.StripPrefix)
 | ||||||
|  | // to r.URL.Path will not affect routing when this flag is on and thus may
 | ||||||
|  | // induce unintended behavior.
 | ||||||
|  | //
 | ||||||
|  | // If not called, the router will match the unencoded path to the routes.
 | ||||||
|  | // For eg. "/path/foo%2Fbar/to" will match the path "/path/foo/bar/to"
 | ||||||
|  | func (r *Router) UseEncodedPath() *Router { | ||||||
|  | 	r.useEncodedPath = true | ||||||
|  | 	return r | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // ----------------------------------------------------------------------------
 | // ----------------------------------------------------------------------------
 | ||||||
| // parentRoute
 | // parentRoute
 | ||||||
| // ----------------------------------------------------------------------------
 | // ----------------------------------------------------------------------------
 | ||||||
|  | @ -152,13 +196,20 @@ func (r *Router) getRegexpGroup() *routeRegexpGroup { | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (r *Router) buildVars(m map[string]string) map[string]string { | ||||||
|  | 	if r.parent != nil { | ||||||
|  | 		m = r.parent.buildVars(m) | ||||||
|  | 	} | ||||||
|  | 	return m | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // ----------------------------------------------------------------------------
 | // ----------------------------------------------------------------------------
 | ||||||
| // Route factories
 | // Route factories
 | ||||||
| // ----------------------------------------------------------------------------
 | // ----------------------------------------------------------------------------
 | ||||||
| 
 | 
 | ||||||
| // NewRoute registers an empty route.
 | // NewRoute registers an empty route.
 | ||||||
| func (r *Router) NewRoute() *Route { | func (r *Router) NewRoute() *Route { | ||||||
| 	route := &Route{parent: r, strictSlash: r.strictSlash} | 	route := &Route{parent: r, strictSlash: r.strictSlash, skipClean: r.skipClean, useEncodedPath: r.useEncodedPath} | ||||||
| 	r.routes = append(r.routes, route) | 	r.routes = append(r.routes, route) | ||||||
| 	return route | 	return route | ||||||
| } | } | ||||||
|  | @ -224,6 +275,61 @@ func (r *Router) Schemes(schemes ...string) *Route { | ||||||
| 	return r.NewRoute().Schemes(schemes...) | 	return r.NewRoute().Schemes(schemes...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // BuildVarsFunc registers a new route with a custom function for modifying
 | ||||||
|  | // route variables before building a URL.
 | ||||||
|  | func (r *Router) BuildVarsFunc(f BuildVarsFunc) *Route { | ||||||
|  | 	return r.NewRoute().BuildVarsFunc(f) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Walk walks the router and all its sub-routers, calling walkFn for each route
 | ||||||
|  | // in the tree. The routes are walked in the order they were added. Sub-routers
 | ||||||
|  | // are explored depth-first.
 | ||||||
|  | func (r *Router) Walk(walkFn WalkFunc) error { | ||||||
|  | 	return r.walk(walkFn, []*Route{}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // SkipRouter is used as a return value from WalkFuncs to indicate that the
 | ||||||
|  | // router that walk is about to descend down to should be skipped.
 | ||||||
|  | var SkipRouter = errors.New("skip this router") | ||||||
|  | 
 | ||||||
|  | // WalkFunc is the type of the function called for each route visited by Walk.
 | ||||||
|  | // At every invocation, it is given the current route, and the current router,
 | ||||||
|  | // and a list of ancestor routes that lead to the current route.
 | ||||||
|  | type WalkFunc func(route *Route, router *Router, ancestors []*Route) error | ||||||
|  | 
 | ||||||
|  | func (r *Router) walk(walkFn WalkFunc, ancestors []*Route) error { | ||||||
|  | 	for _, t := range r.routes { | ||||||
|  | 		if t.regexp == nil || t.regexp.path == nil || t.regexp.path.template == "" { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		err := walkFn(t, r, ancestors) | ||||||
|  | 		if err == SkipRouter { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		for _, sr := range t.matchers { | ||||||
|  | 			if h, ok := sr.(*Router); ok { | ||||||
|  | 				err := h.walk(walkFn, ancestors) | ||||||
|  | 				if err != nil { | ||||||
|  | 					return err | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		if h, ok := t.handler.(*Router); ok { | ||||||
|  | 			ancestors = append(ancestors, t) | ||||||
|  | 			err := h.walk(walkFn, ancestors) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			ancestors = ancestors[:len(ancestors)-1] | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // ----------------------------------------------------------------------------
 | // ----------------------------------------------------------------------------
 | ||||||
| // Context
 | // Context
 | ||||||
| // ----------------------------------------------------------------------------
 | // ----------------------------------------------------------------------------
 | ||||||
|  | @ -244,32 +350,58 @@ const ( | ||||||
| 
 | 
 | ||||||
| // Vars returns the route variables for the current request, if any.
 | // Vars returns the route variables for the current request, if any.
 | ||||||
| func Vars(r *http.Request) map[string]string { | func Vars(r *http.Request) map[string]string { | ||||||
| 	if rv := context.Get(r, varsKey); rv != nil { | 	if rv := contextGet(r, varsKey); rv != nil { | ||||||
| 		return rv.(map[string]string) | 		return rv.(map[string]string) | ||||||
| 	} | 	} | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // CurrentRoute returns the matched route for the current request, if any.
 | // CurrentRoute returns the matched route for the current request, if any.
 | ||||||
|  | // This only works when called inside the handler of the matched route
 | ||||||
|  | // because the matched route is stored in the request context which is cleared
 | ||||||
|  | // after the handler returns, unless the KeepContext option is set on the
 | ||||||
|  | // Router.
 | ||||||
| func CurrentRoute(r *http.Request) *Route { | func CurrentRoute(r *http.Request) *Route { | ||||||
| 	if rv := context.Get(r, routeKey); rv != nil { | 	if rv := contextGet(r, routeKey); rv != nil { | ||||||
| 		return rv.(*Route) | 		return rv.(*Route) | ||||||
| 	} | 	} | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func setVars(r *http.Request, val interface{}) { | func setVars(r *http.Request, val interface{}) *http.Request { | ||||||
| 	context.Set(r, varsKey, val) | 	return contextSet(r, varsKey, val) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func setCurrentRoute(r *http.Request, val interface{}) { | func setCurrentRoute(r *http.Request, val interface{}) *http.Request { | ||||||
| 	context.Set(r, routeKey, val) | 	return contextSet(r, routeKey, val) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // ----------------------------------------------------------------------------
 | // ----------------------------------------------------------------------------
 | ||||||
| // Helpers
 | // Helpers
 | ||||||
| // ----------------------------------------------------------------------------
 | // ----------------------------------------------------------------------------
 | ||||||
| 
 | 
 | ||||||
|  | // getPath returns the escaped path if possible; doing what URL.EscapedPath()
 | ||||||
|  | // which was added in go1.5 does
 | ||||||
|  | func getPath(req *http.Request) string { | ||||||
|  | 	if req.RequestURI != "" { | ||||||
|  | 		// Extract the path from RequestURI (which is escaped unlike URL.Path)
 | ||||||
|  | 		// as detailed here as detailed in https://golang.org/pkg/net/url/#URL
 | ||||||
|  | 		// for < 1.5 server side workaround
 | ||||||
|  | 		// http://localhost/path/here?v=1 -> /path/here
 | ||||||
|  | 		path := req.RequestURI | ||||||
|  | 		path = strings.TrimPrefix(path, req.URL.Scheme+`://`) | ||||||
|  | 		path = strings.TrimPrefix(path, req.URL.Host) | ||||||
|  | 		if i := strings.LastIndex(path, "?"); i > -1 { | ||||||
|  | 			path = path[:i] | ||||||
|  | 		} | ||||||
|  | 		if i := strings.LastIndex(path, "#"); i > -1 { | ||||||
|  | 			path = path[:i] | ||||||
|  | 		} | ||||||
|  | 		return path | ||||||
|  | 	} | ||||||
|  | 	return req.URL.Path | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // cleanPath returns the canonical path for p, eliminating . and .. elements.
 | // cleanPath returns the canonical path for p, eliminating . and .. elements.
 | ||||||
| // Borrowed from the net/http package.
 | // Borrowed from the net/http package.
 | ||||||
| func cleanPath(p string) string { | func cleanPath(p string) string { | ||||||
|  | @ -285,6 +417,7 @@ func cleanPath(p string) string { | ||||||
| 	if p[len(p)-1] == '/' && np != "/" { | 	if p[len(p)-1] == '/' && np != "/" { | ||||||
| 		np += "/" | 		np += "/" | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
| 	return np | 	return np | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -300,13 +433,24 @@ func uniqueVars(s1, s2 []string) error { | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // mapFromPairs converts variadic string parameters to a string map.
 | // checkPairs returns the count of strings passed in, and an error if
 | ||||||
| func mapFromPairs(pairs ...string) (map[string]string, error) { | // the count is not an even number.
 | ||||||
|  | func checkPairs(pairs ...string) (int, error) { | ||||||
| 	length := len(pairs) | 	length := len(pairs) | ||||||
| 	if length%2 != 0 { | 	if length%2 != 0 { | ||||||
| 		return nil, fmt.Errorf( | 		return length, fmt.Errorf( | ||||||
| 			"mux: number of parameters must be multiple of 2, got %v", pairs) | 			"mux: number of parameters must be multiple of 2, got %v", pairs) | ||||||
| 	} | 	} | ||||||
|  | 	return length, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // mapFromPairsToString converts variadic string parameters to a
 | ||||||
|  | // string to string map.
 | ||||||
|  | func mapFromPairsToString(pairs ...string) (map[string]string, error) { | ||||||
|  | 	length, err := checkPairs(pairs...) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
| 	m := make(map[string]string, length/2) | 	m := make(map[string]string, length/2) | ||||||
| 	for i := 0; i < length; i += 2 { | 	for i := 0; i < length; i += 2 { | ||||||
| 		m[pairs[i]] = pairs[i+1] | 		m[pairs[i]] = pairs[i+1] | ||||||
|  | @ -314,6 +458,24 @@ func mapFromPairs(pairs ...string) (map[string]string, error) { | ||||||
| 	return m, nil | 	return m, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // mapFromPairsToRegex converts variadic string paramers to a
 | ||||||
|  | // string to regex map.
 | ||||||
|  | func mapFromPairsToRegex(pairs ...string) (map[string]*regexp.Regexp, error) { | ||||||
|  | 	length, err := checkPairs(pairs...) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	m := make(map[string]*regexp.Regexp, length/2) | ||||||
|  | 	for i := 0; i < length; i += 2 { | ||||||
|  | 		regex, err := regexp.Compile(pairs[i+1]) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		m[pairs[i]] = regex | ||||||
|  | 	} | ||||||
|  | 	return m, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // matchInArray returns true if the given string value is in the array.
 | // matchInArray returns true if the given string value is in the array.
 | ||||||
| func matchInArray(arr []string, value string) bool { | func matchInArray(arr []string, value string) bool { | ||||||
| 	for _, v := range arr { | 	for _, v := range arr { | ||||||
|  | @ -324,9 +486,8 @@ func matchInArray(arr []string, value string) bool { | ||||||
| 	return false | 	return false | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // matchMap returns true if the given key/value pairs exist in a given map.
 | // matchMapWithString returns true if the given key/value pairs exist in a given map.
 | ||||||
| func matchMap(toCheck map[string]string, toMatch map[string][]string, | func matchMapWithString(toCheck map[string]string, toMatch map[string][]string, canonicalKey bool) bool { | ||||||
| 	canonicalKey bool) bool { |  | ||||||
| 	for k, v := range toCheck { | 	for k, v := range toCheck { | ||||||
| 		// Check if key exists.
 | 		// Check if key exists.
 | ||||||
| 		if canonicalKey { | 		if canonicalKey { | ||||||
|  | @ -351,3 +512,31 @@ func matchMap(toCheck map[string]string, toMatch map[string][]string, | ||||||
| 	} | 	} | ||||||
| 	return true | 	return true | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | // matchMapWithRegex returns true if the given key/value pairs exist in a given map compiled against
 | ||||||
|  | // the given regex
 | ||||||
|  | func matchMapWithRegex(toCheck map[string]*regexp.Regexp, toMatch map[string][]string, canonicalKey bool) bool { | ||||||
|  | 	for k, v := range toCheck { | ||||||
|  | 		// Check if key exists.
 | ||||||
|  | 		if canonicalKey { | ||||||
|  | 			k = http.CanonicalHeaderKey(k) | ||||||
|  | 		} | ||||||
|  | 		if values := toMatch[k]; values == nil { | ||||||
|  | 			return false | ||||||
|  | 		} else if v != nil { | ||||||
|  | 			// If value was defined as an empty string we only check that the
 | ||||||
|  | 			// key exists. Otherwise we also check for equality.
 | ||||||
|  | 			valueExists := false | ||||||
|  | 			for _, value := range values { | ||||||
|  | 				if v.MatchString(value) { | ||||||
|  | 					valueExists = true | ||||||
|  | 					break | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			if !valueExists { | ||||||
|  | 				return false | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return true | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -10,6 +10,7 @@ import ( | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 	"regexp" | 	"regexp" | ||||||
|  | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -23,7 +24,7 @@ import ( | ||||||
| // Previously we accepted only Python-like identifiers for variable
 | // Previously we accepted only Python-like identifiers for variable
 | ||||||
| // names ([a-zA-Z_][a-zA-Z0-9_]*), but currently the only restriction is that
 | // 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.
 | // name and pattern can't be empty, and names can't contain a colon.
 | ||||||
| func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash bool) (*routeRegexp, error) { | func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash, useEncodedPath bool) (*routeRegexp, error) { | ||||||
| 	// Check if it is well-formed.
 | 	// Check if it is well-formed.
 | ||||||
| 	idxs, errBraces := braceIndices(tpl) | 	idxs, errBraces := braceIndices(tpl) | ||||||
| 	if errBraces != nil { | 	if errBraces != nil { | ||||||
|  | @ -34,8 +35,7 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash | ||||||
| 	// Now let's parse it.
 | 	// Now let's parse it.
 | ||||||
| 	defaultPattern := "[^/]+" | 	defaultPattern := "[^/]+" | ||||||
| 	if matchQuery { | 	if matchQuery { | ||||||
| 		defaultPattern = "[^?&]+" | 		defaultPattern = "[^?&]*" | ||||||
| 		matchPrefix = true |  | ||||||
| 	} else if matchHost { | 	} else if matchHost { | ||||||
| 		defaultPattern = "[^.]+" | 		defaultPattern = "[^.]+" | ||||||
| 		matchPrefix = false | 		matchPrefix = false | ||||||
|  | @ -53,9 +53,7 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash | ||||||
| 	varsN := make([]string, len(idxs)/2) | 	varsN := make([]string, len(idxs)/2) | ||||||
| 	varsR := make([]*regexp.Regexp, len(idxs)/2) | 	varsR := make([]*regexp.Regexp, len(idxs)/2) | ||||||
| 	pattern := bytes.NewBufferString("") | 	pattern := bytes.NewBufferString("") | ||||||
| 	if !matchQuery { | 	pattern.WriteByte('^') | ||||||
| 		pattern.WriteByte('^') |  | ||||||
| 	} |  | ||||||
| 	reverse := bytes.NewBufferString("") | 	reverse := bytes.NewBufferString("") | ||||||
| 	var end int | 	var end int | ||||||
| 	var err error | 	var err error | ||||||
|  | @ -75,9 +73,11 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash | ||||||
| 				tpl[idxs[i]:end]) | 				tpl[idxs[i]:end]) | ||||||
| 		} | 		} | ||||||
| 		// Build the regexp pattern.
 | 		// Build the regexp pattern.
 | ||||||
| 		fmt.Fprintf(pattern, "%s(%s)", regexp.QuoteMeta(raw), patt) | 		fmt.Fprintf(pattern, "%s(?P<%s>%s)", regexp.QuoteMeta(raw), varGroupName(i/2), patt) | ||||||
|  | 
 | ||||||
| 		// Build the reverse template.
 | 		// Build the reverse template.
 | ||||||
| 		fmt.Fprintf(reverse, "%s%%s", raw) | 		fmt.Fprintf(reverse, "%s%%s", raw) | ||||||
|  | 
 | ||||||
| 		// Append variable name and compiled pattern.
 | 		// Append variable name and compiled pattern.
 | ||||||
| 		varsN[i/2] = name | 		varsN[i/2] = name | ||||||
| 		varsR[i/2], err = regexp.Compile(fmt.Sprintf("^%s$", patt)) | 		varsR[i/2], err = regexp.Compile(fmt.Sprintf("^%s$", patt)) | ||||||
|  | @ -91,6 +91,12 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash | ||||||
| 	if strictSlash { | 	if strictSlash { | ||||||
| 		pattern.WriteString("[/]?") | 		pattern.WriteString("[/]?") | ||||||
| 	} | 	} | ||||||
|  | 	if matchQuery { | ||||||
|  | 		// Add the default pattern if the query value is empty
 | ||||||
|  | 		if queryVal := strings.SplitN(template, "=", 2)[1]; queryVal == "" { | ||||||
|  | 			pattern.WriteString(defaultPattern) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
| 	if !matchPrefix { | 	if !matchPrefix { | ||||||
| 		pattern.WriteByte('$') | 		pattern.WriteByte('$') | ||||||
| 	} | 	} | ||||||
|  | @ -103,16 +109,24 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash | ||||||
| 	if errCompile != nil { | 	if errCompile != nil { | ||||||
| 		return nil, errCompile | 		return nil, errCompile | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	// Check for capturing groups which used to work in older versions
 | ||||||
|  | 	if reg.NumSubexp() != len(idxs)/2 { | ||||||
|  | 		panic(fmt.Sprintf("route %s contains capture groups in its regexp. ", template) + | ||||||
|  | 			"Only non-capturing groups are accepted: e.g. (?:pattern) instead of (pattern)") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	// Done!
 | 	// Done!
 | ||||||
| 	return &routeRegexp{ | 	return &routeRegexp{ | ||||||
| 		template:    template, | 		template:       template, | ||||||
| 		matchHost:   matchHost, | 		matchHost:      matchHost, | ||||||
| 		matchQuery:  matchQuery, | 		matchQuery:     matchQuery, | ||||||
| 		strictSlash: strictSlash, | 		strictSlash:    strictSlash, | ||||||
| 		regexp:      reg, | 		useEncodedPath: useEncodedPath, | ||||||
| 		reverse:     reverse.String(), | 		regexp:         reg, | ||||||
| 		varsN:       varsN, | 		reverse:        reverse.String(), | ||||||
| 		varsR:       varsR, | 		varsN:          varsN, | ||||||
|  | 		varsR:          varsR, | ||||||
| 	}, nil | 	}, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -127,6 +141,9 @@ type routeRegexp struct { | ||||||
| 	matchQuery bool | 	matchQuery bool | ||||||
| 	// The strictSlash value defined on the route, but disabled if PathPrefix was used.
 | 	// The strictSlash value defined on the route, but disabled if PathPrefix was used.
 | ||||||
| 	strictSlash bool | 	strictSlash bool | ||||||
|  | 	// Determines whether to use encoded path from getPath function or unencoded
 | ||||||
|  | 	// req.URL.Path for path matching
 | ||||||
|  | 	useEncodedPath bool | ||||||
| 	// Expanded regexp.
 | 	// Expanded regexp.
 | ||||||
| 	regexp *regexp.Regexp | 	regexp *regexp.Regexp | ||||||
| 	// Reverse template.
 | 	// Reverse template.
 | ||||||
|  | @ -141,20 +158,20 @@ type routeRegexp struct { | ||||||
| func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool { | func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool { | ||||||
| 	if !r.matchHost { | 	if !r.matchHost { | ||||||
| 		if r.matchQuery { | 		if r.matchQuery { | ||||||
| 			return r.regexp.MatchString(req.URL.RawQuery) | 			return r.matchQueryString(req) | ||||||
| 		} else { |  | ||||||
| 			return r.regexp.MatchString(req.URL.Path) |  | ||||||
| 		} | 		} | ||||||
|  | 		path := req.URL.Path | ||||||
|  | 		if r.useEncodedPath { | ||||||
|  | 			path = getPath(req) | ||||||
|  | 		} | ||||||
|  | 		return r.regexp.MatchString(path) | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
| 	return r.regexp.MatchString(getHost(req)) | 	return r.regexp.MatchString(getHost(req)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // url builds a URL part using the given values.
 | // url builds a URL part using the given values.
 | ||||||
| func (r *routeRegexp) url(pairs ...string) (string, error) { | func (r *routeRegexp) url(values map[string]string) (string, error) { | ||||||
| 	values, err := mapFromPairs(pairs...) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return "", err |  | ||||||
| 	} |  | ||||||
| 	urlValues := make([]interface{}, len(r.varsN)) | 	urlValues := make([]interface{}, len(r.varsN)) | ||||||
| 	for k, v := range r.varsN { | 	for k, v := range r.varsN { | ||||||
| 		value, ok := values[v] | 		value, ok := values[v] | ||||||
|  | @ -179,11 +196,31 @@ func (r *routeRegexp) url(pairs ...string) (string, error) { | ||||||
| 	return rv, nil | 	return rv, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // getURLQuery returns a single query parameter from a request URL.
 | ||||||
|  | // For a URL with foo=bar&baz=ding, we return only the relevant key
 | ||||||
|  | // value pair for the routeRegexp.
 | ||||||
|  | func (r *routeRegexp) getURLQuery(req *http.Request) string { | ||||||
|  | 	if !r.matchQuery { | ||||||
|  | 		return "" | ||||||
|  | 	} | ||||||
|  | 	templateKey := strings.SplitN(r.template, "=", 2)[0] | ||||||
|  | 	for key, vals := range req.URL.Query() { | ||||||
|  | 		if key == templateKey && len(vals) > 0 { | ||||||
|  | 			return key + "=" + vals[0] | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return "" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *routeRegexp) matchQueryString(req *http.Request) bool { | ||||||
|  | 	return r.regexp.MatchString(r.getURLQuery(req)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // braceIndices returns the first level curly brace indices from a string.
 | // braceIndices returns the first level curly brace indices from a string.
 | ||||||
| // It returns an error in case of unbalanced braces.
 | // It returns an error in case of unbalanced braces.
 | ||||||
| func braceIndices(s string) ([]int, error) { | func braceIndices(s string) ([]int, error) { | ||||||
| 	var level, idx int | 	var level, idx int | ||||||
| 	idxs := make([]int, 0) | 	var idxs []int | ||||||
| 	for i := 0; i < len(s); i++ { | 	for i := 0; i < len(s); i++ { | ||||||
| 		switch s[i] { | 		switch s[i] { | ||||||
| 		case '{': | 		case '{': | ||||||
|  | @ -204,6 +241,11 @@ func braceIndices(s string) ([]int, error) { | ||||||
| 	return idxs, nil | 	return idxs, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // varGroupName builds a capturing group name for the indexed variable.
 | ||||||
|  | func varGroupName(idx int) string { | ||||||
|  | 	return "v" + strconv.Itoa(idx) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // ----------------------------------------------------------------------------
 | // ----------------------------------------------------------------------------
 | ||||||
| // routeRegexpGroup
 | // routeRegexpGroup
 | ||||||
| // ----------------------------------------------------------------------------
 | // ----------------------------------------------------------------------------
 | ||||||
|  | @ -219,23 +261,24 @@ type routeRegexpGroup struct { | ||||||
| func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route) { | func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route) { | ||||||
| 	// Store host variables.
 | 	// Store host variables.
 | ||||||
| 	if v.host != nil { | 	if v.host != nil { | ||||||
| 		hostVars := v.host.regexp.FindStringSubmatch(getHost(req)) | 		host := getHost(req) | ||||||
| 		if hostVars != nil { | 		matches := v.host.regexp.FindStringSubmatchIndex(host) | ||||||
| 			for k, v := range v.host.varsN { | 		if len(matches) > 0 { | ||||||
| 				m.Vars[v] = hostVars[k+1] | 			extractVars(host, matches, v.host.varsN, m.Vars) | ||||||
| 			} |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | 	path := req.URL.Path | ||||||
|  | 	if r.useEncodedPath { | ||||||
|  | 		path = getPath(req) | ||||||
|  | 	} | ||||||
| 	// Store path variables.
 | 	// Store path variables.
 | ||||||
| 	if v.path != nil { | 	if v.path != nil { | ||||||
| 		pathVars := v.path.regexp.FindStringSubmatch(req.URL.Path) | 		matches := v.path.regexp.FindStringSubmatchIndex(path) | ||||||
| 		if pathVars != nil { | 		if len(matches) > 0 { | ||||||
| 			for k, v := range v.path.varsN { | 			extractVars(path, matches, v.path.varsN, m.Vars) | ||||||
| 				m.Vars[v] = pathVars[k+1] |  | ||||||
| 			} |  | ||||||
| 			// Check if we should redirect.
 | 			// Check if we should redirect.
 | ||||||
| 			if v.path.strictSlash { | 			if v.path.strictSlash { | ||||||
| 				p1 := strings.HasSuffix(req.URL.Path, "/") | 				p1 := strings.HasSuffix(path, "/") | ||||||
| 				p2 := strings.HasSuffix(v.path.template, "/") | 				p2 := strings.HasSuffix(v.path.template, "/") | ||||||
| 				if p1 != p2 { | 				if p1 != p2 { | ||||||
| 					u, _ := url.Parse(req.URL.String()) | 					u, _ := url.Parse(req.URL.String()) | ||||||
|  | @ -250,13 +293,11 @@ func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	// Store query string variables.
 | 	// Store query string variables.
 | ||||||
| 	rawQuery := req.URL.RawQuery |  | ||||||
| 	for _, q := range v.queries { | 	for _, q := range v.queries { | ||||||
| 		queryVars := q.regexp.FindStringSubmatch(rawQuery) | 		queryURL := q.getURLQuery(req) | ||||||
| 		if queryVars != nil { | 		matches := q.regexp.FindStringSubmatchIndex(queryURL) | ||||||
| 			for k, v := range q.varsN { | 		if len(matches) > 0 { | ||||||
| 				m.Vars[v] = queryVars[k+1] | 			extractVars(queryURL, matches, q.varsN, m.Vars) | ||||||
| 			} |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | @ -274,3 +315,9 @@ func getHost(r *http.Request) string { | ||||||
| 	return host | 	return host | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func extractVars(input string, matches []int, names []string, output map[string]string) { | ||||||
|  | 	for i, name := range names { | ||||||
|  | 		output[name] = input[matches[2*i+2]:matches[2*i+3]] | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -9,6 +9,7 @@ import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"net/url" | 	"net/url" | ||||||
|  | 	"regexp" | ||||||
| 	"strings" | 	"strings" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -25,12 +26,23 @@ type Route struct { | ||||||
| 	// If true, when the path pattern is "/path/", accessing "/path" will
 | 	// If true, when the path pattern is "/path/", accessing "/path" will
 | ||||||
| 	// redirect to the former and vice versa.
 | 	// redirect to the former and vice versa.
 | ||||||
| 	strictSlash bool | 	strictSlash bool | ||||||
|  | 	// If true, when the path pattern is "/path//to", accessing "/path//to"
 | ||||||
|  | 	// will not redirect
 | ||||||
|  | 	skipClean bool | ||||||
|  | 	// If true, "/path/foo%2Fbar/to" will match the path "/path/{var}/to"
 | ||||||
|  | 	useEncodedPath bool | ||||||
| 	// If true, this route never matches: it is only used to build URLs.
 | 	// If true, this route never matches: it is only used to build URLs.
 | ||||||
| 	buildOnly bool | 	buildOnly bool | ||||||
| 	// The name used to build URLs.
 | 	// The name used to build URLs.
 | ||||||
| 	name string | 	name string | ||||||
| 	// Error resulted from building a route.
 | 	// Error resulted from building a route.
 | ||||||
| 	err error | 	err error | ||||||
|  | 
 | ||||||
|  | 	buildVarsFunc BuildVarsFunc | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *Route) SkipClean() bool { | ||||||
|  | 	return r.skipClean | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Match matches the route against the request.
 | // Match matches the route against the request.
 | ||||||
|  | @ -141,14 +153,14 @@ func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix, matchQuery | ||||||
| 	} | 	} | ||||||
| 	r.regexp = r.getRegexpGroup() | 	r.regexp = r.getRegexpGroup() | ||||||
| 	if !matchHost && !matchQuery { | 	if !matchHost && !matchQuery { | ||||||
| 		if len(tpl) == 0 || tpl[0] != '/' { | 		if len(tpl) > 0 && tpl[0] != '/' { | ||||||
| 			return fmt.Errorf("mux: path must start with a slash, got %q", tpl) | 			return fmt.Errorf("mux: path must start with a slash, got %q", tpl) | ||||||
| 		} | 		} | ||||||
| 		if r.regexp.path != nil { | 		if r.regexp.path != nil { | ||||||
| 			tpl = strings.TrimRight(r.regexp.path.template, "/") + tpl | 			tpl = strings.TrimRight(r.regexp.path.template, "/") + tpl | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	rr, err := newRouteRegexp(tpl, matchHost, matchPrefix, matchQuery, r.strictSlash) | 	rr, err := newRouteRegexp(tpl, matchHost, matchPrefix, matchQuery, r.strictSlash, r.useEncodedPath) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  | @ -186,7 +198,7 @@ func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix, matchQuery | ||||||
| type headerMatcher map[string]string | type headerMatcher map[string]string | ||||||
| 
 | 
 | ||||||
| func (m headerMatcher) Match(r *http.Request, match *RouteMatch) bool { | func (m headerMatcher) Match(r *http.Request, match *RouteMatch) bool { | ||||||
| 	return matchMap(m, r.Header, true) | 	return matchMapWithString(m, r.Header, true) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Headers adds a matcher for request header values.
 | // Headers adds a matcher for request header values.
 | ||||||
|  | @ -197,22 +209,46 @@ func (m headerMatcher) Match(r *http.Request, match *RouteMatch) bool { | ||||||
| //               "X-Requested-With", "XMLHttpRequest")
 | //               "X-Requested-With", "XMLHttpRequest")
 | ||||||
| //
 | //
 | ||||||
| // The above route will only match if both request header values match.
 | // The above route will only match if both request header values match.
 | ||||||
| //
 | // If the value is an empty string, it will match any value if the key is set.
 | ||||||
| // It the value is an empty string, it will match any value if the key is set.
 |  | ||||||
| func (r *Route) Headers(pairs ...string) *Route { | func (r *Route) Headers(pairs ...string) *Route { | ||||||
| 	if r.err == nil { | 	if r.err == nil { | ||||||
| 		var headers map[string]string | 		var headers map[string]string | ||||||
| 		headers, r.err = mapFromPairs(pairs...) | 		headers, r.err = mapFromPairsToString(pairs...) | ||||||
| 		return r.addMatcher(headerMatcher(headers)) | 		return r.addMatcher(headerMatcher(headers)) | ||||||
| 	} | 	} | ||||||
| 	return r | 	return r | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // headerRegexMatcher matches the request against the route given a regex for the header
 | ||||||
|  | type headerRegexMatcher map[string]*regexp.Regexp | ||||||
|  | 
 | ||||||
|  | func (m headerRegexMatcher) Match(r *http.Request, match *RouteMatch) bool { | ||||||
|  | 	return matchMapWithRegex(m, r.Header, true) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // HeadersRegexp accepts a sequence of key/value pairs, where the value has regex
 | ||||||
|  | // support. For example:
 | ||||||
|  | //
 | ||||||
|  | //     r := mux.NewRouter()
 | ||||||
|  | //     r.HeadersRegexp("Content-Type", "application/(text|json)",
 | ||||||
|  | //               "X-Requested-With", "XMLHttpRequest")
 | ||||||
|  | //
 | ||||||
|  | // The above route will only match if both the request header matches both regular expressions.
 | ||||||
|  | // It the value is an empty string, it will match any value if the key is set.
 | ||||||
|  | func (r *Route) HeadersRegexp(pairs ...string) *Route { | ||||||
|  | 	if r.err == nil { | ||||||
|  | 		var headers map[string]*regexp.Regexp | ||||||
|  | 		headers, r.err = mapFromPairsToRegex(pairs...) | ||||||
|  | 		return r.addMatcher(headerRegexMatcher(headers)) | ||||||
|  | 	} | ||||||
|  | 	return r | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // Host -----------------------------------------------------------------------
 | // Host -----------------------------------------------------------------------
 | ||||||
| 
 | 
 | ||||||
| // Host adds a matcher for the URL host.
 | // Host adds a matcher for the URL host.
 | ||||||
| // It accepts a template with zero or more URL variables enclosed by {}.
 | // It accepts a template with zero or more URL variables enclosed by {}.
 | ||||||
| // Variables can define an optional regexp pattern to me matched:
 | // Variables can define an optional regexp pattern to be matched:
 | ||||||
| //
 | //
 | ||||||
| // - {name} matches anything until the next dot.
 | // - {name} matches anything until the next dot.
 | ||||||
| //
 | //
 | ||||||
|  | @ -221,7 +257,7 @@ func (r *Route) Headers(pairs ...string) *Route { | ||||||
| // For example:
 | // For example:
 | ||||||
| //
 | //
 | ||||||
| //     r := mux.NewRouter()
 | //     r := mux.NewRouter()
 | ||||||
| //     r.Host("www.domain.com")
 | //     r.Host("www.example.com")
 | ||||||
| //     r.Host("{subdomain}.domain.com")
 | //     r.Host("{subdomain}.domain.com")
 | ||||||
| //     r.Host("{subdomain:[a-z]+}.domain.com")
 | //     r.Host("{subdomain:[a-z]+}.domain.com")
 | ||||||
| //
 | //
 | ||||||
|  | @ -237,6 +273,7 @@ func (r *Route) Host(tpl string) *Route { | ||||||
| // MatcherFunc is the function signature used by custom matchers.
 | // MatcherFunc is the function signature used by custom matchers.
 | ||||||
| type MatcherFunc func(*http.Request, *RouteMatch) bool | type MatcherFunc func(*http.Request, *RouteMatch) bool | ||||||
| 
 | 
 | ||||||
|  | // Match returns the match for a given request.
 | ||||||
| func (m MatcherFunc) Match(r *http.Request, match *RouteMatch) bool { | func (m MatcherFunc) Match(r *http.Request, match *RouteMatch) bool { | ||||||
| 	return m(r, match) | 	return m(r, match) | ||||||
| } | } | ||||||
|  | @ -270,7 +307,7 @@ func (r *Route) Methods(methods ...string) *Route { | ||||||
| // Path adds a matcher for the URL path.
 | // Path adds a matcher for the URL path.
 | ||||||
| // It accepts a template with zero or more URL variables enclosed by {}. The
 | // It accepts a template with zero or more URL variables enclosed by {}. The
 | ||||||
| // template must start with a "/".
 | // template must start with a "/".
 | ||||||
| // Variables can define an optional regexp pattern to me matched:
 | // Variables can define an optional regexp pattern to be matched:
 | ||||||
| //
 | //
 | ||||||
| // - {name} matches anything until the next slash.
 | // - {name} matches anything until the next slash.
 | ||||||
| //
 | //
 | ||||||
|  | @ -321,7 +358,7 @@ func (r *Route) PathPrefix(tpl string) *Route { | ||||||
| //
 | //
 | ||||||
| // It the value is an empty string, it will match any value if the key is set.
 | // It the value is an empty string, it will match any value if the key is set.
 | ||||||
| //
 | //
 | ||||||
| // Variables can define an optional regexp pattern to me matched:
 | // Variables can define an optional regexp pattern to be matched:
 | ||||||
| //
 | //
 | ||||||
| // - {name} matches anything until the next slash.
 | // - {name} matches anything until the next slash.
 | ||||||
| //
 | //
 | ||||||
|  | @ -334,7 +371,7 @@ func (r *Route) Queries(pairs ...string) *Route { | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| 	for i := 0; i < length; i += 2 { | 	for i := 0; i < length; i += 2 { | ||||||
| 		if r.err = r.addRegexpMatcher(pairs[i]+"="+pairs[i+1], false, true, true); r.err != nil { | 		if r.err = r.addRegexpMatcher(pairs[i]+"="+pairs[i+1], false, false, true); r.err != nil { | ||||||
| 			return r | 			return r | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | @ -360,6 +397,19 @@ func (r *Route) Schemes(schemes ...string) *Route { | ||||||
| 	return r.addMatcher(schemeMatcher(schemes)) | 	return r.addMatcher(schemeMatcher(schemes)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // BuildVarsFunc --------------------------------------------------------------
 | ||||||
|  | 
 | ||||||
|  | // BuildVarsFunc is the function signature used by custom build variable
 | ||||||
|  | // functions (which can modify route variables before a route's URL is built).
 | ||||||
|  | type BuildVarsFunc func(map[string]string) map[string]string | ||||||
|  | 
 | ||||||
|  | // BuildVarsFunc adds a custom function to be used to modify build variables
 | ||||||
|  | // before a route's URL is built.
 | ||||||
|  | func (r *Route) BuildVarsFunc(f BuildVarsFunc) *Route { | ||||||
|  | 	r.buildVarsFunc = f | ||||||
|  | 	return r | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // Subrouter ------------------------------------------------------------------
 | // Subrouter ------------------------------------------------------------------
 | ||||||
| 
 | 
 | ||||||
| // Subrouter creates a subrouter for the route.
 | // Subrouter creates a subrouter for the route.
 | ||||||
|  | @ -367,7 +417,7 @@ func (r *Route) Schemes(schemes ...string) *Route { | ||||||
| // It will test the inner routes only if the parent route matched. For example:
 | // It will test the inner routes only if the parent route matched. For example:
 | ||||||
| //
 | //
 | ||||||
| //     r := mux.NewRouter()
 | //     r := mux.NewRouter()
 | ||||||
| //     s := r.Host("www.domain.com").Subrouter()
 | //     s := r.Host("www.example.com").Subrouter()
 | ||||||
| //     s.HandleFunc("/products/", ProductsHandler)
 | //     s.HandleFunc("/products/", ProductsHandler)
 | ||||||
| //     s.HandleFunc("/products/{key}", ProductHandler)
 | //     s.HandleFunc("/products/{key}", ProductHandler)
 | ||||||
| //     s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler)
 | //     s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler)
 | ||||||
|  | @ -422,17 +472,20 @@ func (r *Route) URL(pairs ...string) (*url.URL, error) { | ||||||
| 	if r.regexp == nil { | 	if r.regexp == nil { | ||||||
| 		return nil, errors.New("mux: route doesn't have a host or path") | 		return nil, errors.New("mux: route doesn't have a host or path") | ||||||
| 	} | 	} | ||||||
|  | 	values, err := r.prepareVars(pairs...) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
| 	var scheme, host, path string | 	var scheme, host, path string | ||||||
| 	var err error |  | ||||||
| 	if r.regexp.host != nil { | 	if r.regexp.host != nil { | ||||||
| 		// Set a default scheme.
 | 		// Set a default scheme.
 | ||||||
| 		scheme = "http" | 		scheme = "http" | ||||||
| 		if host, err = r.regexp.host.url(pairs...); err != nil { | 		if host, err = r.regexp.host.url(values); err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	if r.regexp.path != nil { | 	if r.regexp.path != nil { | ||||||
| 		if path, err = r.regexp.path.url(pairs...); err != nil { | 		if path, err = r.regexp.path.url(values); err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | @ -453,7 +506,11 @@ func (r *Route) URLHost(pairs ...string) (*url.URL, error) { | ||||||
| 	if r.regexp == nil || r.regexp.host == nil { | 	if r.regexp == nil || r.regexp.host == nil { | ||||||
| 		return nil, errors.New("mux: route doesn't have a host") | 		return nil, errors.New("mux: route doesn't have a host") | ||||||
| 	} | 	} | ||||||
| 	host, err := r.regexp.host.url(pairs...) | 	values, err := r.prepareVars(pairs...) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	host, err := r.regexp.host.url(values) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  | @ -473,7 +530,11 @@ func (r *Route) URLPath(pairs ...string) (*url.URL, error) { | ||||||
| 	if r.regexp == nil || r.regexp.path == nil { | 	if r.regexp == nil || r.regexp.path == nil { | ||||||
| 		return nil, errors.New("mux: route doesn't have a path") | 		return nil, errors.New("mux: route doesn't have a path") | ||||||
| 	} | 	} | ||||||
| 	path, err := r.regexp.path.url(pairs...) | 	values, err := r.prepareVars(pairs...) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	path, err := r.regexp.path.url(values) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  | @ -482,6 +543,56 @@ func (r *Route) URLPath(pairs ...string) (*url.URL, error) { | ||||||
| 	}, nil | 	}, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // GetPathTemplate returns the template used to build the
 | ||||||
|  | // route match.
 | ||||||
|  | // This is useful for building simple REST API documentation and for instrumentation
 | ||||||
|  | // against third-party services.
 | ||||||
|  | // An error will be returned if the route does not define a path.
 | ||||||
|  | func (r *Route) GetPathTemplate() (string, error) { | ||||||
|  | 	if r.err != nil { | ||||||
|  | 		return "", r.err | ||||||
|  | 	} | ||||||
|  | 	if r.regexp == nil || r.regexp.path == nil { | ||||||
|  | 		return "", errors.New("mux: route doesn't have a path") | ||||||
|  | 	} | ||||||
|  | 	return r.regexp.path.template, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetHostTemplate returns the template used to build the
 | ||||||
|  | // route match.
 | ||||||
|  | // This is useful for building simple REST API documentation and for instrumentation
 | ||||||
|  | // against third-party services.
 | ||||||
|  | // An error will be returned if the route does not define a host.
 | ||||||
|  | func (r *Route) GetHostTemplate() (string, error) { | ||||||
|  | 	if r.err != nil { | ||||||
|  | 		return "", r.err | ||||||
|  | 	} | ||||||
|  | 	if r.regexp == nil || r.regexp.host == nil { | ||||||
|  | 		return "", errors.New("mux: route doesn't have a host") | ||||||
|  | 	} | ||||||
|  | 	return r.regexp.host.template, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // prepareVars converts the route variable pairs into a map. If the route has a
 | ||||||
|  | // BuildVarsFunc, it is invoked.
 | ||||||
|  | func (r *Route) prepareVars(pairs ...string) (map[string]string, error) { | ||||||
|  | 	m, err := mapFromPairsToString(pairs...) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return r.buildVars(m), nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *Route) buildVars(m map[string]string) map[string]string { | ||||||
|  | 	if r.parent != nil { | ||||||
|  | 		m = r.parent.buildVars(m) | ||||||
|  | 	} | ||||||
|  | 	if r.buildVarsFunc != nil { | ||||||
|  | 		m = r.buildVarsFunc(m) | ||||||
|  | 	} | ||||||
|  | 	return m | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // ----------------------------------------------------------------------------
 | // ----------------------------------------------------------------------------
 | ||||||
| // parentRoute
 | // parentRoute
 | ||||||
| // ----------------------------------------------------------------------------
 | // ----------------------------------------------------------------------------
 | ||||||
|  | @ -490,6 +601,7 @@ func (r *Route) URLPath(pairs ...string) (*url.URL, error) { | ||||||
| type parentRoute interface { | type parentRoute interface { | ||||||
| 	getNamedRoutes() map[string]*Route | 	getNamedRoutes() map[string]*Route | ||||||
| 	getRegexpGroup() *routeRegexpGroup | 	getRegexpGroup() *routeRegexpGroup | ||||||
|  | 	buildVars(map[string]string) map[string]string | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // getNamedRoutes returns the map where named routes are registered.
 | // getNamedRoutes returns the map where named routes are registered.
 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue