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/gorilla/context 14f550f51af52180c2eefed15e5fd18d63c0a64a | ||||
| github.com/gorilla/handlers 60c7bfde3e33c201519a200a4507a158cc03a17b | ||||
| github.com/gorilla/mux e444e69cbd2e2e3e0749a2f3c717cec491552bbf | ||||
| github.com/gorilla/mux 599cba5e7b6137d46ddf58fb1765f5d928e69604 | ||||
| github.com/inconshreveable/mousetrap 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 | ||||
| github.com/jmespath/go-jmespath bd40a432e4c76585ef6b72d3fd96fb9b6dc7b68d | ||||
| 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.
 | ||||
| 
 | ||||
| /* | ||||
| 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 | ||||
| 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}/{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 | ||||
| calling mux.Vars(): | ||||
| 
 | ||||
| 	vars := mux.Vars(request) | ||||
| 	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 | ||||
| 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: | ||||
| 
 | ||||
| 	r := mux.NewRouter() | ||||
| 	// Only matches if domain is "www.domain.com".
 | ||||
| 	r.Host("www.domain.com") | ||||
| 	// Only matches if domain is "www.example.com".
 | ||||
| 	r.Host("www.example.com") | ||||
| 	// Matches a dynamic subdomain.
 | ||||
| 	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 { | ||||
| 		return r.ProtoMajor == 0 | ||||
|     }) | ||||
| 	}) | ||||
| 
 | ||||
| ...and finally, it is possible to combine several matchers in a single route: | ||||
| 
 | ||||
| 	r.HandleFunc("/products", ProductsHandler). | ||||
| 	  Host("www.domain.com"). | ||||
| 	  Host("www.example.com"). | ||||
| 	  Methods("GET"). | ||||
| 	  Schemes("http") | ||||
| 
 | ||||
|  | @ -103,11 +112,11 @@ 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" | ||||
| host is "www.example.com". Create a route for that host and get a "subrouter" | ||||
| from it: | ||||
| 
 | ||||
| 	r := mux.NewRouter() | ||||
| 	s := r.Host("www.domain.com").Subrouter() | ||||
| 	s := r.Host("www.example.com").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) | ||||
| 
 | ||||
| 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 | ||||
| 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"
 | ||||
| 	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. | ||||
| 
 | ||||
| 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, err := r.Get("article").URL("subdomain", "news", | ||||
| 									 "category", "technology", | ||||
| 									 "id", "42") | ||||
| 	                                 "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: | ||||
| 
 | ||||
| 	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: | ||||
|  | @ -193,7 +234,7 @@ as well: | |||
| 
 | ||||
| 	// "http://news.domain.com/articles/technology/42"
 | ||||
| 	url, err := r.Get("article").URL("subdomain", "news", | ||||
| 									 "category", "technology", | ||||
| 									 "id", "42") | ||||
| 	                                 "category", "technology", | ||||
| 	                                 "id", "42") | ||||
| */ | ||||
| package mux | ||||
|  |  | |||
|  | @ -5,11 +5,12 @@ | |||
| package mux | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 	"path" | ||||
| 
 | ||||
| 	"github.com/gorilla/context" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
| ) | ||||
| 
 | ||||
| // NewRouter returns a new router instance.
 | ||||
|  | @ -46,8 +47,14 @@ type Router struct { | |||
| 	namedRoutes map[string]*Route | ||||
| 	// See Router.StrictSlash(). This defines the flag for new routes.
 | ||||
| 	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 | ||||
| 	// see Router.UseEncodedPath(). This defines a flag for all routes.
 | ||||
| 	useEncodedPath bool | ||||
| } | ||||
| 
 | ||||
| // Match matches registered routes against the request.
 | ||||
|  | @ -57,6 +64,12 @@ func (r *Router) Match(req *http.Request, match *RouteMatch) bool { | |||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// Closest match for a router (includes sub-routers)
 | ||||
| 	if r.NotFoundHandler != nil { | ||||
| 		match.Handler = r.NotFoundHandler | ||||
| 		return true | ||||
| 	} | ||||
| 	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
 | ||||
| // 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 { | ||||
| 	if !r.skipClean { | ||||
| 		path := req.URL.Path | ||||
| 		if r.useEncodedPath { | ||||
| 			path = getPath(req) | ||||
| 		} | ||||
| 		// Clean path to canonical form and redirect.
 | ||||
| 		if p := cleanPath(path); p != path { | ||||
| 
 | ||||
| 		// Added 3 lines (Philip Schlump) - It was droping the query string and #whatever from query.
 | ||||
| 		// This matches with fix in go 1.2 r.c. 4 for same problem.  Go Issue:
 | ||||
| 		// http://code.google.com/p/go/issues/detail?id=5252
 | ||||
| 		url := *req.URL | ||||
| 		url.Path = p | ||||
| 		p = url.String() | ||||
| 			// Added 3 lines (Philip Schlump) - It was dropping the query string and #whatever from query.
 | ||||
| 			// This matches with fix in go 1.2 r.c. 4 for same problem.  Go Issue:
 | ||||
| 			// http://code.google.com/p/go/issues/detail?id=5252
 | ||||
| 			url := *req.URL | ||||
| 			url.Path = p | ||||
| 			p = url.String() | ||||
| 
 | ||||
| 		w.Header().Set("Location", p) | ||||
| 		w.WriteHeader(http.StatusMovedPermanently) | ||||
| 		return | ||||
| 			w.Header().Set("Location", p) | ||||
| 			w.WriteHeader(http.StatusMovedPermanently) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	var match RouteMatch | ||||
| 	var handler http.Handler | ||||
| 	if r.Match(req, &match) { | ||||
| 		handler = match.Handler | ||||
| 		setVars(req, match.Vars) | ||||
| 		setCurrentRoute(req, match.Route) | ||||
| 		req = setVars(req, match.Vars) | ||||
| 		req = setCurrentRoute(req, match.Route) | ||||
| 	} | ||||
| 	if handler == nil { | ||||
| 		handler = r.NotFoundHandler | ||||
| 		if handler == nil { | ||||
| 			handler = http.NotFoundHandler() | ||||
| 		} | ||||
| 		handler = http.NotFoundHandler() | ||||
| 	} | ||||
| 	if !r.KeepContext { | ||||
| 		defer context.Clear(req) | ||||
| 		defer contextClear(req) | ||||
| 	} | ||||
| 	handler.ServeHTTP(w, req) | ||||
| } | ||||
|  | @ -128,6 +144,34 @@ func (r *Router) StrictSlash(value bool) *Router { | |||
| 	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
 | ||||
| // ----------------------------------------------------------------------------
 | ||||
|  | @ -152,13 +196,20 @@ func (r *Router) getRegexpGroup() *routeRegexpGroup { | |||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (r *Router) buildVars(m map[string]string) map[string]string { | ||||
| 	if r.parent != nil { | ||||
| 		m = r.parent.buildVars(m) | ||||
| 	} | ||||
| 	return m | ||||
| } | ||||
| 
 | ||||
| // ----------------------------------------------------------------------------
 | ||||
| // Route factories
 | ||||
| // ----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| // NewRoute registers an empty route.
 | ||||
| func (r *Router) NewRoute() *Route { | ||||
| 	route := &Route{parent: r, strictSlash: r.strictSlash} | ||||
| 	route := &Route{parent: r, strictSlash: r.strictSlash, skipClean: r.skipClean, useEncodedPath: r.useEncodedPath} | ||||
| 	r.routes = append(r.routes, route) | ||||
| 	return route | ||||
| } | ||||
|  | @ -224,6 +275,61 @@ func (r *Router) Schemes(schemes ...string) *Route { | |||
| 	return r.NewRoute().Schemes(schemes...) | ||||
| } | ||||
| 
 | ||||
| // BuildVarsFunc registers a new route with a custom function for modifying
 | ||||
| // route variables before building a URL.
 | ||||
| func (r *Router) BuildVarsFunc(f BuildVarsFunc) *Route { | ||||
| 	return r.NewRoute().BuildVarsFunc(f) | ||||
| } | ||||
| 
 | ||||
| // Walk walks the router and all its sub-routers, calling walkFn for each route
 | ||||
| // in the tree. The routes are walked in the order they were added. Sub-routers
 | ||||
| // are explored depth-first.
 | ||||
| func (r *Router) Walk(walkFn WalkFunc) error { | ||||
| 	return r.walk(walkFn, []*Route{}) | ||||
| } | ||||
| 
 | ||||
| // SkipRouter is used as a return value from WalkFuncs to indicate that the
 | ||||
| // router that walk is about to descend down to should be skipped.
 | ||||
| var SkipRouter = errors.New("skip this router") | ||||
| 
 | ||||
| // WalkFunc is the type of the function called for each route visited by Walk.
 | ||||
| // At every invocation, it is given the current route, and the current router,
 | ||||
| // and a list of ancestor routes that lead to the current route.
 | ||||
| type WalkFunc func(route *Route, router *Router, ancestors []*Route) error | ||||
| 
 | ||||
| func (r *Router) walk(walkFn WalkFunc, ancestors []*Route) error { | ||||
| 	for _, t := range r.routes { | ||||
| 		if t.regexp == nil || t.regexp.path == nil || t.regexp.path.template == "" { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		err := walkFn(t, r, ancestors) | ||||
| 		if err == SkipRouter { | ||||
| 			continue | ||||
| 		} | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		for _, sr := range t.matchers { | ||||
| 			if h, ok := sr.(*Router); ok { | ||||
| 				err := h.walk(walkFn, ancestors) | ||||
| 				if err != nil { | ||||
| 					return err | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		if h, ok := t.handler.(*Router); ok { | ||||
| 			ancestors = append(ancestors, t) | ||||
| 			err := h.walk(walkFn, ancestors) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			ancestors = ancestors[:len(ancestors)-1] | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // ----------------------------------------------------------------------------
 | ||||
| // Context
 | ||||
| // ----------------------------------------------------------------------------
 | ||||
|  | @ -244,32 +350,58 @@ const ( | |||
| 
 | ||||
| // 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 { | ||||
| 	if rv := contextGet(r, varsKey); rv != nil { | ||||
| 		return rv.(map[string]string) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // CurrentRoute returns the matched route for the current request, if any.
 | ||||
| // This only works when called inside the handler of the matched route
 | ||||
| // because the matched route is stored in the request context which is cleared
 | ||||
| // after the handler returns, unless the KeepContext option is set on the
 | ||||
| // Router.
 | ||||
| func CurrentRoute(r *http.Request) *Route { | ||||
| 	if rv := context.Get(r, routeKey); rv != nil { | ||||
| 	if rv := contextGet(r, routeKey); rv != nil { | ||||
| 		return rv.(*Route) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func setVars(r *http.Request, val interface{}) { | ||||
| 	context.Set(r, varsKey, val) | ||||
| func setVars(r *http.Request, val interface{}) *http.Request { | ||||
| 	return contextSet(r, varsKey, val) | ||||
| } | ||||
| 
 | ||||
| func setCurrentRoute(r *http.Request, val interface{}) { | ||||
| 	context.Set(r, routeKey, val) | ||||
| func setCurrentRoute(r *http.Request, val interface{}) *http.Request { | ||||
| 	return contextSet(r, routeKey, val) | ||||
| } | ||||
| 
 | ||||
| // ----------------------------------------------------------------------------
 | ||||
| // Helpers
 | ||||
| // ----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| // getPath returns the escaped path if possible; doing what URL.EscapedPath()
 | ||||
| // which was added in go1.5 does
 | ||||
| func getPath(req *http.Request) string { | ||||
| 	if req.RequestURI != "" { | ||||
| 		// Extract the path from RequestURI (which is escaped unlike URL.Path)
 | ||||
| 		// as detailed here as detailed in https://golang.org/pkg/net/url/#URL
 | ||||
| 		// for < 1.5 server side workaround
 | ||||
| 		// http://localhost/path/here?v=1 -> /path/here
 | ||||
| 		path := req.RequestURI | ||||
| 		path = strings.TrimPrefix(path, req.URL.Scheme+`://`) | ||||
| 		path = strings.TrimPrefix(path, req.URL.Host) | ||||
| 		if i := strings.LastIndex(path, "?"); i > -1 { | ||||
| 			path = path[:i] | ||||
| 		} | ||||
| 		if i := strings.LastIndex(path, "#"); i > -1 { | ||||
| 			path = path[:i] | ||||
| 		} | ||||
| 		return path | ||||
| 	} | ||||
| 	return req.URL.Path | ||||
| } | ||||
| 
 | ||||
| // cleanPath returns the canonical path for p, eliminating . and .. elements.
 | ||||
| // Borrowed from the net/http package.
 | ||||
| func cleanPath(p string) string { | ||||
|  | @ -285,6 +417,7 @@ func cleanPath(p string) string { | |||
| 	if p[len(p)-1] == '/' && np != "/" { | ||||
| 		np += "/" | ||||
| 	} | ||||
| 
 | ||||
| 	return np | ||||
| } | ||||
| 
 | ||||
|  | @ -300,13 +433,24 @@ func uniqueVars(s1, s2 []string) error { | |||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // mapFromPairs converts variadic string parameters to a string map.
 | ||||
| func mapFromPairs(pairs ...string) (map[string]string, error) { | ||||
| // checkPairs returns the count of strings passed in, and an error if
 | ||||
| // the count is not an even number.
 | ||||
| func checkPairs(pairs ...string) (int, error) { | ||||
| 	length := len(pairs) | ||||
| 	if length%2 != 0 { | ||||
| 		return nil, fmt.Errorf( | ||||
| 		return length, fmt.Errorf( | ||||
| 			"mux: number of parameters must be multiple of 2, got %v", pairs) | ||||
| 	} | ||||
| 	return length, nil | ||||
| } | ||||
| 
 | ||||
| // mapFromPairsToString converts variadic string parameters to a
 | ||||
| // string to string map.
 | ||||
| func mapFromPairsToString(pairs ...string) (map[string]string, error) { | ||||
| 	length, err := checkPairs(pairs...) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	m := make(map[string]string, length/2) | ||||
| 	for i := 0; i < length; i += 2 { | ||||
| 		m[pairs[i]] = pairs[i+1] | ||||
|  | @ -314,6 +458,24 @@ func mapFromPairs(pairs ...string) (map[string]string, error) { | |||
| 	return m, nil | ||||
| } | ||||
| 
 | ||||
| // mapFromPairsToRegex converts variadic string paramers to a
 | ||||
| // string to regex map.
 | ||||
| func mapFromPairsToRegex(pairs ...string) (map[string]*regexp.Regexp, error) { | ||||
| 	length, err := checkPairs(pairs...) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	m := make(map[string]*regexp.Regexp, length/2) | ||||
| 	for i := 0; i < length; i += 2 { | ||||
| 		regex, err := regexp.Compile(pairs[i+1]) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		m[pairs[i]] = regex | ||||
| 	} | ||||
| 	return m, nil | ||||
| } | ||||
| 
 | ||||
| // matchInArray returns true if the given string value is in the array.
 | ||||
| func matchInArray(arr []string, value string) bool { | ||||
| 	for _, v := range arr { | ||||
|  | @ -324,9 +486,8 @@ func matchInArray(arr []string, value string) bool { | |||
| 	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 { | ||||
| // matchMapWithString returns true if the given key/value pairs exist in a given map.
 | ||||
| func matchMapWithString(toCheck map[string]string, toMatch map[string][]string, canonicalKey bool) bool { | ||||
| 	for k, v := range toCheck { | ||||
| 		// Check if key exists.
 | ||||
| 		if canonicalKey { | ||||
|  | @ -351,3 +512,31 @@ func matchMap(toCheck map[string]string, toMatch map[string][]string, | |||
| 	} | ||||
| 	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/url" | ||||
| 	"regexp" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| ) | ||||
| 
 | ||||
|  | @ -23,7 +24,7 @@ import ( | |||
| // 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, matchQuery, strictSlash bool) (*routeRegexp, error) { | ||||
| func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash, useEncodedPath bool) (*routeRegexp, error) { | ||||
| 	// Check if it is well-formed.
 | ||||
| 	idxs, errBraces := braceIndices(tpl) | ||||
| 	if errBraces != nil { | ||||
|  | @ -34,8 +35,7 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash | |||
| 	// Now let's parse it.
 | ||||
| 	defaultPattern := "[^/]+" | ||||
| 	if matchQuery { | ||||
| 		defaultPattern = "[^?&]+" | ||||
| 		matchPrefix = true | ||||
| 		defaultPattern = "[^?&]*" | ||||
| 	} else if matchHost { | ||||
| 		defaultPattern = "[^.]+" | ||||
| 		matchPrefix = false | ||||
|  | @ -53,9 +53,7 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash | |||
| 	varsN := make([]string, len(idxs)/2) | ||||
| 	varsR := make([]*regexp.Regexp, len(idxs)/2) | ||||
| 	pattern := bytes.NewBufferString("") | ||||
| 	if !matchQuery { | ||||
| 		pattern.WriteByte('^') | ||||
| 	} | ||||
| 	pattern.WriteByte('^') | ||||
| 	reverse := bytes.NewBufferString("") | ||||
| 	var end int | ||||
| 	var err error | ||||
|  | @ -75,9 +73,11 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash | |||
| 				tpl[idxs[i]:end]) | ||||
| 		} | ||||
| 		// 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.
 | ||||
| 		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)) | ||||
|  | @ -91,6 +91,12 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash | |||
| 	if strictSlash { | ||||
| 		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 { | ||||
| 		pattern.WriteByte('$') | ||||
| 	} | ||||
|  | @ -103,16 +109,24 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash | |||
| 	if errCompile != nil { | ||||
| 		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!
 | ||||
| 	return &routeRegexp{ | ||||
| 		template:    template, | ||||
| 		matchHost:   matchHost, | ||||
| 		matchQuery:  matchQuery, | ||||
| 		strictSlash: strictSlash, | ||||
| 		regexp:      reg, | ||||
| 		reverse:     reverse.String(), | ||||
| 		varsN:       varsN, | ||||
| 		varsR:       varsR, | ||||
| 		template:       template, | ||||
| 		matchHost:      matchHost, | ||||
| 		matchQuery:     matchQuery, | ||||
| 		strictSlash:    strictSlash, | ||||
| 		useEncodedPath: useEncodedPath, | ||||
| 		regexp:         reg, | ||||
| 		reverse:        reverse.String(), | ||||
| 		varsN:          varsN, | ||||
| 		varsR:          varsR, | ||||
| 	}, nil | ||||
| } | ||||
| 
 | ||||
|  | @ -127,6 +141,9 @@ type routeRegexp struct { | |||
| 	matchQuery bool | ||||
| 	// The strictSlash value defined on the route, but disabled if PathPrefix was used.
 | ||||
| 	strictSlash bool | ||||
| 	// Determines whether to use encoded path from getPath function or unencoded
 | ||||
| 	// req.URL.Path for path matching
 | ||||
| 	useEncodedPath bool | ||||
| 	// Expanded regexp.
 | ||||
| 	regexp *regexp.Regexp | ||||
| 	// Reverse template.
 | ||||
|  | @ -141,20 +158,20 @@ type routeRegexp struct { | |||
| func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool { | ||||
| 	if !r.matchHost { | ||||
| 		if r.matchQuery { | ||||
| 			return r.regexp.MatchString(req.URL.RawQuery) | ||||
| 		} else { | ||||
| 			return r.regexp.MatchString(req.URL.Path) | ||||
| 			return r.matchQueryString(req) | ||||
| 		} | ||||
| 		path := req.URL.Path | ||||
| 		if r.useEncodedPath { | ||||
| 			path = getPath(req) | ||||
| 		} | ||||
| 		return r.regexp.MatchString(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 | ||||
| 	} | ||||
| func (r *routeRegexp) url(values map[string]string) (string, error) { | ||||
| 	urlValues := make([]interface{}, len(r.varsN)) | ||||
| 	for k, v := range r.varsN { | ||||
| 		value, ok := values[v] | ||||
|  | @ -179,11 +196,31 @@ func (r *routeRegexp) url(pairs ...string) (string, error) { | |||
| 	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.
 | ||||
| // It returns an error in case of unbalanced braces.
 | ||||
| func braceIndices(s string) ([]int, error) { | ||||
| 	var level, idx int | ||||
| 	idxs := make([]int, 0) | ||||
| 	var idxs []int | ||||
| 	for i := 0; i < len(s); i++ { | ||||
| 		switch s[i] { | ||||
| 		case '{': | ||||
|  | @ -204,6 +241,11 @@ func braceIndices(s string) ([]int, error) { | |||
| 	return idxs, nil | ||||
| } | ||||
| 
 | ||||
| // varGroupName builds a capturing group name for the indexed variable.
 | ||||
| func varGroupName(idx int) string { | ||||
| 	return "v" + strconv.Itoa(idx) | ||||
| } | ||||
| 
 | ||||
| // ----------------------------------------------------------------------------
 | ||||
| // routeRegexpGroup
 | ||||
| // ----------------------------------------------------------------------------
 | ||||
|  | @ -219,23 +261,24 @@ type routeRegexpGroup struct { | |||
| 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] | ||||
| 			} | ||||
| 		host := getHost(req) | ||||
| 		matches := v.host.regexp.FindStringSubmatchIndex(host) | ||||
| 		if len(matches) > 0 { | ||||
| 			extractVars(host, matches, v.host.varsN, m.Vars) | ||||
| 		} | ||||
| 	} | ||||
| 	path := req.URL.Path | ||||
| 	if r.useEncodedPath { | ||||
| 		path = getPath(req) | ||||
| 	} | ||||
| 	// 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] | ||||
| 			} | ||||
| 		matches := v.path.regexp.FindStringSubmatchIndex(path) | ||||
| 		if len(matches) > 0 { | ||||
| 			extractVars(path, matches, v.path.varsN, m.Vars) | ||||
| 			// Check if we should redirect.
 | ||||
| 			if v.path.strictSlash { | ||||
| 				p1 := strings.HasSuffix(req.URL.Path, "/") | ||||
| 				p1 := strings.HasSuffix(path, "/") | ||||
| 				p2 := strings.HasSuffix(v.path.template, "/") | ||||
| 				if p1 != p2 { | ||||
| 					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.
 | ||||
| 	rawQuery := req.URL.RawQuery | ||||
| 	for _, q := range v.queries { | ||||
| 		queryVars := q.regexp.FindStringSubmatch(rawQuery) | ||||
| 		if queryVars != nil { | ||||
| 			for k, v := range q.varsN { | ||||
| 				m.Vars[v] = queryVars[k+1] | ||||
| 			} | ||||
| 		queryURL := q.getURLQuery(req) | ||||
| 		matches := q.regexp.FindStringSubmatchIndex(queryURL) | ||||
| 		if len(matches) > 0 { | ||||
| 			extractVars(queryURL, matches, q.varsN, m.Vars) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | @ -274,3 +315,9 @@ func getHost(r *http.Request) string { | |||
| 	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" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
| ) | ||||
| 
 | ||||
|  | @ -25,12 +26,23 @@ type Route struct { | |||
| 	// If true, when the path pattern is "/path/", accessing "/path" will
 | ||||
| 	// redirect to the former and vice versa.
 | ||||
| 	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.
 | ||||
| 	buildOnly bool | ||||
| 	// The name used to build URLs.
 | ||||
| 	name string | ||||
| 	// Error resulted from building a route.
 | ||||
| 	err error | ||||
| 
 | ||||
| 	buildVarsFunc BuildVarsFunc | ||||
| } | ||||
| 
 | ||||
| func (r *Route) SkipClean() bool { | ||||
| 	return r.skipClean | ||||
| } | ||||
| 
 | ||||
| // Match matches the route against the request.
 | ||||
|  | @ -141,14 +153,14 @@ func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix, matchQuery | |||
| 	} | ||||
| 	r.regexp = r.getRegexpGroup() | ||||
| 	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) | ||||
| 		} | ||||
| 		if r.regexp.path != nil { | ||||
| 			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 { | ||||
| 		return err | ||||
| 	} | ||||
|  | @ -186,7 +198,7 @@ func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix, matchQuery | |||
| type headerMatcher map[string]string | ||||
| 
 | ||||
| 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.
 | ||||
|  | @ -197,22 +209,46 @@ func (m headerMatcher) Match(r *http.Request, match *RouteMatch) bool { | |||
| //               "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.
 | ||||
| // If 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...) | ||||
| 		headers, r.err = mapFromPairsToString(pairs...) | ||||
| 		return r.addMatcher(headerMatcher(headers)) | ||||
| 	} | ||||
| 	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 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:
 | ||||
| // Variables can define an optional regexp pattern to be matched:
 | ||||
| //
 | ||||
| // - {name} matches anything until the next dot.
 | ||||
| //
 | ||||
|  | @ -221,7 +257,7 @@ func (r *Route) Headers(pairs ...string) *Route { | |||
| // For example:
 | ||||
| //
 | ||||
| //     r := mux.NewRouter()
 | ||||
| //     r.Host("www.domain.com")
 | ||||
| //     r.Host("www.example.com")
 | ||||
| //     r.Host("{subdomain}.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.
 | ||||
| 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 { | ||||
| 	return m(r, match) | ||||
| } | ||||
|  | @ -270,7 +307,7 @@ func (r *Route) Methods(methods ...string) *Route { | |||
| // Path adds a matcher for the URL path.
 | ||||
| // It accepts a template with zero or more URL variables enclosed by {}. The
 | ||||
| // 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.
 | ||||
| //
 | ||||
|  | @ -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.
 | ||||
| //
 | ||||
| // 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.
 | ||||
| //
 | ||||
|  | @ -334,7 +371,7 @@ func (r *Route) Queries(pairs ...string) *Route { | |||
| 		return nil | ||||
| 	} | ||||
| 	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 | ||||
| 		} | ||||
| 	} | ||||
|  | @ -360,6 +397,19 @@ func (r *Route) Schemes(schemes ...string) *Route { | |||
| 	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 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:
 | ||||
| //
 | ||||
| //     r := mux.NewRouter()
 | ||||
| //     s := r.Host("www.domain.com").Subrouter()
 | ||||
| //     s := r.Host("www.example.com").Subrouter()
 | ||||
| //     s.HandleFunc("/products/", ProductsHandler)
 | ||||
| //     s.HandleFunc("/products/{key}", ProductHandler)
 | ||||
| //     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 { | ||||
| 		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 err error | ||||
| 	if r.regexp.host != nil { | ||||
| 		// Set a default scheme.
 | ||||
| 		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 | ||||
| 		} | ||||
| 	} | ||||
| 	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 | ||||
| 		} | ||||
| 	} | ||||
|  | @ -453,7 +506,11 @@ func (r *Route) URLHost(pairs ...string) (*url.URL, error) { | |||
| 	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...) | ||||
| 	values, err := r.prepareVars(pairs...) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	host, err := r.regexp.host.url(values) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | @ -473,7 +530,11 @@ func (r *Route) URLPath(pairs ...string) (*url.URL, error) { | |||
| 	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...) | ||||
| 	values, err := r.prepareVars(pairs...) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	path, err := r.regexp.path.url(values) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | @ -482,6 +543,56 @@ func (r *Route) URLPath(pairs ...string) (*url.URL, error) { | |||
| 	}, 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
 | ||||
| // ----------------------------------------------------------------------------
 | ||||
|  | @ -490,6 +601,7 @@ func (r *Route) URLPath(pairs ...string) (*url.URL, error) { | |||
| type parentRoute interface { | ||||
| 	getNamedRoutes() map[string]*Route | ||||
| 	getRegexpGroup() *routeRegexpGroup | ||||
| 	buildVars(map[string]string) map[string]string | ||||
| } | ||||
| 
 | ||||
| // getNamedRoutes returns the map where named routes are registered.
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue