commit
						f4506b517a
					
				
							
								
								
									
										2
									
								
								go.mod
								
								
								
								
							
							
						
						
									
										2
									
								
								go.mod
								
								
								
								
							|  | @ -21,7 +21,7 @@ require ( | ||||||
| 	github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 | 	github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 | ||||||
| 	github.com/gomodule/redigo v1.8.2 | 	github.com/gomodule/redigo v1.8.2 | ||||||
| 	github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33 | 	github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33 | ||||||
| 	github.com/gorilla/mux v1.7.2 | 	github.com/gorilla/mux v1.8.0 | ||||||
| 	github.com/inconshreveable/mousetrap v1.0.0 // indirect | 	github.com/inconshreveable/mousetrap v1.0.0 // indirect | ||||||
| 	github.com/kr/pretty v0.1.0 // indirect | 	github.com/kr/pretty v0.1.0 // indirect | ||||||
| 	github.com/marstr/guid v1.1.0 // indirect | 	github.com/marstr/guid v1.1.0 // indirect | ||||||
|  |  | ||||||
							
								
								
									
										4
									
								
								go.sum
								
								
								
								
							
							
						
						
									
										4
									
								
								go.sum
								
								
								
								
							|  | @ -57,8 +57,8 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw | ||||||
| github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= | ||||||
| github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33 h1:893HsJqtxp9z1SF76gg6hY70hRY1wVlTSnC/h1yUDCo= | github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33 h1:893HsJqtxp9z1SF76gg6hY70hRY1wVlTSnC/h1yUDCo= | ||||||
| github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= | github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= | ||||||
| github.com/gorilla/mux v1.7.2 h1:zoNxOV7WjqXptQOVngLmcSQgXmgk4NMz1HibBchjl/I= | github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= | ||||||
| github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= | github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= | ||||||
| github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= | ||||||
| github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= | ||||||
| github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc= | github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc= | ||||||
|  |  | ||||||
|  | @ -1,24 +0,0 @@ | ||||||
| language: go |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| matrix: |  | ||||||
|   include: |  | ||||||
|     - go: 1.7.x |  | ||||||
|     - go: 1.8.x |  | ||||||
|     - go: 1.9.x |  | ||||||
|     - go: 1.10.x |  | ||||||
|     - go: 1.11.x |  | ||||||
|     - go: 1.x |  | ||||||
|       env: LATEST=true |  | ||||||
|     - go: tip |  | ||||||
|   allow_failures: |  | ||||||
|     - go: tip |  | ||||||
| 
 |  | ||||||
| install: |  | ||||||
|   - # Skip |  | ||||||
| 
 |  | ||||||
| script: |  | ||||||
|   - go get -t -v ./... |  | ||||||
|   - diff -u <(echo -n) <(gofmt -d .) |  | ||||||
|   - if [[ "$LATEST" = true ]]; then go vet .; fi |  | ||||||
|   - go test -v -race ./... |  | ||||||
|  | @ -1,11 +0,0 @@ | ||||||
| **What version of Go are you running?** (Paste the output of `go version`) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| **What version of gorilla/mux are you at?** (Paste the output of `git rev-parse HEAD` inside `$GOPATH/src/github.com/gorilla/mux`) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| **Describe your problem** (and what you have tried so far) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| **Paste a minimal, runnable, reproduction of your issue below** (use backticks to format it) |  | ||||||
| 
 |  | ||||||
|  | @ -1,10 +1,10 @@ | ||||||
| # gorilla/mux | # gorilla/mux | ||||||
| 
 | 
 | ||||||
| [](https://godoc.org/github.com/gorilla/mux) | [](https://godoc.org/github.com/gorilla/mux) | ||||||
| [](https://travis-ci.org/gorilla/mux) | [](https://circleci.com/gh/gorilla/mux) | ||||||
| [](https://sourcegraph.com/github.com/gorilla/mux?badge) | [](https://sourcegraph.com/github.com/gorilla/mux?badge) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
| 
 | 
 | ||||||
| https://www.gorillatoolkit.org/pkg/mux | https://www.gorillatoolkit.org/pkg/mux | ||||||
| 
 | 
 | ||||||
|  | @ -25,10 +25,12 @@ The name mux stands for "HTTP request multiplexer". Like the standard `http.Serv | ||||||
| * [Examples](#examples) | * [Examples](#examples) | ||||||
| * [Matching Routes](#matching-routes) | * [Matching Routes](#matching-routes) | ||||||
| * [Static Files](#static-files) | * [Static Files](#static-files) | ||||||
|  | * [Serving Single Page Applications](#serving-single-page-applications) (e.g. React, Vue, Ember.js, etc.) | ||||||
| * [Registered URLs](#registered-urls) | * [Registered URLs](#registered-urls) | ||||||
| * [Walking Routes](#walking-routes) | * [Walking Routes](#walking-routes) | ||||||
| * [Graceful Shutdown](#graceful-shutdown) | * [Graceful Shutdown](#graceful-shutdown) | ||||||
| * [Middleware](#middleware) | * [Middleware](#middleware) | ||||||
|  | * [Handling CORS Requests](#handling-cors-requests) | ||||||
| * [Testing Handlers](#testing-handlers) | * [Testing Handlers](#testing-handlers) | ||||||
| * [Full Example](#full-example) | * [Full Example](#full-example) | ||||||
| 
 | 
 | ||||||
|  | @ -210,6 +212,93 @@ func main() { | ||||||
| } | } | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
|  | ### Serving Single Page Applications | ||||||
|  | 
 | ||||||
|  | Most of the time it makes sense to serve your SPA on a separate web server from your API, | ||||||
|  | but sometimes it's desirable to serve them both from one place. It's possible to write a simple | ||||||
|  | handler for serving your SPA (for use with React Router's [BrowserRouter](https://reacttraining.com/react-router/web/api/BrowserRouter) for example), and leverage | ||||||
|  | mux's powerful routing for your API endpoints. | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  | package main | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"log" | ||||||
|  | 	"net/http" | ||||||
|  | 	"os" | ||||||
|  | 	"path/filepath" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"github.com/gorilla/mux" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // spaHandler implements the http.Handler interface, so we can use it | ||||||
|  | // to respond to HTTP requests. The path to the static directory and | ||||||
|  | // path to the index file within that static directory are used to | ||||||
|  | // serve the SPA in the given static directory. | ||||||
|  | type spaHandler struct { | ||||||
|  | 	staticPath string | ||||||
|  | 	indexPath  string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ServeHTTP inspects the URL path to locate a file within the static dir | ||||||
|  | // on the SPA handler. If a file is found, it will be served. If not, the | ||||||
|  | // file located at the index path on the SPA handler will be served. This | ||||||
|  | // is suitable behavior for serving an SPA (single page application). | ||||||
|  | func (h spaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { | ||||||
|  |     // get the absolute path to prevent directory traversal | ||||||
|  | 	path, err := filepath.Abs(r.URL.Path) | ||||||
|  | 	if err != nil { | ||||||
|  |         // if we failed to get the absolute path respond with a 400 bad request | ||||||
|  |         // and stop | ||||||
|  | 		http.Error(w, err.Error(), http.StatusBadRequest) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  |     // prepend the path with the path to the static directory | ||||||
|  | 	path = filepath.Join(h.staticPath, path) | ||||||
|  | 
 | ||||||
|  |     // check whether a file exists at the given path | ||||||
|  | 	_, err = os.Stat(path) | ||||||
|  | 	if os.IsNotExist(err) { | ||||||
|  | 		// file does not exist, serve index.html | ||||||
|  | 		http.ServeFile(w, r, filepath.Join(h.staticPath, h.indexPath)) | ||||||
|  | 		return | ||||||
|  | 	} else if err != nil { | ||||||
|  |         // if we got an error (that wasn't that the file doesn't exist) stating the | ||||||
|  |         // file, return a 500 internal server error and stop | ||||||
|  | 		http.Error(w, err.Error(), http.StatusInternalServerError) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  |     // otherwise, use http.FileServer to serve the static dir | ||||||
|  | 	http.FileServer(http.Dir(h.staticPath)).ServeHTTP(w, r) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func main() { | ||||||
|  | 	router := mux.NewRouter() | ||||||
|  | 
 | ||||||
|  | 	router.HandleFunc("/api/health", func(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 		// an example API handler | ||||||
|  | 		json.NewEncoder(w).Encode(map[string]bool{"ok": true}) | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	spa := spaHandler{staticPath: "build", indexPath: "index.html"} | ||||||
|  | 	router.PathPrefix("/").Handler(spa) | ||||||
|  | 
 | ||||||
|  | 	srv := &http.Server{ | ||||||
|  | 		Handler: router, | ||||||
|  | 		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 | ### Registered URLs | ||||||
| 
 | 
 | ||||||
| Now let's see how to build registered URLs. | Now let's see how to build registered URLs. | ||||||
|  | @ -491,6 +580,73 @@ r.Use(amw.Middleware) | ||||||
| 
 | 
 | ||||||
| Note: The handler chain will be stopped if your middleware doesn't call `next.ServeHTTP()` with the corresponding parameters. This can be used to abort a request if the middleware writer wants to. Middlewares _should_ write to `ResponseWriter` if they _are_ going to terminate the request, and they _should not_ write to `ResponseWriter` if they _are not_ going to terminate it. | Note: The handler chain will be stopped if your middleware doesn't call `next.ServeHTTP()` with the corresponding parameters. This can be used to abort a request if the middleware writer wants to. Middlewares _should_ write to `ResponseWriter` if they _are_ going to terminate the request, and they _should not_ write to `ResponseWriter` if they _are not_ going to terminate it. | ||||||
| 
 | 
 | ||||||
|  | ### Handling CORS Requests | ||||||
|  | 
 | ||||||
|  | [CORSMethodMiddleware](https://godoc.org/github.com/gorilla/mux#CORSMethodMiddleware) intends to make it easier to strictly set the `Access-Control-Allow-Methods` response header. | ||||||
|  | 
 | ||||||
|  | * You will still need to use your own CORS handler to set the other CORS headers such as `Access-Control-Allow-Origin` | ||||||
|  | * The middleware will set the `Access-Control-Allow-Methods` header to all the method matchers (e.g. `r.Methods(http.MethodGet, http.MethodPut, http.MethodOptions)` -> `Access-Control-Allow-Methods: GET,PUT,OPTIONS`) on a route | ||||||
|  | * If you do not specify any methods, then: | ||||||
|  | > _Important_: there must be an `OPTIONS` method matcher for the middleware to set the headers. | ||||||
|  | 
 | ||||||
|  | Here is an example of using `CORSMethodMiddleware` along with a custom `OPTIONS` handler to set all the required CORS headers: | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  | package main | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"net/http" | ||||||
|  | 	"github.com/gorilla/mux" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func main() { | ||||||
|  |     r := mux.NewRouter() | ||||||
|  | 
 | ||||||
|  |     // IMPORTANT: you must specify an OPTIONS method matcher for the middleware to set CORS headers | ||||||
|  |     r.HandleFunc("/foo", fooHandler).Methods(http.MethodGet, http.MethodPut, http.MethodPatch, http.MethodOptions) | ||||||
|  |     r.Use(mux.CORSMethodMiddleware(r)) | ||||||
|  |      | ||||||
|  |     http.ListenAndServe(":8080", r) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func fooHandler(w http.ResponseWriter, r *http.Request) { | ||||||
|  |     w.Header().Set("Access-Control-Allow-Origin", "*") | ||||||
|  |     if r.Method == http.MethodOptions { | ||||||
|  |         return | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     w.Write([]byte("foo")) | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | And an request to `/foo` using something like: | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | curl localhost:8080/foo -v | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Would look like: | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | *   Trying ::1... | ||||||
|  | * TCP_NODELAY set | ||||||
|  | * Connected to localhost (::1) port 8080 (#0) | ||||||
|  | > GET /foo HTTP/1.1 | ||||||
|  | > Host: localhost:8080 | ||||||
|  | > User-Agent: curl/7.59.0 | ||||||
|  | > Accept: */* | ||||||
|  | >  | ||||||
|  | < HTTP/1.1 200 OK | ||||||
|  | < Access-Control-Allow-Methods: GET,PUT,PATCH,OPTIONS | ||||||
|  | < Access-Control-Allow-Origin: * | ||||||
|  | < Date: Fri, 28 Jun 2019 20:13:30 GMT | ||||||
|  | < Content-Length: 3 | ||||||
|  | < Content-Type: text/plain; charset=utf-8 | ||||||
|  | <  | ||||||
|  | * Connection #0 to host localhost left intact | ||||||
|  | foo | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
| ### Testing Handlers | ### Testing Handlers | ||||||
| 
 | 
 | ||||||
| Testing handlers in a Go web application is straightforward, and _mux_ doesn't complicate this any further. Given two files: `endpoints.go` and `endpoints_test.go`, here's how we'd test an application using _mux_. | Testing handlers in a Go web application is straightforward, and _mux_ doesn't complicate this any further. Given two files: `endpoints.go` and `endpoints_test.go`, here's how we'd test an application using _mux_. | ||||||
|  |  | ||||||
|  | @ -1,18 +0,0 @@ | ||||||
| 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)) |  | ||||||
| } |  | ||||||
|  | @ -295,7 +295,7 @@ A more complex authentication middleware, which maps session token to users, cou | ||||||
| 	r := mux.NewRouter() | 	r := mux.NewRouter() | ||||||
| 	r.HandleFunc("/", handler) | 	r.HandleFunc("/", handler) | ||||||
| 
 | 
 | ||||||
| 	amw := authenticationMiddleware{} | 	amw := authenticationMiddleware{tokenUsers: make(map[string]string)} | ||||||
| 	amw.Populate() | 	amw.Populate() | ||||||
| 
 | 
 | ||||||
| 	r.Use(amw.Middleware) | 	r.Use(amw.Middleware) | ||||||
|  |  | ||||||
|  | @ -1 +1,3 @@ | ||||||
| module github.com/gorilla/mux | module github.com/gorilla/mux | ||||||
|  | 
 | ||||||
|  | go 1.12 | ||||||
|  |  | ||||||
|  | @ -32,37 +32,19 @@ func (r *Router) useInterface(mw middleware) { | ||||||
| 	r.middlewares = append(r.middlewares, mw) | 	r.middlewares = append(r.middlewares, mw) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // CORSMethodMiddleware sets the Access-Control-Allow-Methods response header
 | // CORSMethodMiddleware automatically sets the Access-Control-Allow-Methods response header
 | ||||||
| // on a request, by matching routes based only on paths. It also handles
 | // on requests for routes that have an OPTIONS method matcher to all the method matchers on
 | ||||||
| // OPTIONS requests, by settings Access-Control-Allow-Methods, and then
 | // the route. Routes that do not explicitly handle OPTIONS requests will not be processed
 | ||||||
| // returning without calling the next http handler.
 | // by the middleware. See examples for usage.
 | ||||||
| func CORSMethodMiddleware(r *Router) MiddlewareFunc { | func CORSMethodMiddleware(r *Router) MiddlewareFunc { | ||||||
| 	return func(next http.Handler) http.Handler { | 	return func(next http.Handler) http.Handler { | ||||||
| 		return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { | 		return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { | ||||||
| 			var allMethods []string | 			allMethods, err := getAllMethodsForRoute(r, req) | ||||||
| 
 |  | ||||||
| 			err := r.Walk(func(route *Route, _ *Router, _ []*Route) error { |  | ||||||
| 				for _, m := range route.matchers { |  | ||||||
| 					if _, ok := m.(*routeRegexp); ok { |  | ||||||
| 						if m.Match(req, &RouteMatch{}) { |  | ||||||
| 							methods, err := route.GetMethods() |  | ||||||
| 							if err != nil { |  | ||||||
| 								return err |  | ||||||
| 							} |  | ||||||
| 
 |  | ||||||
| 							allMethods = append(allMethods, methods...) |  | ||||||
| 						} |  | ||||||
| 						break |  | ||||||
| 					} |  | ||||||
| 				} |  | ||||||
| 				return nil |  | ||||||
| 			}) |  | ||||||
| 
 |  | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				w.Header().Set("Access-Control-Allow-Methods", strings.Join(append(allMethods, "OPTIONS"), ",")) | 				for _, v := range allMethods { | ||||||
| 
 | 					if v == http.MethodOptions { | ||||||
| 				if req.Method == "OPTIONS" { | 						w.Header().Set("Access-Control-Allow-Methods", strings.Join(allMethods, ",")) | ||||||
| 					return | 					} | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
|  | @ -70,3 +52,23 @@ func CORSMethodMiddleware(r *Router) MiddlewareFunc { | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | // getAllMethodsForRoute returns all the methods from method matchers matching a given
 | ||||||
|  | // request.
 | ||||||
|  | func getAllMethodsForRoute(r *Router, req *http.Request) ([]string, error) { | ||||||
|  | 	var allMethods []string | ||||||
|  | 
 | ||||||
|  | 	for _, route := range r.routes { | ||||||
|  | 		var match RouteMatch | ||||||
|  | 		if route.Match(req, &match) || match.MatchErr == ErrMethodMismatch { | ||||||
|  | 			methods, err := route.GetMethods() | ||||||
|  | 			if err != nil { | ||||||
|  | 				return nil, err | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			allMethods = append(allMethods, methods...) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return allMethods, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -5,6 +5,7 @@ | ||||||
| package mux | package mux | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"context" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"net/http" | 	"net/http" | ||||||
|  | @ -58,8 +59,7 @@ type Router struct { | ||||||
| 
 | 
 | ||||||
| 	// If true, do not clear the request context after handling the request.
 | 	// If true, do not clear the request context after handling the request.
 | ||||||
| 	//
 | 	//
 | ||||||
| 	// Deprecated: No effect when go1.7+ is used, since the context is stored
 | 	// Deprecated: No effect, since the context is stored on the request itself.
 | ||||||
| 	// on the request itself.
 |  | ||||||
| 	KeepContext bool | 	KeepContext bool | ||||||
| 
 | 
 | ||||||
| 	// Slice of middlewares to be called after a match is found
 | 	// Slice of middlewares to be called after a match is found
 | ||||||
|  | @ -111,10 +111,8 @@ func copyRouteConf(r routeConf) routeConf { | ||||||
| 		c.regexp.queries = append(c.regexp.queries, copyRouteRegexp(q)) | 		c.regexp.queries = append(c.regexp.queries, copyRouteRegexp(q)) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	c.matchers = make([]matcher, 0, len(r.matchers)) | 	c.matchers = make([]matcher, len(r.matchers)) | ||||||
| 	for _, m := range r.matchers { | 	copy(c.matchers, r.matchers) | ||||||
| 		c.matchers = append(c.matchers, m) |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	return c | 	return c | ||||||
| } | } | ||||||
|  | @ -197,8 +195,8 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { | ||||||
| 	var handler http.Handler | 	var handler http.Handler | ||||||
| 	if r.Match(req, &match) { | 	if r.Match(req, &match) { | ||||||
| 		handler = match.Handler | 		handler = match.Handler | ||||||
| 		req = setVars(req, match.Vars) | 		req = requestWithVars(req, match.Vars) | ||||||
| 		req = setCurrentRoute(req, match.Route) | 		req = requestWithRoute(req, match.Route) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if handler == nil && match.MatchErr == ErrMethodMismatch { | 	if handler == nil && match.MatchErr == ErrMethodMismatch { | ||||||
|  | @ -428,7 +426,7 @@ 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 := contextGet(r, varsKey); rv != nil { | 	if rv := r.Context().Value(varsKey); rv != nil { | ||||||
| 		return rv.(map[string]string) | 		return rv.(map[string]string) | ||||||
| 	} | 	} | ||||||
| 	return nil | 	return nil | ||||||
|  | @ -437,21 +435,22 @@ func Vars(r *http.Request) map[string]string { | ||||||
| // 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
 | // 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
 | // 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
 | // after the handler returns.
 | ||||||
| // Router.
 |  | ||||||
| func CurrentRoute(r *http.Request) *Route { | func CurrentRoute(r *http.Request) *Route { | ||||||
| 	if rv := contextGet(r, routeKey); rv != nil { | 	if rv := r.Context().Value(routeKey); rv != nil { | ||||||
| 		return rv.(*Route) | 		return rv.(*Route) | ||||||
| 	} | 	} | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func setVars(r *http.Request, val interface{}) *http.Request { | func requestWithVars(r *http.Request, vars map[string]string) *http.Request { | ||||||
| 	return contextSet(r, varsKey, val) | 	ctx := context.WithValue(r.Context(), varsKey, vars) | ||||||
|  | 	return r.WithContext(ctx) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func setCurrentRoute(r *http.Request, val interface{}) *http.Request { | func requestWithRoute(r *http.Request, route *Route) *http.Request { | ||||||
| 	return contextSet(r, routeKey, val) | 	ctx := context.WithValue(r.Context(), routeKey, route) | ||||||
|  | 	return r.WithContext(ctx) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // ----------------------------------------------------------------------------
 | // ----------------------------------------------------------------------------
 | ||||||
|  |  | ||||||
|  | @ -181,21 +181,21 @@ func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool { | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 		return r.regexp.MatchString(host) | 		return r.regexp.MatchString(host) | ||||||
| 	} else { |  | ||||||
| 		if r.regexpType == regexpTypeQuery { |  | ||||||
| 			return r.matchQueryString(req) |  | ||||||
| 		} |  | ||||||
| 		path := req.URL.Path |  | ||||||
| 		if r.options.useEncodedPath { |  | ||||||
| 			path = req.URL.EscapedPath() |  | ||||||
| 		} |  | ||||||
| 		return r.regexp.MatchString(path) |  | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	if r.regexpType == regexpTypeQuery { | ||||||
|  | 		return r.matchQueryString(req) | ||||||
|  | 	} | ||||||
|  | 	path := req.URL.Path | ||||||
|  | 	if r.options.useEncodedPath { | ||||||
|  | 		path = req.URL.EscapedPath() | ||||||
|  | 	} | ||||||
|  | 	return r.regexp.MatchString(path) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // url builds a URL part using the given values.
 | // url builds a URL part using the given values.
 | ||||||
| func (r *routeRegexp) url(values map[string]string) (string, error) { | func (r *routeRegexp) url(values map[string]string) (string, error) { | ||||||
| 	urlValues := make([]interface{}, len(r.varsN)) | 	urlValues := make([]interface{}, len(r.varsN), len(r.varsN)) | ||||||
| 	for k, v := range r.varsN { | 	for k, v := range r.varsN { | ||||||
| 		value, ok := values[v] | 		value, ok := values[v] | ||||||
| 		if !ok { | 		if !ok { | ||||||
|  | @ -230,14 +230,51 @@ func (r *routeRegexp) getURLQuery(req *http.Request) string { | ||||||
| 		return "" | 		return "" | ||||||
| 	} | 	} | ||||||
| 	templateKey := strings.SplitN(r.template, "=", 2)[0] | 	templateKey := strings.SplitN(r.template, "=", 2)[0] | ||||||
| 	for key, vals := range req.URL.Query() { | 	val, ok := findFirstQueryKey(req.URL.RawQuery, templateKey) | ||||||
| 		if key == templateKey && len(vals) > 0 { | 	if ok { | ||||||
| 			return key + "=" + vals[0] | 		return templateKey + "=" + val | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| 	return "" | 	return "" | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // findFirstQueryKey returns the same result as (*url.URL).Query()[key][0].
 | ||||||
|  | // If key was not found, empty string and false is returned.
 | ||||||
|  | func findFirstQueryKey(rawQuery, key string) (value string, ok bool) { | ||||||
|  | 	query := []byte(rawQuery) | ||||||
|  | 	for len(query) > 0 { | ||||||
|  | 		foundKey := query | ||||||
|  | 		if i := bytes.IndexAny(foundKey, "&;"); i >= 0 { | ||||||
|  | 			foundKey, query = foundKey[:i], foundKey[i+1:] | ||||||
|  | 		} else { | ||||||
|  | 			query = query[:0] | ||||||
|  | 		} | ||||||
|  | 		if len(foundKey) == 0 { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		var value []byte | ||||||
|  | 		if i := bytes.IndexByte(foundKey, '='); i >= 0 { | ||||||
|  | 			foundKey, value = foundKey[:i], foundKey[i+1:] | ||||||
|  | 		} | ||||||
|  | 		if len(foundKey) < len(key) { | ||||||
|  | 			// Cannot possibly be key.
 | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		keyString, err := url.QueryUnescape(string(foundKey)) | ||||||
|  | 		if err != nil { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		if keyString != key { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		valueString, err := url.QueryUnescape(string(value)) | ||||||
|  | 		if err != nil { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		return valueString, true | ||||||
|  | 	} | ||||||
|  | 	return "", false | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (r *routeRegexp) matchQueryString(req *http.Request) bool { | func (r *routeRegexp) matchQueryString(req *http.Request) bool { | ||||||
| 	return r.regexp.MatchString(r.getURLQuery(req)) | 	return r.regexp.MatchString(r.getURLQuery(req)) | ||||||
| } | } | ||||||
|  | @ -288,6 +325,12 @@ 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 { | ||||||
| 		host := getHost(req) | 		host := getHost(req) | ||||||
|  | 		if v.host.wildcardHostPort { | ||||||
|  | 			// Don't be strict on the port match
 | ||||||
|  | 			if i := strings.Index(host, ":"); i != -1 { | ||||||
|  | 				host = host[:i] | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
| 		matches := v.host.regexp.FindStringSubmatchIndex(host) | 		matches := v.host.regexp.FindStringSubmatchIndex(host) | ||||||
| 		if len(matches) > 0 { | 		if len(matches) > 0 { | ||||||
| 			extractVars(host, matches, v.host.varsN, m.Vars) | 			extractVars(host, matches, v.host.varsN, m.Vars) | ||||||
|  |  | ||||||
|  | @ -74,7 +74,7 @@ func (r *Route) Match(req *http.Request, match *RouteMatch) bool { | ||||||
| 		return false | 		return false | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if match.MatchErr == ErrMethodMismatch { | 	if match.MatchErr == ErrMethodMismatch && r.handler != nil { | ||||||
| 		// We found a route which matches request method, clear MatchErr
 | 		// We found a route which matches request method, clear MatchErr
 | ||||||
| 		match.MatchErr = nil | 		match.MatchErr = nil | ||||||
| 		// Then override the mis-matched handler
 | 		// Then override the mis-matched handler
 | ||||||
|  | @ -412,11 +412,30 @@ func (r *Route) Queries(pairs ...string) *Route { | ||||||
| type schemeMatcher []string | type schemeMatcher []string | ||||||
| 
 | 
 | ||||||
| func (m schemeMatcher) Match(r *http.Request, match *RouteMatch) bool { | func (m schemeMatcher) Match(r *http.Request, match *RouteMatch) bool { | ||||||
| 	return matchInArray(m, r.URL.Scheme) | 	scheme := r.URL.Scheme | ||||||
|  | 	// https://golang.org/pkg/net/http/#Request
 | ||||||
|  | 	// "For [most] server requests, fields other than Path and RawQuery will be
 | ||||||
|  | 	// empty."
 | ||||||
|  | 	// Since we're an http muxer, the scheme is either going to be http or https
 | ||||||
|  | 	// though, so we can just set it based on the tls termination state.
 | ||||||
|  | 	if scheme == "" { | ||||||
|  | 		if r.TLS == nil { | ||||||
|  | 			scheme = "http" | ||||||
|  | 		} else { | ||||||
|  | 			scheme = "https" | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return matchInArray(m, scheme) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Schemes adds a matcher for URL schemes.
 | // Schemes adds a matcher for URL schemes.
 | ||||||
| // It accepts a sequence of schemes to be matched, e.g.: "http", "https".
 | // It accepts a sequence of schemes to be matched, e.g.: "http", "https".
 | ||||||
|  | // If the request's URL has a scheme set, it will be matched against.
 | ||||||
|  | // Generally, the URL scheme will only be set if a previous handler set it,
 | ||||||
|  | // such as the ProxyHeaders handler from gorilla/handlers.
 | ||||||
|  | // If unset, the scheme will be determined based on the request's TLS
 | ||||||
|  | // termination state.
 | ||||||
|  | // The first argument to Schemes will be used when constructing a route URL.
 | ||||||
| func (r *Route) Schemes(schemes ...string) *Route { | func (r *Route) Schemes(schemes ...string) *Route { | ||||||
| 	for k, v := range schemes { | 	for k, v := range schemes { | ||||||
| 		schemes[k] = strings.ToLower(v) | 		schemes[k] = strings.ToLower(v) | ||||||
|  | @ -493,8 +512,8 @@ func (r *Route) Subrouter() *Router { | ||||||
| // This also works for host variables:
 | // This also works for host variables:
 | ||||||
| //
 | //
 | ||||||
| //     r := mux.NewRouter()
 | //     r := mux.NewRouter()
 | ||||||
| //     r.Host("{subdomain}.domain.com").
 | //     r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
 | ||||||
| //       HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
 | //       Host("{subdomain}.domain.com").
 | ||||||
| //       Name("article")
 | //       Name("article")
 | ||||||
| //
 | //
 | ||||||
| //     // url.String() will be "http://news.domain.com/articles/technology/42"
 | //     // url.String() will be "http://news.domain.com/articles/technology/42"
 | ||||||
|  | @ -502,6 +521,13 @@ func (r *Route) Subrouter() *Router { | ||||||
| //                                      "category", "technology",
 | //                                      "category", "technology",
 | ||||||
| //                                      "id", "42")
 | //                                      "id", "42")
 | ||||||
| //
 | //
 | ||||||
|  | // The scheme of the resulting url will be the first argument that was passed to Schemes:
 | ||||||
|  | //
 | ||||||
|  | //     // url.String() will be "https://example.com"
 | ||||||
|  | //     r := mux.NewRouter()
 | ||||||
|  | //     url, err := r.Host("example.com")
 | ||||||
|  | //                  .Schemes("https", "http").URL()
 | ||||||
|  | //
 | ||||||
| // 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.
 | // conform to the corresponding patterns.
 | ||||||
| func (r *Route) URL(pairs ...string) (*url.URL, error) { | func (r *Route) URL(pairs ...string) (*url.URL, error) { | ||||||
|  | @ -635,7 +661,7 @@ func (r *Route) GetQueriesRegexp() ([]string, error) { | ||||||
| 	if r.regexp.queries == nil { | 	if r.regexp.queries == nil { | ||||||
| 		return nil, errors.New("mux: route doesn't have queries") | 		return nil, errors.New("mux: route doesn't have queries") | ||||||
| 	} | 	} | ||||||
| 	var queries []string | 	queries := make([]string, 0, len(r.regexp.queries)) | ||||||
| 	for _, query := range r.regexp.queries { | 	for _, query := range r.regexp.queries { | ||||||
| 		queries = append(queries, query.regexp.String()) | 		queries = append(queries, query.regexp.String()) | ||||||
| 	} | 	} | ||||||
|  | @ -654,7 +680,7 @@ func (r *Route) GetQueriesTemplates() ([]string, error) { | ||||||
| 	if r.regexp.queries == nil { | 	if r.regexp.queries == nil { | ||||||
| 		return nil, errors.New("mux: route doesn't have queries") | 		return nil, errors.New("mux: route doesn't have queries") | ||||||
| 	} | 	} | ||||||
| 	var queries []string | 	queries := make([]string, 0, len(r.regexp.queries)) | ||||||
| 	for _, query := range r.regexp.queries { | 	for _, query := range r.regexp.queries { | ||||||
| 		queries = append(queries, query.template) | 		queries = append(queries, query.template) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -15,5 +15,5 @@ import "net/http" | ||||||
| // can be set by making a route that captures the required variables,
 | // can be set by making a route that captures the required variables,
 | ||||||
| // starting a server and sending the request to that server.
 | // starting a server and sending the request to that server.
 | ||||||
| func SetURLVars(r *http.Request, val map[string]string) *http.Request { | func SetURLVars(r *http.Request, val map[string]string) *http.Request { | ||||||
| 	return setVars(r, val) | 	return requestWithVars(r, val) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -85,7 +85,7 @@ github.com/golang/protobuf/proto | ||||||
| github.com/gomodule/redigo/redis | github.com/gomodule/redigo/redis | ||||||
| # github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33 | # github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33 | ||||||
| github.com/gorilla/handlers | github.com/gorilla/handlers | ||||||
| # github.com/gorilla/mux v1.7.2 | # github.com/gorilla/mux v1.8.0 | ||||||
| github.com/gorilla/mux | github.com/gorilla/mux | ||||||
| # github.com/inconshreveable/mousetrap v1.0.0 | # github.com/inconshreveable/mousetrap v1.0.0 | ||||||
| github.com/inconshreveable/mousetrap | github.com/inconshreveable/mousetrap | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue