├── .editorconfig ├── .github └── workflows │ ├── issues.yml │ ├── security.yml │ ├── test.yml │ └── verify.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── doc.go ├── go.mod ├── go.sum ├── pat.go ├── pat_test.go └── vendor ├── github.com └── gorilla │ ├── context │ ├── .editorconfig │ ├── .gitignore │ ├── .golangci.yml │ ├── LICENSE │ ├── Makefile │ ├── README.md │ ├── context.go │ └── doc.go │ └── mux │ ├── .editorconfig │ ├── .gitignore │ ├── LICENSE │ ├── Makefile │ ├── README.md │ ├── doc.go │ ├── middleware.go │ ├── mux.go │ ├── regexp.go │ ├── route.go │ └── test_helpers.go └── modules.txt /.editorconfig: -------------------------------------------------------------------------------- 1 | ; https://editorconfig.org/ 2 | 3 | root = true 4 | 5 | [*] 6 | insert_final_newline = true 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | indent_style = space 10 | indent_size = 2 11 | 12 | [{Makefile,go.mod,go.sum,*.go,.gitmodules}] 13 | indent_style = tab 14 | indent_size = 4 15 | 16 | [*.md] 17 | indent_size = 4 18 | trim_trailing_whitespace = false 19 | 20 | eclint_indent_style = unset 21 | -------------------------------------------------------------------------------- /.github/workflows/issues.yml: -------------------------------------------------------------------------------- 1 | # Add all the issues created to the project. 2 | name: Add issue or pull request to Project 3 | 4 | on: 5 | issues: 6 | types: 7 | - opened 8 | pull_request_target: 9 | types: 10 | - opened 11 | - reopened 12 | 13 | jobs: 14 | add-to-project: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Add issue to project 18 | uses: actions/add-to-project@v0.5.0 19 | with: 20 | project-url: https://github.com/orgs/gorilla/projects/4 21 | github-token: ${{ secrets.ADD_TO_PROJECT_TOKEN }} 22 | -------------------------------------------------------------------------------- /.github/workflows/security.yml: -------------------------------------------------------------------------------- 1 | name: Security 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - main 9 | permissions: 10 | contents: read 11 | jobs: 12 | scan: 13 | strategy: 14 | matrix: 15 | go: ['1.20','1.21'] 16 | fail-fast: true 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Checkout Code 20 | uses: actions/checkout@v3 21 | 22 | - name: Setup Go ${{ matrix.go }} 23 | uses: actions/setup-go@v4 24 | with: 25 | go-version: ${{ matrix.go }} 26 | cache: false 27 | 28 | - name: Run GoSec 29 | uses: securego/gosec@master 30 | with: 31 | args: -exclude-dir examples ./... 32 | 33 | - name: Run GoVulnCheck 34 | uses: golang/govulncheck-action@v1 35 | with: 36 | go-version-input: ${{ matrix.go }} 37 | go-package: ./... 38 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - main 9 | permissions: 10 | contents: read 11 | jobs: 12 | unit: 13 | strategy: 14 | matrix: 15 | go: ['1.20','1.21'] 16 | os: [ubuntu-latest, macos-latest, windows-latest] 17 | fail-fast: true 18 | runs-on: ${{ matrix.os }} 19 | steps: 20 | - name: Checkout Code 21 | uses: actions/checkout@v3 22 | 23 | - name: Setup Go ${{ matrix.go }} 24 | uses: actions/setup-go@v4 25 | with: 26 | go-version: ${{ matrix.go }} 27 | cache: false 28 | 29 | - name: Run Tests 30 | run: go test -race -cover -coverprofile=coverage -covermode=atomic -v ./... 31 | 32 | - name: Upload coverage to Codecov 33 | uses: codecov/codecov-action@v3 34 | with: 35 | files: ./coverage 36 | -------------------------------------------------------------------------------- /.github/workflows/verify.yml: -------------------------------------------------------------------------------- 1 | name: Verify 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - main 9 | permissions: 10 | contents: read 11 | jobs: 12 | lint: 13 | strategy: 14 | matrix: 15 | go: ['1.20','1.21'] 16 | fail-fast: true 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Checkout Code 20 | uses: actions/checkout@v3 21 | 22 | - name: Setup Go ${{ matrix.go }} 23 | uses: actions/setup-go@v4 24 | with: 25 | go-version: ${{ matrix.go }} 26 | cache: false 27 | 28 | - name: Run GolangCI-Lint 29 | uses: golangci/golangci-lint-action@v3 30 | with: 31 | version: v1.53 32 | args: --timeout=5m 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage.coverprofile 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023 The Gorilla Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GO_LINT=$(shell which golangci-lint 2> /dev/null || echo '') 2 | GO_LINT_URI=github.com/golangci/golangci-lint/cmd/golangci-lint@latest 3 | 4 | GO_SEC=$(shell which gosec 2> /dev/null || echo '') 5 | GO_SEC_URI=github.com/securego/gosec/v2/cmd/gosec@latest 6 | 7 | GO_VULNCHECK=$(shell which govulncheck 2> /dev/null || echo '') 8 | GO_VULNCHECK_URI=golang.org/x/vuln/cmd/govulncheck@latest 9 | 10 | .PHONY: golangci-lint 11 | golangci-lint: 12 | $(if $(GO_LINT), ,go install $(GO_LINT_URI)) 13 | @echo "##### Running golangci-lint" 14 | golangci-lint run -v 15 | 16 | .PHONY: gosec 17 | gosec: 18 | $(if $(GO_SEC), ,go install $(GO_SEC_URI)) 19 | @echo "##### Running gosec" 20 | gosec ./... 21 | 22 | .PHONY: govulncheck 23 | govulncheck: 24 | $(if $(GO_VULNCHECK), ,go install $(GO_VULNCHECK_URI)) 25 | @echo "##### Running govulncheck" 26 | govulncheck ./... 27 | 28 | .PHONY: verify 29 | verify: golangci-lint gosec govulncheck 30 | 31 | .PHONY: test 32 | test: 33 | @echo "##### Running tests" 34 | go test -race -cover -coverprofile=coverage.coverprofile -covermode=atomic -v ./... 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pat 2 | 3 | ![testing](https://github.com/gorilla/pat/actions/workflows/test.yml/badge.svg) 4 | [![codecov](https://codecov.io/github/gorilla/pat/branch/main/graph/badge.svg)](https://codecov.io/github/gorilla/pat) 5 | [![godoc](https://godoc.org/github.com/gorilla/pat?status.svg)](https://godoc.org/github.com/gorilla/pat) 6 | [![sourcegraph](https://sourcegraph.com/github.com/gorilla/pat/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/pat?badge) 7 | 8 | ![Gorilla Logo](https://github.com/gorilla/.github/assets/53367916/d92caabf-98e0-473e-bfbf-ab554ba435e5) 9 | 10 | ### Install 11 | With a properly configured Go toolchain: 12 | ```sh 13 | go get github.com/gorilla/pat 14 | ``` 15 | 16 | ### Example 17 | 18 | Here's an example of a RESTful api: 19 | 20 | ```go 21 | package main 22 | 23 | import ( 24 | "log" 25 | "net/http" 26 | 27 | "github.com/gorilla/pat" 28 | ) 29 | 30 | func homeHandler(wr http.ResponseWriter, req *http.Request) { 31 | wr.WriteHeader(http.StatusOK) 32 | wr.Write([]byte("Yay! We're home, Jim!")) 33 | } 34 | 35 | func getAllTheThings(wr http.ResponseWriter, req *http.Request) { 36 | wr.WriteHeader(http.StatusOK) 37 | wr.Write([]byte("Look, Jim! Get all the things!")) 38 | } 39 | 40 | func putOneThing(wr http.ResponseWriter, req *http.Request) { 41 | wr.WriteHeader(http.StatusOK) 42 | wr.Write([]byte("Look, Jim! Put one thing!")) 43 | } 44 | 45 | func deleteOneThing(wr http.ResponseWriter, req *http.Request) { 46 | wr.WriteHeader(http.StatusOK) 47 | wr.Write([]byte("Look, Jim! Delete one thing!")) 48 | } 49 | 50 | func main() { 51 | router := pat.New() 52 | 53 | router.Get("/things", getAllTheThings) 54 | router.Put("/things/{id}", putOneThing) 55 | router.Delete("/things/{id}", deleteOneThing) 56 | router.Get("/", homeHandler) 57 | 58 | http.Handle("/", router) 59 | 60 | log.Print("Listening on 127.0.0.1:8000...") 61 | log.Fatal(http.ListenAndServe(":8000", nil)) 62 | } 63 | ``` 64 | Notice how the routes descend? That's because Pat will take the first route 65 | that matches. 66 | For your own testing, take the line ```router.Get("/", 67 | homeHandler)``` and put it above the other routes and run the example. When you 68 | try to curl any of the routes, you'll only get what the homeHandler returns. 69 | Design your routes carefully. 70 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Gorilla Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | /* 6 | Package gorilla/pat is a request router and dispatcher with a pat-like 7 | interface. It is an alternative to gorilla/mux that showcases how it can 8 | be used as a base for different API flavors. Package pat is documented at: 9 | 10 | http://godoc.org/github.com/bmizerany/pat 11 | 12 | Let's start registering a couple of URL paths and handlers: 13 | 14 | func main() { 15 | r := pat.New() 16 | r.Get("/products", ProductsHandler) 17 | r.Get("/articles", ArticlesHandler) 18 | r.Get("/", HomeHandler) 19 | http.Handle("/", r) 20 | } 21 | 22 | Here we register three routes mapping URL paths to handlers. This is 23 | equivalent to how http.HandleFunc() works: if an incoming GET request matches 24 | one of the paths, the corresponding handler is called passing 25 | (http.ResponseWriter, *http.Request) as parameters. 26 | 27 | Note: gorilla/pat matches path prefixes, so you must register the most 28 | specific paths first. 29 | 30 | Note: differently from pat, these methods accept a handler function, and not an 31 | http.Handler. We think this is shorter and more convenient. To set an 32 | http.Handler, use the Add() method. 33 | 34 | Paths can have variables. They are defined using the format {name} or 35 | {name:pattern}. If a regular expression pattern is not defined, the matched 36 | variable will be anything until the next slash. For example: 37 | 38 | r := pat.New() 39 | r.Get("/articles/{category}/{id:[0-9]+}", ArticleHandler) 40 | r.Get("/articles/{category}/", ArticlesCategoryHandler) 41 | r.Get("/products/{key}", ProductHandler) 42 | 43 | The names are used to create a map of route variables which are stored in the 44 | URL query, prefixed by a colon: 45 | 46 | category := req.URL.Query().Get(":category") 47 | 48 | As in the gorilla/mux package, other matchers can be added to the registered 49 | routes and URLs can be reversed as well. To build a URL for a route, first 50 | add a name to it: 51 | 52 | r.Get("/products/{key}", ProductHandler).Name("product") 53 | 54 | Then you can get it using the name and generate a URL: 55 | 56 | url, err := r.GetRoute("product").URL("key", "transmogrifier") 57 | 58 | ...and the result will be a url.URL with the following path: 59 | 60 | "/products/transmogrifier" 61 | 62 | Check the mux documentation for more details about URL building and extra 63 | matchers: 64 | 65 | http://gorilla-web.appspot.com/pkg/mux/ 66 | */ 67 | package pat 68 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gorilla/pat 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/gorilla/context v1.1.2 7 | github.com/gorilla/mux v1.8.1 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o= 2 | github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM= 3 | github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= 4 | github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= 5 | -------------------------------------------------------------------------------- /pat.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Gorilla Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package pat 6 | 7 | import ( 8 | "net/http" 9 | "net/url" 10 | "path" 11 | "strings" 12 | 13 | "github.com/gorilla/context" 14 | "github.com/gorilla/mux" 15 | ) 16 | 17 | // New returns a new router. 18 | func New() *Router { 19 | return &Router{ 20 | Router: *mux.NewRouter(), 21 | } 22 | } 23 | 24 | // Router is a request router that implements a pat-like API. 25 | // 26 | // pat docs: http://godoc.org/github.com/bmizerany/pat 27 | type Router struct { 28 | mux.Router 29 | } 30 | 31 | // Add registers a pattern with a handler for the given request method. 32 | func (r *Router) Add(meth, pat string, h http.Handler) *mux.Route { 33 | return r.NewRoute().PathPrefix(pat).Handler(h).Methods(meth) 34 | } 35 | 36 | // Options registers a pattern with a handler for OPTIONS requests. 37 | func (r *Router) Options(pat string, h http.HandlerFunc) *mux.Route { 38 | return r.Add("OPTIONS", pat, h) 39 | } 40 | 41 | // Delete registers a pattern with a handler for DELETE requests. 42 | func (r *Router) Delete(pat string, h http.HandlerFunc) *mux.Route { 43 | return r.Add("DELETE", pat, h) 44 | } 45 | 46 | // Head registers a pattern with a handler for HEAD requests. 47 | func (r *Router) Head(pat string, h http.HandlerFunc) *mux.Route { 48 | return r.Add("HEAD", pat, h) 49 | } 50 | 51 | // Get registers a pattern with a handler for GET requests. 52 | func (r *Router) Get(pat string, h http.HandlerFunc) *mux.Route { 53 | return r.Add("GET", pat, h) 54 | } 55 | 56 | // Post registers a pattern with a handler for POST requests. 57 | func (r *Router) Post(pat string, h http.HandlerFunc) *mux.Route { 58 | return r.Add("POST", pat, h) 59 | } 60 | 61 | // Put registers a pattern with a handler for PUT requests. 62 | func (r *Router) Put(pat string, h http.HandlerFunc) *mux.Route { 63 | return r.Add("PUT", pat, h) 64 | } 65 | 66 | // Patch registers a pattern with a handler for PATCH requests. 67 | func (r *Router) Patch(pat string, h http.HandlerFunc) *mux.Route { 68 | return r.Add("PATCH", pat, h) 69 | } 70 | 71 | // ServeHTTP dispatches the handler registered in the matched route. 72 | func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { 73 | // Clean path to canonical form and redirect. 74 | if p := cleanPath(req.URL.Path); p != req.URL.Path { 75 | w.Header().Set("Location", p) 76 | w.WriteHeader(http.StatusMovedPermanently) 77 | return 78 | } 79 | var match mux.RouteMatch 80 | var handler http.Handler 81 | if matched := r.Match(req, &match); matched { 82 | handler = match.Handler 83 | registerVars(req, match.Vars) 84 | } 85 | if handler == nil { 86 | if r.NotFoundHandler == nil { 87 | r.NotFoundHandler = http.NotFoundHandler() 88 | } 89 | handler = r.NotFoundHandler 90 | } 91 | 92 | defer context.Clear(req) 93 | 94 | handler.ServeHTTP(w, req) 95 | } 96 | 97 | // registerVars adds the matched route variables to the URL query. 98 | func registerVars(r *http.Request, vars map[string]string) { 99 | parts, i := make([]string, len(vars)), 0 100 | for key, value := range vars { 101 | parts[i] = url.QueryEscape(":"+key) + "=" + url.QueryEscape(value) 102 | i++ 103 | } 104 | q := strings.Join(parts, "&") 105 | if r.URL.RawQuery == "" { 106 | r.URL.RawQuery = q 107 | } else { 108 | r.URL.RawQuery += "&" + q 109 | } 110 | } 111 | 112 | // cleanPath returns the canonical path for p, eliminating . and .. elements. 113 | // Borrowed from the net/http package. 114 | func cleanPath(p string) string { 115 | if p == "" { 116 | return "/" 117 | } 118 | if p[0] != '/' { 119 | p = "/" + p 120 | } 121 | np := path.Clean(p) 122 | // path.Clean removes trailing slash except for root; 123 | // put the trailing slash back if necessary. 124 | if p[len(p)-1] == '/' && np != "/" { 125 | np += "/" 126 | } 127 | return np 128 | } 129 | -------------------------------------------------------------------------------- /pat_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Gorilla Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package pat 6 | 7 | import ( 8 | "net/http" 9 | "testing" 10 | 11 | "github.com/gorilla/mux" 12 | ) 13 | 14 | func myHandler(w http.ResponseWriter, r *http.Request) { 15 | } 16 | 17 | func testMatch(t *testing.T, meth, pat, path string, ok bool, vars map[string]string) { 18 | r := New() 19 | switch meth { 20 | case "OPTIONS": 21 | r.Options(pat, myHandler) 22 | case "DELETE": 23 | r.Delete(pat, myHandler) 24 | case "HEAD": 25 | r.Head(pat, myHandler) 26 | case "GET": 27 | r.Get(pat, myHandler) 28 | case "POST": 29 | r.Post(pat, myHandler) 30 | case "PUT": 31 | r.Put(pat, myHandler) 32 | case "PATCH": 33 | r.Patch(pat, myHandler) 34 | } 35 | req, _ := http.NewRequest(meth, "http://localhost"+path, nil) 36 | m := mux.RouteMatch{} 37 | if r.Match(req, &m) != ok { 38 | if ok { 39 | t.Errorf("Expected request to %q to match %q", path, pat) 40 | } else { 41 | t.Errorf("Expected request to %q to not match %q", path, pat) 42 | } 43 | } else if ok && vars != nil { 44 | registerVars(req, m.Vars) 45 | q := req.URL.Query() 46 | for k, v := range vars { 47 | if q.Get(k) != v { 48 | t.Errorf("Variable missing: %q (value: %q)", k, q.Get(k)) 49 | } 50 | } 51 | } 52 | } 53 | 54 | func TestPatMatch(t *testing.T) { 55 | testMatch(t, "OPTIONS", "/foo/{name}", "/foo/bar", true, map[string]string{":name": "bar"}) 56 | testMatch(t, "DELETE", "/foo/{name}", "/foo/bar", true, map[string]string{":name": "bar"}) 57 | testMatch(t, "HEAD", "/foo/{name}", "/foo/bar", true, map[string]string{":name": "bar"}) 58 | testMatch(t, "GET", "/foo/{name}", "/foo/bar/baz", true, map[string]string{":name": "bar"}) 59 | testMatch(t, "POST", "/foo/{name}/baz", "/foo/bar/baz", true, map[string]string{":name": "bar"}) 60 | testMatch(t, "PUT", "/foo/{name}/baz", "/foo/bar/baz/ding", true, map[string]string{":name": "bar"}) 61 | testMatch(t, "GET", "/foo/x{name}", "/foo/xbar", true, map[string]string{":name": "bar"}) 62 | testMatch(t, "GET", "/foo/x{name}", "/foo/xbar/baz", true, map[string]string{":name": "bar"}) 63 | testMatch(t, "PATCH", "/foo/x{name}", "/foo/xbar/baz", true, map[string]string{":name": "bar"}) 64 | } 65 | 66 | func TestNamedRoutes(t *testing.T) { 67 | router := New() 68 | route := router.Get("/", nil) 69 | name := "foo" 70 | route.Name(name) 71 | if route.GetError() != nil { 72 | t.Errorf("Route name assign: %v", route.GetError()) 73 | } 74 | gotRoute := router.Router.Get(name) 75 | if gotRoute == nil { 76 | t.Errorf("mux.Router.Get by name returned nil") 77 | } 78 | gotName := gotRoute.GetName() 79 | if gotName != name { 80 | t.Errorf("Unexpected route name: got=%q, want=%q", gotName, name) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/context/.editorconfig: -------------------------------------------------------------------------------- 1 | ; https://editorconfig.org/ 2 | 3 | root = true 4 | 5 | [*] 6 | insert_final_newline = true 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | indent_style = space 10 | indent_size = 2 11 | 12 | [{Makefile,go.mod,go.sum,*.go,.gitmodules}] 13 | indent_style = tab 14 | indent_size = 4 15 | 16 | [*.md] 17 | indent_size = 4 18 | trim_trailing_whitespace = false 19 | 20 | eclint_indent_style = unset 21 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/context/.gitignore: -------------------------------------------------------------------------------- 1 | coverage.coverprofile 2 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/context/.golangci.yml: -------------------------------------------------------------------------------- 1 | linters: 2 | enable: 3 | - errcheck 4 | - gosimple 5 | - govet 6 | - ineffassign 7 | - staticcheck 8 | - unused 9 | - contextcheck 10 | - goconst 11 | - gofmt 12 | - misspell 13 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/context/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2023 The Gorilla web toolkit authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/context/Makefile: -------------------------------------------------------------------------------- 1 | GO_LINT=$(shell which golangci-lint 2> /dev/null || echo '') 2 | GO_LINT_URI=github.com/golangci/golangci-lint/cmd/golangci-lint@latest 3 | 4 | GO_SEC=$(shell which gosec 2> /dev/null || echo '') 5 | GO_SEC_URI=github.com/securego/gosec/v2/cmd/gosec@latest 6 | 7 | GO_VULNCHECK=$(shell which govulncheck 2> /dev/null || echo '') 8 | GO_VULNCHECK_URI=golang.org/x/vuln/cmd/govulncheck@latest 9 | 10 | .PHONY: golangci-lint 11 | golangci-lint: ## Run golangci-lint. Example: make golangci-lint 12 | $(if $(GO_LINT), ,go install $(GO_LINT_URI)) 13 | @echo "##### Running golangci-lint #####" 14 | golangci-lint run -v 15 | 16 | .PHONY: verify 17 | verify: ## Run all verifications [golangci-lint]. Example: make verify 18 | @echo "##### Running verifications #####" 19 | $(MAKE) golangci-lint 20 | 21 | .PHONY: gosec 22 | gosec: ## Run gosec. Example: make gosec 23 | $(if $(GO_SEC), ,go install $(GO_SEC_URI)) 24 | @echo "##### Running gosec #####" 25 | gosec ./... 26 | 27 | .PHONY: govulncheck 28 | govulncheck: ## Run govulncheck. Example: make govulncheck 29 | $(if $(GO_VULNCHECK), ,go install $(GO_VULNCHECK_URI)) 30 | @echo "##### Running govulncheck #####" 31 | govulncheck ./... 32 | 33 | .PHONY: security 34 | security: ## Run all security checks [gosec, govulncheck]. Example: make security 35 | @echo "##### Running security checks #####" 36 | $(MAKE) gosec 37 | $(MAKE) govulncheck 38 | 39 | .PHONY: test-unit 40 | test-unit: ## Run unit tests. Example: make test-unit 41 | @echo "##### Running unit tests #####" 42 | go test -race -cover -coverprofile=coverage.coverprofile -covermode=atomic -v ./... 43 | 44 | .PHONY: test 45 | test: ## Run all tests [test-unit]. Example: make test 46 | @echo "##### Running tests #####" 47 | $(MAKE) test-unit 48 | 49 | .PHONY: help 50 | help: ## Print this help. Example: make help 51 | @echo "##### Printing help #####" 52 | @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) 53 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/context/README.md: -------------------------------------------------------------------------------- 1 | # gorilla/context 2 | 3 | [![License](https://img.shields.io/github/license/gorilla/.github)](https://img.shields.io/github/license/gorilla/.github) 4 | ![testing](https://github.com/gorilla/context/actions/workflows/test.yml/badge.svg) 5 | [![codecov](https://codecov.io/github/gorilla/context/branch/main/graph/badge.svg)](https://codecov.io/github/gorilla/context) 6 | [![godoc](https://godoc.org/github.com/gorilla/context?status.svg)](https://godoc.org/github.com/gorilla/context) 7 | [![sourcegraph](https://sourcegraph.com/github.com/gorilla/context/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/context?badge) 8 | [![OpenSSF Best Practices](https://bestpractices.coreinfrastructure.org/projects/7656/badge)](https://bestpractices.coreinfrastructure.org/projects/7656) 9 | 10 | ![Gorilla Logo](https://github.com/gorilla/.github/assets/53367916/d92caabf-98e0-473e-bfbf-ab554ba435e5) 11 | 12 | > ⚠⚠⚠ **Note** ⚠⚠⚠ gorilla/context, having been born well before `context.Context` existed, does not play well 13 | > with the shallow copying of the request that [`http.Request.WithContext`](https://golang.org/pkg/net/http/#Request.WithContext) (added to net/http Go 1.7 onwards) performs. 14 | > 15 | > Using gorilla/context may lead to memory leaks under those conditions, as the pointers to each `http.Request` become "islanded" and will not be cleaned up when the response is sent. 16 | > 17 | > You should use the `http.Request.Context()` feature in Go 1.7. 18 | 19 | gorilla/context is a general purpose registry for global request variables. 20 | 21 | * It stores a `map[*http.Request]map[interface{}]interface{}` as a global singleton, and thus tracks variables by their HTTP request. 22 | 23 | 24 | ### License 25 | 26 | See the LICENSE file for details. 27 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/context/context.go: -------------------------------------------------------------------------------- 1 | package context 2 | 3 | import ( 4 | "net/http" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | var ( 10 | mutex sync.RWMutex 11 | data = make(map[*http.Request]map[interface{}]interface{}) 12 | datat = make(map[*http.Request]int64) 13 | ) 14 | 15 | // Set stores a value for a given key in a given request. 16 | func Set(r *http.Request, key, val interface{}) { 17 | mutex.Lock() 18 | if data[r] == nil { 19 | data[r] = make(map[interface{}]interface{}) 20 | datat[r] = time.Now().Unix() 21 | } 22 | data[r][key] = val 23 | mutex.Unlock() 24 | } 25 | 26 | // Get returns a value stored for a given key in a given request. 27 | func Get(r *http.Request, key interface{}) interface{} { 28 | mutex.RLock() 29 | if ctx := data[r]; ctx != nil { 30 | value := ctx[key] 31 | mutex.RUnlock() 32 | return value 33 | } 34 | mutex.RUnlock() 35 | return nil 36 | } 37 | 38 | // GetOk returns stored value and presence state like multi-value return of map access. 39 | func GetOk(r *http.Request, key interface{}) (interface{}, bool) { 40 | mutex.RLock() 41 | if _, ok := data[r]; ok { 42 | value, ok := data[r][key] 43 | mutex.RUnlock() 44 | return value, ok 45 | } 46 | mutex.RUnlock() 47 | return nil, false 48 | } 49 | 50 | // GetAll returns all stored values for the request as a map. Nil is returned for invalid requests. 51 | func GetAll(r *http.Request) map[interface{}]interface{} { 52 | mutex.RLock() 53 | if context, ok := data[r]; ok { 54 | result := make(map[interface{}]interface{}, len(context)) 55 | for k, v := range context { 56 | result[k] = v 57 | } 58 | mutex.RUnlock() 59 | return result 60 | } 61 | mutex.RUnlock() 62 | return nil 63 | } 64 | 65 | // GetAllOk returns all stored values for the request as a map and a boolean value that indicates if 66 | // the request was registered. 67 | func GetAllOk(r *http.Request) (map[interface{}]interface{}, bool) { 68 | mutex.RLock() 69 | context, ok := data[r] 70 | result := make(map[interface{}]interface{}, len(context)) 71 | for k, v := range context { 72 | result[k] = v 73 | } 74 | mutex.RUnlock() 75 | return result, ok 76 | } 77 | 78 | // Delete removes a value stored for a given key in a given request. 79 | func Delete(r *http.Request, key interface{}) { 80 | mutex.Lock() 81 | if data[r] != nil { 82 | delete(data[r], key) 83 | } 84 | mutex.Unlock() 85 | } 86 | 87 | // Clear removes all values stored for a given request. 88 | // 89 | // This is usually called by a handler wrapper to clean up request 90 | // variables at the end of a request lifetime. See ClearHandler(). 91 | func Clear(r *http.Request) { 92 | mutex.Lock() 93 | clear(r) 94 | mutex.Unlock() 95 | } 96 | 97 | // clear is Clear without the lock. 98 | func clear(r *http.Request) { 99 | delete(data, r) 100 | delete(datat, r) 101 | } 102 | 103 | // Purge removes request data stored for longer than maxAge, in seconds. 104 | // It returns the amount of requests removed. 105 | // 106 | // If maxAge <= 0, all request data is removed. 107 | // 108 | // This is only used for sanity check: in case context cleaning was not 109 | // properly set some request data can be kept forever, consuming an increasing 110 | // amount of memory. In case this is detected, Purge() must be called 111 | // periodically until the problem is fixed. 112 | func Purge(maxAge int) int { 113 | mutex.Lock() 114 | count := 0 115 | if maxAge <= 0 { 116 | count = len(data) 117 | data = make(map[*http.Request]map[interface{}]interface{}) 118 | datat = make(map[*http.Request]int64) 119 | } else { 120 | min := time.Now().Unix() - int64(maxAge) 121 | for r := range data { 122 | if datat[r] < min { 123 | clear(r) 124 | count++ 125 | } 126 | } 127 | } 128 | mutex.Unlock() 129 | return count 130 | } 131 | 132 | // ClearHandler wraps an http.Handler and clears request values at the end 133 | // of a request lifetime. 134 | func ClearHandler(h http.Handler) http.Handler { 135 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 136 | defer Clear(r) 137 | h.ServeHTTP(w, r) 138 | }) 139 | } 140 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/context/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Gorilla Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | /* 6 | Package context stores values shared during a request lifetime. 7 | 8 | Note: gorilla/context, having been born well before `context.Context` existed, 9 | does not play well > with the shallow copying of the request that 10 | [`http.Request.WithContext`](https://golang.org/pkg/net/http/#Request.WithContext) 11 | (added to net/http Go 1.7 onwards) performs. You should either use *just* 12 | gorilla/context, or moving forward, the new `http.Request.Context()`. 13 | 14 | For example, a router can set variables extracted from the URL and later 15 | application handlers can access those values, or it can be used to store 16 | sessions values to be saved at the end of a request. There are several 17 | others common uses. 18 | 19 | The idea was posted by Brad Fitzpatrick to the go-nuts mailing list: 20 | 21 | http://groups.google.com/group/golang-nuts/msg/e2d679d303aa5d53 22 | 23 | Here's the basic usage: first define the keys that you will need. The key 24 | type is interface{} so a key can be of any type that supports equality. 25 | Here we define a key using a custom int type to avoid name collisions: 26 | 27 | package foo 28 | 29 | import ( 30 | "github.com/gorilla/context" 31 | ) 32 | 33 | type key int 34 | 35 | const MyKey key = 0 36 | 37 | Then set a variable. Variables are bound to an http.Request object, so you 38 | need a request instance to set a value: 39 | 40 | context.Set(r, MyKey, "bar") 41 | 42 | The application can later access the variable using the same key you provided: 43 | 44 | func MyHandler(w http.ResponseWriter, r *http.Request) { 45 | // val is "bar". 46 | val := context.Get(r, foo.MyKey) 47 | 48 | // returns ("bar", true) 49 | val, ok := context.GetOk(r, foo.MyKey) 50 | // ... 51 | } 52 | 53 | And that's all about the basic usage. We discuss some other ideas below. 54 | 55 | Any type can be stored in the context. To enforce a given type, make the key 56 | private and wrap Get() and Set() to accept and return values of a specific 57 | type: 58 | 59 | type key int 60 | 61 | const mykey key = 0 62 | 63 | // GetMyKey returns a value for this package from the request values. 64 | func GetMyKey(r *http.Request) SomeType { 65 | if rv := context.Get(r, mykey); rv != nil { 66 | return rv.(SomeType) 67 | } 68 | return nil 69 | } 70 | 71 | // SetMyKey sets a value for this package in the request values. 72 | func SetMyKey(r *http.Request, val SomeType) { 73 | context.Set(r, mykey, val) 74 | } 75 | 76 | Variables must be cleared at the end of a request, to remove all values 77 | that were stored. This can be done in an http.Handler, after a request was 78 | served. Just call Clear() passing the request: 79 | 80 | context.Clear(r) 81 | 82 | ...or use ClearHandler(), which conveniently wraps an http.Handler to clear 83 | variables at the end of a request lifetime. 84 | 85 | The Routers from the packages gorilla/mux and gorilla/pat call Clear() 86 | so if you are using either of them you don't need to clear the context manually. 87 | */ 88 | package context 89 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/mux/.editorconfig: -------------------------------------------------------------------------------- 1 | ; https://editorconfig.org/ 2 | 3 | root = true 4 | 5 | [*] 6 | insert_final_newline = true 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | indent_style = space 10 | indent_size = 2 11 | 12 | [{Makefile,go.mod,go.sum,*.go,.gitmodules}] 13 | indent_style = tab 14 | indent_size = 4 15 | 16 | [*.md] 17 | indent_size = 4 18 | trim_trailing_whitespace = false 19 | 20 | eclint_indent_style = unset -------------------------------------------------------------------------------- /vendor/github.com/gorilla/mux/.gitignore: -------------------------------------------------------------------------------- 1 | coverage.coverprofile 2 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/mux/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023 The Gorilla Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/mux/Makefile: -------------------------------------------------------------------------------- 1 | GO_LINT=$(shell which golangci-lint 2> /dev/null || echo '') 2 | GO_LINT_URI=github.com/golangci/golangci-lint/cmd/golangci-lint@latest 3 | 4 | GO_SEC=$(shell which gosec 2> /dev/null || echo '') 5 | GO_SEC_URI=github.com/securego/gosec/v2/cmd/gosec@latest 6 | 7 | GO_VULNCHECK=$(shell which govulncheck 2> /dev/null || echo '') 8 | GO_VULNCHECK_URI=golang.org/x/vuln/cmd/govulncheck@latest 9 | 10 | .PHONY: golangci-lint 11 | golangci-lint: 12 | $(if $(GO_LINT), ,go install $(GO_LINT_URI)) 13 | @echo "##### Running golangci-lint" 14 | golangci-lint run -v 15 | 16 | .PHONY: gosec 17 | gosec: 18 | $(if $(GO_SEC), ,go install $(GO_SEC_URI)) 19 | @echo "##### Running gosec" 20 | gosec ./... 21 | 22 | .PHONY: govulncheck 23 | govulncheck: 24 | $(if $(GO_VULNCHECK), ,go install $(GO_VULNCHECK_URI)) 25 | @echo "##### Running govulncheck" 26 | govulncheck ./... 27 | 28 | .PHONY: verify 29 | verify: golangci-lint gosec govulncheck 30 | 31 | .PHONY: test 32 | test: 33 | @echo "##### Running tests" 34 | go test -race -cover -coverprofile=coverage.coverprofile -covermode=atomic -v ./... -------------------------------------------------------------------------------- /vendor/github.com/gorilla/mux/README.md: -------------------------------------------------------------------------------- 1 | # gorilla/mux 2 | 3 | ![testing](https://github.com/gorilla/mux/actions/workflows/test.yml/badge.svg) 4 | [![codecov](https://codecov.io/github/gorilla/mux/branch/main/graph/badge.svg)](https://codecov.io/github/gorilla/mux) 5 | [![godoc](https://godoc.org/github.com/gorilla/mux?status.svg)](https://godoc.org/github.com/gorilla/mux) 6 | [![sourcegraph](https://sourcegraph.com/github.com/gorilla/mux/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/mux?badge) 7 | 8 | 9 | ![Gorilla Logo](https://github.com/gorilla/.github/assets/53367916/d92caabf-98e0-473e-bfbf-ab554ba435e5) 10 | 11 | Package `gorilla/mux` implements a request router and dispatcher for matching incoming requests to 12 | their respective handler. 13 | 14 | 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: 15 | 16 | * It implements the `http.Handler` interface so it is compatible with the standard `http.ServeMux`. 17 | * Requests can be matched based on URL host, path, path prefix, schemes, header and query values, HTTP methods or using custom matchers. 18 | * URL hosts, paths and query values can have variables with an optional regular expression. 19 | * Registered URLs can be built, or "reversed", which helps maintaining references to resources. 20 | * 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. 21 | 22 | --- 23 | 24 | * [Install](#install) 25 | * [Examples](#examples) 26 | * [Matching Routes](#matching-routes) 27 | * [Static Files](#static-files) 28 | * [Serving Single Page Applications](#serving-single-page-applications) (e.g. React, Vue, Ember.js, etc.) 29 | * [Registered URLs](#registered-urls) 30 | * [Walking Routes](#walking-routes) 31 | * [Graceful Shutdown](#graceful-shutdown) 32 | * [Middleware](#middleware) 33 | * [Handling CORS Requests](#handling-cors-requests) 34 | * [Testing Handlers](#testing-handlers) 35 | * [Full Example](#full-example) 36 | 37 | --- 38 | 39 | ## Install 40 | 41 | With a [correctly configured](https://golang.org/doc/install#testing) Go toolchain: 42 | 43 | ```sh 44 | go get -u github.com/gorilla/mux 45 | ``` 46 | 47 | ## Examples 48 | 49 | Let's start registering a couple of URL paths and handlers: 50 | 51 | ```go 52 | func main() { 53 | r := mux.NewRouter() 54 | r.HandleFunc("/", HomeHandler) 55 | r.HandleFunc("/products", ProductsHandler) 56 | r.HandleFunc("/articles", ArticlesHandler) 57 | http.Handle("/", r) 58 | } 59 | ``` 60 | 61 | 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. 62 | 63 | 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: 64 | 65 | ```go 66 | r := mux.NewRouter() 67 | r.HandleFunc("/products/{key}", ProductHandler) 68 | r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler) 69 | r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler) 70 | ``` 71 | 72 | The names are used to create a map of route variables which can be retrieved calling `mux.Vars()`: 73 | 74 | ```go 75 | func ArticlesCategoryHandler(w http.ResponseWriter, r *http.Request) { 76 | vars := mux.Vars(r) 77 | w.WriteHeader(http.StatusOK) 78 | fmt.Fprintf(w, "Category: %v\n", vars["category"]) 79 | } 80 | ``` 81 | 82 | And this is all you need to know about the basic usage. More advanced options are explained below. 83 | 84 | ### Matching Routes 85 | 86 | Routes can also be restricted to a domain or subdomain. Just define a host pattern to be matched. They can also have variables: 87 | 88 | ```go 89 | r := mux.NewRouter() 90 | // Only matches if domain is "www.example.com". 91 | r.Host("www.example.com") 92 | // Matches a dynamic subdomain. 93 | r.Host("{subdomain:[a-z]+}.example.com") 94 | ``` 95 | 96 | There are several other matchers that can be added. To match path prefixes: 97 | 98 | ```go 99 | r.PathPrefix("/products/") 100 | ``` 101 | 102 | ...or HTTP methods: 103 | 104 | ```go 105 | r.Methods("GET", "POST") 106 | ``` 107 | 108 | ...or URL schemes: 109 | 110 | ```go 111 | r.Schemes("https") 112 | ``` 113 | 114 | ...or header values: 115 | 116 | ```go 117 | r.Headers("X-Requested-With", "XMLHttpRequest") 118 | ``` 119 | 120 | ...or query values: 121 | 122 | ```go 123 | r.Queries("key", "value") 124 | ``` 125 | 126 | ...or to use a custom matcher function: 127 | 128 | ```go 129 | r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool { 130 | return r.ProtoMajor == 0 131 | }) 132 | ``` 133 | 134 | ...and finally, it is possible to combine several matchers in a single route: 135 | 136 | ```go 137 | r.HandleFunc("/products", ProductsHandler). 138 | Host("www.example.com"). 139 | Methods("GET"). 140 | Schemes("http") 141 | ``` 142 | 143 | Routes are tested in the order they were added to the router. If two routes match, the first one wins: 144 | 145 | ```go 146 | r := mux.NewRouter() 147 | r.HandleFunc("/specific", specificHandler) 148 | r.PathPrefix("/").Handler(catchAllHandler) 149 | ``` 150 | 151 | 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". 152 | 153 | 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: 154 | 155 | ```go 156 | r := mux.NewRouter() 157 | s := r.Host("www.example.com").Subrouter() 158 | ``` 159 | 160 | Then register routes in the subrouter: 161 | 162 | ```go 163 | s.HandleFunc("/products/", ProductsHandler) 164 | s.HandleFunc("/products/{key}", ProductHandler) 165 | s.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler) 166 | ``` 167 | 168 | 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. 169 | 170 | 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. 171 | 172 | There's one more thing about subroutes. When a subrouter has a path prefix, the inner routes use it as base for their paths: 173 | 174 | ```go 175 | r := mux.NewRouter() 176 | s := r.PathPrefix("/products").Subrouter() 177 | // "/products/" 178 | s.HandleFunc("/", ProductsHandler) 179 | // "/products/{key}/" 180 | s.HandleFunc("/{key}/", ProductHandler) 181 | // "/products/{key}/details" 182 | s.HandleFunc("/{key}/details", ProductDetailsHandler) 183 | ``` 184 | 185 | 186 | ### Static Files 187 | 188 | Note that the path provided to `PathPrefix()` represents a "wildcard": calling 189 | `PathPrefix("/static/").Handler(...)` means that the handler will be passed any 190 | request that matches "/static/\*". This makes it easy to serve static files with mux: 191 | 192 | ```go 193 | func main() { 194 | var dir string 195 | 196 | flag.StringVar(&dir, "dir", ".", "the directory to serve files from. Defaults to the current dir") 197 | flag.Parse() 198 | r := mux.NewRouter() 199 | 200 | // This will serve files under http://localhost:8000/static/ 201 | r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(dir)))) 202 | 203 | srv := &http.Server{ 204 | Handler: r, 205 | Addr: "127.0.0.1:8000", 206 | // Good practice: enforce timeouts for servers you create! 207 | WriteTimeout: 15 * time.Second, 208 | ReadTimeout: 15 * time.Second, 209 | } 210 | 211 | log.Fatal(srv.ListenAndServe()) 212 | } 213 | ``` 214 | 215 | ### Serving Single Page Applications 216 | 217 | Most of the time it makes sense to serve your SPA on a separate web server from your API, 218 | but sometimes it's desirable to serve them both from one place. It's possible to write a simple 219 | handler for serving your SPA (for use with React Router's [BrowserRouter](https://reacttraining.com/react-router/web/api/BrowserRouter) for example), and leverage 220 | mux's powerful routing for your API endpoints. 221 | 222 | ```go 223 | package main 224 | 225 | import ( 226 | "encoding/json" 227 | "log" 228 | "net/http" 229 | "os" 230 | "path/filepath" 231 | "time" 232 | 233 | "github.com/gorilla/mux" 234 | ) 235 | 236 | // spaHandler implements the http.Handler interface, so we can use it 237 | // to respond to HTTP requests. The path to the static directory and 238 | // path to the index file within that static directory are used to 239 | // serve the SPA in the given static directory. 240 | type spaHandler struct { 241 | staticPath string 242 | indexPath string 243 | } 244 | 245 | // ServeHTTP inspects the URL path to locate a file within the static dir 246 | // on the SPA handler. If a file is found, it will be served. If not, the 247 | // file located at the index path on the SPA handler will be served. This 248 | // is suitable behavior for serving an SPA (single page application). 249 | func (h spaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 250 | // Join internally call path.Clean to prevent directory traversal 251 | path := filepath.Join(h.staticPath, r.URL.Path) 252 | 253 | // check whether a file exists or is a directory at the given path 254 | fi, err := os.Stat(path) 255 | if os.IsNotExist(err) || fi.IsDir() { 256 | // file does not exist or path is a directory, serve index.html 257 | http.ServeFile(w, r, filepath.Join(h.staticPath, h.indexPath)) 258 | return 259 | } 260 | 261 | if err != nil { 262 | // if we got an error (that wasn't that the file doesn't exist) stating the 263 | // file, return a 500 internal server error and stop 264 | http.Error(w, err.Error(), http.StatusInternalServerError) 265 | return 266 | } 267 | 268 | // otherwise, use http.FileServer to serve the static file 269 | http.FileServer(http.Dir(h.staticPath)).ServeHTTP(w, r) 270 | } 271 | 272 | func main() { 273 | router := mux.NewRouter() 274 | 275 | router.HandleFunc("/api/health", func(w http.ResponseWriter, r *http.Request) { 276 | // an example API handler 277 | json.NewEncoder(w).Encode(map[string]bool{"ok": true}) 278 | }) 279 | 280 | spa := spaHandler{staticPath: "build", indexPath: "index.html"} 281 | router.PathPrefix("/").Handler(spa) 282 | 283 | srv := &http.Server{ 284 | Handler: router, 285 | Addr: "127.0.0.1:8000", 286 | // Good practice: enforce timeouts for servers you create! 287 | WriteTimeout: 15 * time.Second, 288 | ReadTimeout: 15 * time.Second, 289 | } 290 | 291 | log.Fatal(srv.ListenAndServe()) 292 | } 293 | ``` 294 | 295 | ### Registered URLs 296 | 297 | Now let's see how to build registered URLs. 298 | 299 | 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: 300 | 301 | ```go 302 | r := mux.NewRouter() 303 | r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler). 304 | Name("article") 305 | ``` 306 | 307 | 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: 308 | 309 | ```go 310 | url, err := r.Get("article").URL("category", "technology", "id", "42") 311 | ``` 312 | 313 | ...and the result will be a `url.URL` with the following path: 314 | 315 | ``` 316 | "/articles/technology/42" 317 | ``` 318 | 319 | This also works for host and query value variables: 320 | 321 | ```go 322 | r := mux.NewRouter() 323 | r.Host("{subdomain}.example.com"). 324 | Path("/articles/{category}/{id:[0-9]+}"). 325 | Queries("filter", "{filter}"). 326 | HandlerFunc(ArticleHandler). 327 | Name("article") 328 | 329 | // url.String() will be "http://news.example.com/articles/technology/42?filter=gorilla" 330 | url, err := r.Get("article").URL("subdomain", "news", 331 | "category", "technology", 332 | "id", "42", 333 | "filter", "gorilla") 334 | ``` 335 | 336 | 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. 337 | 338 | Regex support also exists for matching Headers within a route. For example, we could do: 339 | 340 | ```go 341 | r.HeadersRegexp("Content-Type", "application/(text|json)") 342 | ``` 343 | 344 | ...and the route will match both requests with a Content-Type of `application/json` as well as `application/text` 345 | 346 | 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: 347 | 348 | ```go 349 | // "http://news.example.com/" 350 | host, err := r.Get("article").URLHost("subdomain", "news") 351 | 352 | // "/articles/technology/42" 353 | path, err := r.Get("article").URLPath("category", "technology", "id", "42") 354 | ``` 355 | 356 | And if you use subrouters, host and path defined separately can be built as well: 357 | 358 | ```go 359 | r := mux.NewRouter() 360 | s := r.Host("{subdomain}.example.com").Subrouter() 361 | s.Path("/articles/{category}/{id:[0-9]+}"). 362 | HandlerFunc(ArticleHandler). 363 | Name("article") 364 | 365 | // "http://news.example.com/articles/technology/42" 366 | url, err := r.Get("article").URL("subdomain", "news", 367 | "category", "technology", 368 | "id", "42") 369 | ``` 370 | 371 | To find all the required variables for a given route when calling `URL()`, the method `GetVarNames()` is available: 372 | ```go 373 | r := mux.NewRouter() 374 | r.Host("{domain}"). 375 | Path("/{group}/{item_id}"). 376 | Queries("some_data1", "{some_data1}"). 377 | Queries("some_data2", "{some_data2}"). 378 | Name("article") 379 | 380 | // Will print [domain group item_id some_data1 some_data2] 381 | fmt.Println(r.Get("article").GetVarNames()) 382 | 383 | ``` 384 | ### Walking Routes 385 | 386 | The `Walk` function on `mux.Router` can be used to visit all of the routes that are registered on a router. For example, 387 | the following prints all of the registered routes: 388 | 389 | ```go 390 | package main 391 | 392 | import ( 393 | "fmt" 394 | "net/http" 395 | "strings" 396 | 397 | "github.com/gorilla/mux" 398 | ) 399 | 400 | func handler(w http.ResponseWriter, r *http.Request) { 401 | return 402 | } 403 | 404 | func main() { 405 | r := mux.NewRouter() 406 | r.HandleFunc("/", handler) 407 | r.HandleFunc("/products", handler).Methods("POST") 408 | r.HandleFunc("/articles", handler).Methods("GET") 409 | r.HandleFunc("/articles/{id}", handler).Methods("GET", "PUT") 410 | r.HandleFunc("/authors", handler).Queries("surname", "{surname}") 411 | err := r.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error { 412 | pathTemplate, err := route.GetPathTemplate() 413 | if err == nil { 414 | fmt.Println("ROUTE:", pathTemplate) 415 | } 416 | pathRegexp, err := route.GetPathRegexp() 417 | if err == nil { 418 | fmt.Println("Path regexp:", pathRegexp) 419 | } 420 | queriesTemplates, err := route.GetQueriesTemplates() 421 | if err == nil { 422 | fmt.Println("Queries templates:", strings.Join(queriesTemplates, ",")) 423 | } 424 | queriesRegexps, err := route.GetQueriesRegexp() 425 | if err == nil { 426 | fmt.Println("Queries regexps:", strings.Join(queriesRegexps, ",")) 427 | } 428 | methods, err := route.GetMethods() 429 | if err == nil { 430 | fmt.Println("Methods:", strings.Join(methods, ",")) 431 | } 432 | fmt.Println() 433 | return nil 434 | }) 435 | 436 | if err != nil { 437 | fmt.Println(err) 438 | } 439 | 440 | http.Handle("/", r) 441 | } 442 | ``` 443 | 444 | ### Graceful Shutdown 445 | 446 | Go 1.8 introduced the ability to [gracefully shutdown](https://golang.org/doc/go1.8#http_shutdown) a `*http.Server`. Here's how to do that alongside `mux`: 447 | 448 | ```go 449 | package main 450 | 451 | import ( 452 | "context" 453 | "flag" 454 | "log" 455 | "net/http" 456 | "os" 457 | "os/signal" 458 | "time" 459 | 460 | "github.com/gorilla/mux" 461 | ) 462 | 463 | func main() { 464 | var wait time.Duration 465 | flag.DurationVar(&wait, "graceful-timeout", time.Second * 15, "the duration for which the server gracefully wait for existing connections to finish - e.g. 15s or 1m") 466 | flag.Parse() 467 | 468 | r := mux.NewRouter() 469 | // Add your routes as needed 470 | 471 | srv := &http.Server{ 472 | Addr: "0.0.0.0:8080", 473 | // Good practice to set timeouts to avoid Slowloris attacks. 474 | WriteTimeout: time.Second * 15, 475 | ReadTimeout: time.Second * 15, 476 | IdleTimeout: time.Second * 60, 477 | Handler: r, // Pass our instance of gorilla/mux in. 478 | } 479 | 480 | // Run our server in a goroutine so that it doesn't block. 481 | go func() { 482 | if err := srv.ListenAndServe(); err != nil { 483 | log.Println(err) 484 | } 485 | }() 486 | 487 | c := make(chan os.Signal, 1) 488 | // We'll accept graceful shutdowns when quit via SIGINT (Ctrl+C) 489 | // SIGKILL, SIGQUIT or SIGTERM (Ctrl+/) will not be caught. 490 | signal.Notify(c, os.Interrupt) 491 | 492 | // Block until we receive our signal. 493 | <-c 494 | 495 | // Create a deadline to wait for. 496 | ctx, cancel := context.WithTimeout(context.Background(), wait) 497 | defer cancel() 498 | // Doesn't block if no connections, but will otherwise wait 499 | // until the timeout deadline. 500 | srv.Shutdown(ctx) 501 | // Optionally, you could run srv.Shutdown in a goroutine and block on 502 | // <-ctx.Done() if your application should wait for other services 503 | // to finalize based on context cancellation. 504 | log.Println("shutting down") 505 | os.Exit(0) 506 | } 507 | ``` 508 | 509 | ### Middleware 510 | 511 | Mux supports the addition of middlewares to a [Router](https://godoc.org/github.com/gorilla/mux#Router), which are executed in the order they are added if a match is found, including its subrouters. 512 | Middlewares are (typically) small pieces of code which take one request, do something with it, and pass it down to another middleware or the final handler. Some common use cases for middleware are request logging, header manipulation, or `ResponseWriter` hijacking. 513 | 514 | Mux middlewares are defined using the de facto standard type: 515 | 516 | ```go 517 | type MiddlewareFunc func(http.Handler) http.Handler 518 | ``` 519 | 520 | Typically, the returned handler is a closure which does something with the http.ResponseWriter and http.Request passed to it, and then calls the handler passed as parameter to the MiddlewareFunc. This takes advantage of closures being able access variables from the context where they are created, while retaining the signature enforced by the receivers. 521 | 522 | A very basic middleware which logs the URI of the request being handled could be written as: 523 | 524 | ```go 525 | func loggingMiddleware(next http.Handler) http.Handler { 526 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 527 | // Do stuff here 528 | log.Println(r.RequestURI) 529 | // Call the next handler, which can be another middleware in the chain, or the final handler. 530 | next.ServeHTTP(w, r) 531 | }) 532 | } 533 | ``` 534 | 535 | Middlewares can be added to a router using `Router.Use()`: 536 | 537 | ```go 538 | r := mux.NewRouter() 539 | r.HandleFunc("/", handler) 540 | r.Use(loggingMiddleware) 541 | ``` 542 | 543 | A more complex authentication middleware, which maps session token to users, could be written as: 544 | 545 | ```go 546 | // Define our struct 547 | type authenticationMiddleware struct { 548 | tokenUsers map[string]string 549 | } 550 | 551 | // Initialize it somewhere 552 | func (amw *authenticationMiddleware) Populate() { 553 | amw.tokenUsers["00000000"] = "user0" 554 | amw.tokenUsers["aaaaaaaa"] = "userA" 555 | amw.tokenUsers["05f717e5"] = "randomUser" 556 | amw.tokenUsers["deadbeef"] = "user0" 557 | } 558 | 559 | // Middleware function, which will be called for each request 560 | func (amw *authenticationMiddleware) Middleware(next http.Handler) http.Handler { 561 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 562 | token := r.Header.Get("X-Session-Token") 563 | 564 | if user, found := amw.tokenUsers[token]; found { 565 | // We found the token in our map 566 | log.Printf("Authenticated user %s\n", user) 567 | // Pass down the request to the next middleware (or final handler) 568 | next.ServeHTTP(w, r) 569 | } else { 570 | // Write an error and stop the handler chain 571 | http.Error(w, "Forbidden", http.StatusForbidden) 572 | } 573 | }) 574 | } 575 | ``` 576 | 577 | ```go 578 | r := mux.NewRouter() 579 | r.HandleFunc("/", handler) 580 | 581 | amw := authenticationMiddleware{tokenUsers: make(map[string]string)} 582 | amw.Populate() 583 | 584 | r.Use(amw.Middleware) 585 | ``` 586 | 587 | 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. 588 | 589 | ### Handling CORS Requests 590 | 591 | [CORSMethodMiddleware](https://godoc.org/github.com/gorilla/mux#CORSMethodMiddleware) intends to make it easier to strictly set the `Access-Control-Allow-Methods` response header. 592 | 593 | * You will still need to use your own CORS handler to set the other CORS headers such as `Access-Control-Allow-Origin` 594 | * 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 595 | * If you do not specify any methods, then: 596 | > _Important_: there must be an `OPTIONS` method matcher for the middleware to set the headers. 597 | 598 | Here is an example of using `CORSMethodMiddleware` along with a custom `OPTIONS` handler to set all the required CORS headers: 599 | 600 | ```go 601 | package main 602 | 603 | import ( 604 | "net/http" 605 | "github.com/gorilla/mux" 606 | ) 607 | 608 | func main() { 609 | r := mux.NewRouter() 610 | 611 | // IMPORTANT: you must specify an OPTIONS method matcher for the middleware to set CORS headers 612 | r.HandleFunc("/foo", fooHandler).Methods(http.MethodGet, http.MethodPut, http.MethodPatch, http.MethodOptions) 613 | r.Use(mux.CORSMethodMiddleware(r)) 614 | 615 | http.ListenAndServe(":8080", r) 616 | } 617 | 618 | func fooHandler(w http.ResponseWriter, r *http.Request) { 619 | w.Header().Set("Access-Control-Allow-Origin", "*") 620 | if r.Method == http.MethodOptions { 621 | return 622 | } 623 | 624 | w.Write([]byte("foo")) 625 | } 626 | ``` 627 | 628 | And an request to `/foo` using something like: 629 | 630 | ```bash 631 | curl localhost:8080/foo -v 632 | ``` 633 | 634 | Would look like: 635 | 636 | ```bash 637 | * Trying ::1... 638 | * TCP_NODELAY set 639 | * Connected to localhost (::1) port 8080 (#0) 640 | > GET /foo HTTP/1.1 641 | > Host: localhost:8080 642 | > User-Agent: curl/7.59.0 643 | > Accept: */* 644 | > 645 | < HTTP/1.1 200 OK 646 | < Access-Control-Allow-Methods: GET,PUT,PATCH,OPTIONS 647 | < Access-Control-Allow-Origin: * 648 | < Date: Fri, 28 Jun 2019 20:13:30 GMT 649 | < Content-Length: 3 650 | < Content-Type: text/plain; charset=utf-8 651 | < 652 | * Connection #0 to host localhost left intact 653 | foo 654 | ``` 655 | 656 | ### Testing Handlers 657 | 658 | 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_. 659 | 660 | First, our simple HTTP handler: 661 | 662 | ```go 663 | // endpoints.go 664 | package main 665 | 666 | func HealthCheckHandler(w http.ResponseWriter, r *http.Request) { 667 | // A very simple health check. 668 | w.Header().Set("Content-Type", "application/json") 669 | w.WriteHeader(http.StatusOK) 670 | 671 | // In the future we could report back on the status of our DB, or our cache 672 | // (e.g. Redis) by performing a simple PING, and include them in the response. 673 | io.WriteString(w, `{"alive": true}`) 674 | } 675 | 676 | func main() { 677 | r := mux.NewRouter() 678 | r.HandleFunc("/health", HealthCheckHandler) 679 | 680 | log.Fatal(http.ListenAndServe("localhost:8080", r)) 681 | } 682 | ``` 683 | 684 | Our test code: 685 | 686 | ```go 687 | // endpoints_test.go 688 | package main 689 | 690 | import ( 691 | "net/http" 692 | "net/http/httptest" 693 | "testing" 694 | ) 695 | 696 | func TestHealthCheckHandler(t *testing.T) { 697 | // Create a request to pass to our handler. We don't have any query parameters for now, so we'll 698 | // pass 'nil' as the third parameter. 699 | req, err := http.NewRequest("GET", "/health", nil) 700 | if err != nil { 701 | t.Fatal(err) 702 | } 703 | 704 | // We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response. 705 | rr := httptest.NewRecorder() 706 | handler := http.HandlerFunc(HealthCheckHandler) 707 | 708 | // Our handlers satisfy http.Handler, so we can call their ServeHTTP method 709 | // directly and pass in our Request and ResponseRecorder. 710 | handler.ServeHTTP(rr, req) 711 | 712 | // Check the status code is what we expect. 713 | if status := rr.Code; status != http.StatusOK { 714 | t.Errorf("handler returned wrong status code: got %v want %v", 715 | status, http.StatusOK) 716 | } 717 | 718 | // Check the response body is what we expect. 719 | expected := `{"alive": true}` 720 | if rr.Body.String() != expected { 721 | t.Errorf("handler returned unexpected body: got %v want %v", 722 | rr.Body.String(), expected) 723 | } 724 | } 725 | ``` 726 | 727 | In the case that our routes have [variables](#examples), we can pass those in the request. We could write 728 | [table-driven tests](https://dave.cheney.net/2013/06/09/writing-table-driven-tests-in-go) to test multiple 729 | possible route variables as needed. 730 | 731 | ```go 732 | // endpoints.go 733 | func main() { 734 | r := mux.NewRouter() 735 | // A route with a route variable: 736 | r.HandleFunc("/metrics/{type}", MetricsHandler) 737 | 738 | log.Fatal(http.ListenAndServe("localhost:8080", r)) 739 | } 740 | ``` 741 | 742 | Our test file, with a table-driven test of `routeVariables`: 743 | 744 | ```go 745 | // endpoints_test.go 746 | func TestMetricsHandler(t *testing.T) { 747 | tt := []struct{ 748 | routeVariable string 749 | shouldPass bool 750 | }{ 751 | {"goroutines", true}, 752 | {"heap", true}, 753 | {"counters", true}, 754 | {"queries", true}, 755 | {"adhadaeqm3k", false}, 756 | } 757 | 758 | for _, tc := range tt { 759 | path := fmt.Sprintf("/metrics/%s", tc.routeVariable) 760 | req, err := http.NewRequest("GET", path, nil) 761 | if err != nil { 762 | t.Fatal(err) 763 | } 764 | 765 | rr := httptest.NewRecorder() 766 | 767 | // To add the vars to the context, 768 | // we need to create a router through which we can pass the request. 769 | router := mux.NewRouter() 770 | router.HandleFunc("/metrics/{type}", MetricsHandler) 771 | router.ServeHTTP(rr, req) 772 | 773 | // In this case, our MetricsHandler returns a non-200 response 774 | // for a route variable it doesn't know about. 775 | if rr.Code == http.StatusOK && !tc.shouldPass { 776 | t.Errorf("handler should have failed on routeVariable %s: got %v want %v", 777 | tc.routeVariable, rr.Code, http.StatusOK) 778 | } 779 | } 780 | } 781 | ``` 782 | 783 | ## Full Example 784 | 785 | Here's a complete, runnable example of a small `mux` based server: 786 | 787 | ```go 788 | package main 789 | 790 | import ( 791 | "net/http" 792 | "log" 793 | "github.com/gorilla/mux" 794 | ) 795 | 796 | func YourHandler(w http.ResponseWriter, r *http.Request) { 797 | w.Write([]byte("Gorilla!\n")) 798 | } 799 | 800 | func main() { 801 | r := mux.NewRouter() 802 | // Routes consist of a path and a handler function. 803 | r.HandleFunc("/", YourHandler) 804 | 805 | // Bind to a port and pass our router in 806 | log.Fatal(http.ListenAndServe(":8000", r)) 807 | } 808 | ``` 809 | 810 | ## License 811 | 812 | BSD licensed. See the LICENSE file for details. 813 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/mux/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Gorilla Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | /* 6 | Package mux implements a request router and dispatcher. 7 | 8 | The name mux stands for "HTTP request multiplexer". Like the standard 9 | http.ServeMux, mux.Router matches incoming requests against a list of 10 | registered routes and calls a handler for the route that matches the URL 11 | or other conditions. The main features are: 12 | 13 | - Requests can be matched based on URL host, path, path prefix, schemes, 14 | header and query values, HTTP methods or using custom matchers. 15 | - URL hosts, paths and query values can have variables with an optional 16 | regular expression. 17 | - Registered URLs can be built, or "reversed", which helps maintaining 18 | references to resources. 19 | - Routes can be used as subrouters: nested routes are only tested if the 20 | parent route matches. This is useful to define groups of routes that 21 | share common conditions like a host, a path prefix or other repeated 22 | attributes. As a bonus, this optimizes request matching. 23 | - It implements the http.Handler interface so it is compatible with the 24 | standard http.ServeMux. 25 | 26 | Let's start registering a couple of URL paths and handlers: 27 | 28 | func main() { 29 | r := mux.NewRouter() 30 | r.HandleFunc("/", HomeHandler) 31 | r.HandleFunc("/products", ProductsHandler) 32 | r.HandleFunc("/articles", ArticlesHandler) 33 | http.Handle("/", r) 34 | } 35 | 36 | Here we register three routes mapping URL paths to handlers. This is 37 | equivalent to how http.HandleFunc() works: if an incoming request URL matches 38 | one of the paths, the corresponding handler is called passing 39 | (http.ResponseWriter, *http.Request) as parameters. 40 | 41 | Paths can have variables. They are defined using the format {name} or 42 | {name:pattern}. If a regular expression pattern is not defined, the matched 43 | variable will be anything until the next slash. For example: 44 | 45 | r := mux.NewRouter() 46 | r.HandleFunc("/products/{key}", ProductHandler) 47 | r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler) 48 | r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler) 49 | 50 | Groups can be used inside patterns, as long as they are non-capturing (?:re). For example: 51 | 52 | r.HandleFunc("/articles/{category}/{sort:(?:asc|desc|new)}", ArticlesCategoryHandler) 53 | 54 | The names are used to create a map of route variables which can be retrieved 55 | calling mux.Vars(): 56 | 57 | vars := mux.Vars(request) 58 | category := vars["category"] 59 | 60 | Note that if any capturing groups are present, mux will panic() during parsing. To prevent 61 | this, convert any capturing groups to non-capturing, e.g. change "/{sort:(asc|desc)}" to 62 | "/{sort:(?:asc|desc)}". This is a change from prior versions which behaved unpredictably 63 | when capturing groups were present. 64 | 65 | And this is all you need to know about the basic usage. More advanced options 66 | are explained below. 67 | 68 | Routes can also be restricted to a domain or subdomain. Just define a host 69 | pattern to be matched. They can also have variables: 70 | 71 | r := mux.NewRouter() 72 | // Only matches if domain is "www.example.com". 73 | r.Host("www.example.com") 74 | // Matches a dynamic subdomain. 75 | r.Host("{subdomain:[a-z]+}.domain.com") 76 | 77 | There are several other matchers that can be added. To match path prefixes: 78 | 79 | r.PathPrefix("/products/") 80 | 81 | ...or HTTP methods: 82 | 83 | r.Methods("GET", "POST") 84 | 85 | ...or URL schemes: 86 | 87 | r.Schemes("https") 88 | 89 | ...or header values: 90 | 91 | r.Headers("X-Requested-With", "XMLHttpRequest") 92 | 93 | ...or query values: 94 | 95 | r.Queries("key", "value") 96 | 97 | ...or to use a custom matcher function: 98 | 99 | r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool { 100 | return r.ProtoMajor == 0 101 | }) 102 | 103 | ...and finally, it is possible to combine several matchers in a single route: 104 | 105 | r.HandleFunc("/products", ProductsHandler). 106 | Host("www.example.com"). 107 | Methods("GET"). 108 | Schemes("http") 109 | 110 | Setting the same matching conditions again and again can be boring, so we have 111 | a way to group several routes that share the same requirements. 112 | We call it "subrouting". 113 | 114 | For example, let's say we have several URLs that should only match when the 115 | host is "www.example.com". Create a route for that host and get a "subrouter" 116 | from it: 117 | 118 | r := mux.NewRouter() 119 | s := r.Host("www.example.com").Subrouter() 120 | 121 | Then register routes in the subrouter: 122 | 123 | s.HandleFunc("/products/", ProductsHandler) 124 | s.HandleFunc("/products/{key}", ProductHandler) 125 | s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler) 126 | 127 | The three URL paths we registered above will only be tested if the domain is 128 | "www.example.com", because the subrouter is tested first. This is not 129 | only convenient, but also optimizes request matching. You can create 130 | subrouters combining any attribute matchers accepted by a route. 131 | 132 | Subrouters can be used to create domain or path "namespaces": you define 133 | subrouters in a central place and then parts of the app can register its 134 | paths relatively to a given subrouter. 135 | 136 | There's one more thing about subroutes. When a subrouter has a path prefix, 137 | the inner routes use it as base for their paths: 138 | 139 | r := mux.NewRouter() 140 | s := r.PathPrefix("/products").Subrouter() 141 | // "/products/" 142 | s.HandleFunc("/", ProductsHandler) 143 | // "/products/{key}/" 144 | s.HandleFunc("/{key}/", ProductHandler) 145 | // "/products/{key}/details" 146 | s.HandleFunc("/{key}/details", ProductDetailsHandler) 147 | 148 | Note that the path provided to PathPrefix() represents a "wildcard": calling 149 | PathPrefix("/static/").Handler(...) means that the handler will be passed any 150 | request that matches "/static/*". This makes it easy to serve static files with mux: 151 | 152 | func main() { 153 | var dir string 154 | 155 | flag.StringVar(&dir, "dir", ".", "the directory to serve files from. Defaults to the current dir") 156 | flag.Parse() 157 | r := mux.NewRouter() 158 | 159 | // This will serve files under http://localhost:8000/static/ 160 | r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(dir)))) 161 | 162 | srv := &http.Server{ 163 | Handler: r, 164 | Addr: "127.0.0.1:8000", 165 | // Good practice: enforce timeouts for servers you create! 166 | WriteTimeout: 15 * time.Second, 167 | ReadTimeout: 15 * time.Second, 168 | } 169 | 170 | log.Fatal(srv.ListenAndServe()) 171 | } 172 | 173 | Now let's see how to build registered URLs. 174 | 175 | Routes can be named. All routes that define a name can have their URLs built, 176 | or "reversed". We define a name calling Name() on a route. For example: 177 | 178 | r := mux.NewRouter() 179 | r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler). 180 | Name("article") 181 | 182 | To build a URL, get the route and call the URL() method, passing a sequence of 183 | key/value pairs for the route variables. For the previous route, we would do: 184 | 185 | url, err := r.Get("article").URL("category", "technology", "id", "42") 186 | 187 | ...and the result will be a url.URL with the following path: 188 | 189 | "/articles/technology/42" 190 | 191 | This also works for host and query value variables: 192 | 193 | r := mux.NewRouter() 194 | r.Host("{subdomain}.domain.com"). 195 | Path("/articles/{category}/{id:[0-9]+}"). 196 | Queries("filter", "{filter}"). 197 | HandlerFunc(ArticleHandler). 198 | Name("article") 199 | 200 | // url.String() will be "http://news.domain.com/articles/technology/42?filter=gorilla" 201 | url, err := r.Get("article").URL("subdomain", "news", 202 | "category", "technology", 203 | "id", "42", 204 | "filter", "gorilla") 205 | 206 | All variables defined in the route are required, and their values must 207 | conform to the corresponding patterns. These requirements guarantee that a 208 | generated URL will always match a registered route -- the only exception is 209 | for explicitly defined "build-only" routes which never match. 210 | 211 | Regex support also exists for matching Headers within a route. For example, we could do: 212 | 213 | r.HeadersRegexp("Content-Type", "application/(text|json)") 214 | 215 | ...and the route will match both requests with a Content-Type of `application/json` as well as 216 | `application/text` 217 | 218 | There's also a way to build only the URL host or path for a route: 219 | use the methods URLHost() or URLPath() instead. For the previous route, 220 | we would do: 221 | 222 | // "http://news.domain.com/" 223 | host, err := r.Get("article").URLHost("subdomain", "news") 224 | 225 | // "/articles/technology/42" 226 | path, err := r.Get("article").URLPath("category", "technology", "id", "42") 227 | 228 | And if you use subrouters, host and path defined separately can be built 229 | as well: 230 | 231 | r := mux.NewRouter() 232 | s := r.Host("{subdomain}.domain.com").Subrouter() 233 | s.Path("/articles/{category}/{id:[0-9]+}"). 234 | HandlerFunc(ArticleHandler). 235 | Name("article") 236 | 237 | // "http://news.domain.com/articles/technology/42" 238 | url, err := r.Get("article").URL("subdomain", "news", 239 | "category", "technology", 240 | "id", "42") 241 | 242 | Mux supports the addition of middlewares to a Router, which are executed in the order they are added if a match is found, including its subrouters. Middlewares are (typically) small pieces of code which take one request, do something with it, and pass it down to another middleware or the final handler. Some common use cases for middleware are request logging, header manipulation, or ResponseWriter hijacking. 243 | 244 | type MiddlewareFunc func(http.Handler) http.Handler 245 | 246 | Typically, the returned handler is a closure which does something with the http.ResponseWriter and http.Request passed to it, and then calls the handler passed as parameter to the MiddlewareFunc (closures can access variables from the context where they are created). 247 | 248 | A very basic middleware which logs the URI of the request being handled could be written as: 249 | 250 | func simpleMw(next http.Handler) http.Handler { 251 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 252 | // Do stuff here 253 | log.Println(r.RequestURI) 254 | // Call the next handler, which can be another middleware in the chain, or the final handler. 255 | next.ServeHTTP(w, r) 256 | }) 257 | } 258 | 259 | Middlewares can be added to a router using `Router.Use()`: 260 | 261 | r := mux.NewRouter() 262 | r.HandleFunc("/", handler) 263 | r.Use(simpleMw) 264 | 265 | A more complex authentication middleware, which maps session token to users, could be written as: 266 | 267 | // Define our struct 268 | type authenticationMiddleware struct { 269 | tokenUsers map[string]string 270 | } 271 | 272 | // Initialize it somewhere 273 | func (amw *authenticationMiddleware) Populate() { 274 | amw.tokenUsers["00000000"] = "user0" 275 | amw.tokenUsers["aaaaaaaa"] = "userA" 276 | amw.tokenUsers["05f717e5"] = "randomUser" 277 | amw.tokenUsers["deadbeef"] = "user0" 278 | } 279 | 280 | // Middleware function, which will be called for each request 281 | func (amw *authenticationMiddleware) Middleware(next http.Handler) http.Handler { 282 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 283 | token := r.Header.Get("X-Session-Token") 284 | 285 | if user, found := amw.tokenUsers[token]; found { 286 | // We found the token in our map 287 | log.Printf("Authenticated user %s\n", user) 288 | next.ServeHTTP(w, r) 289 | } else { 290 | http.Error(w, "Forbidden", http.StatusForbidden) 291 | } 292 | }) 293 | } 294 | 295 | r := mux.NewRouter() 296 | r.HandleFunc("/", handler) 297 | 298 | amw := authenticationMiddleware{tokenUsers: make(map[string]string)} 299 | amw.Populate() 300 | 301 | r.Use(amw.Middleware) 302 | 303 | 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. 304 | */ 305 | package mux 306 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/mux/middleware.go: -------------------------------------------------------------------------------- 1 | package mux 2 | 3 | import ( 4 | "net/http" 5 | "strings" 6 | ) 7 | 8 | // MiddlewareFunc is a function which receives an http.Handler and returns another http.Handler. 9 | // Typically, the returned handler is a closure which does something with the http.ResponseWriter and http.Request passed 10 | // to it, and then calls the handler passed as parameter to the MiddlewareFunc. 11 | type MiddlewareFunc func(http.Handler) http.Handler 12 | 13 | // middleware interface is anything which implements a MiddlewareFunc named Middleware. 14 | type middleware interface { 15 | Middleware(handler http.Handler) http.Handler 16 | } 17 | 18 | // Middleware allows MiddlewareFunc to implement the middleware interface. 19 | func (mw MiddlewareFunc) Middleware(handler http.Handler) http.Handler { 20 | return mw(handler) 21 | } 22 | 23 | // Use appends a MiddlewareFunc to the chain. Middleware can be used to intercept or otherwise modify requests and/or responses, and are executed in the order that they are applied to the Router. 24 | func (r *Router) Use(mwf ...MiddlewareFunc) { 25 | for _, fn := range mwf { 26 | r.middlewares = append(r.middlewares, fn) 27 | } 28 | } 29 | 30 | // useInterface appends a middleware to the chain. Middleware can be used to intercept or otherwise modify requests and/or responses, and are executed in the order that they are applied to the Router. 31 | func (r *Router) useInterface(mw middleware) { 32 | r.middlewares = append(r.middlewares, mw) 33 | } 34 | 35 | // CORSMethodMiddleware automatically sets the Access-Control-Allow-Methods response header 36 | // on requests for routes that have an OPTIONS method matcher to all the method matchers on 37 | // the route. Routes that do not explicitly handle OPTIONS requests will not be processed 38 | // by the middleware. See examples for usage. 39 | func CORSMethodMiddleware(r *Router) MiddlewareFunc { 40 | return func(next http.Handler) http.Handler { 41 | return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 42 | allMethods, err := getAllMethodsForRoute(r, req) 43 | if err == nil { 44 | for _, v := range allMethods { 45 | if v == http.MethodOptions { 46 | w.Header().Set("Access-Control-Allow-Methods", strings.Join(allMethods, ",")) 47 | } 48 | } 49 | } 50 | 51 | next.ServeHTTP(w, req) 52 | }) 53 | } 54 | } 55 | 56 | // getAllMethodsForRoute returns all the methods from method matchers matching a given 57 | // request. 58 | func getAllMethodsForRoute(r *Router, req *http.Request) ([]string, error) { 59 | var allMethods []string 60 | 61 | for _, route := range r.routes { 62 | var match RouteMatch 63 | if route.Match(req, &match) || match.MatchErr == ErrMethodMismatch { 64 | methods, err := route.GetMethods() 65 | if err != nil { 66 | return nil, err 67 | } 68 | 69 | allMethods = append(allMethods, methods...) 70 | } 71 | } 72 | 73 | return allMethods, nil 74 | } 75 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/mux/mux.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Gorilla Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package mux 6 | 7 | import ( 8 | "context" 9 | "errors" 10 | "fmt" 11 | "net/http" 12 | "path" 13 | "regexp" 14 | ) 15 | 16 | var ( 17 | // ErrMethodMismatch is returned when the method in the request does not match 18 | // the method defined against the route. 19 | ErrMethodMismatch = errors.New("method is not allowed") 20 | // ErrNotFound is returned when no route match is found. 21 | ErrNotFound = errors.New("no matching route was found") 22 | ) 23 | 24 | // NewRouter returns a new router instance. 25 | func NewRouter() *Router { 26 | return &Router{namedRoutes: make(map[string]*Route)} 27 | } 28 | 29 | // Router registers routes to be matched and dispatches a handler. 30 | // 31 | // It implements the http.Handler interface, so it can be registered to serve 32 | // requests: 33 | // 34 | // var router = mux.NewRouter() 35 | // 36 | // func main() { 37 | // http.Handle("/", router) 38 | // } 39 | // 40 | // Or, for Google App Engine, register it in a init() function: 41 | // 42 | // func init() { 43 | // http.Handle("/", router) 44 | // } 45 | // 46 | // This will send all incoming requests to the router. 47 | type Router struct { 48 | // Configurable Handler to be used when no route matches. 49 | // This can be used to render your own 404 Not Found errors. 50 | NotFoundHandler http.Handler 51 | 52 | // Configurable Handler to be used when the request method does not match the route. 53 | // This can be used to render your own 405 Method Not Allowed errors. 54 | MethodNotAllowedHandler http.Handler 55 | 56 | // Routes to be matched, in order. 57 | routes []*Route 58 | 59 | // Routes by name for URL building. 60 | namedRoutes map[string]*Route 61 | 62 | // If true, do not clear the request context after handling the request. 63 | // 64 | // Deprecated: No effect, since the context is stored on the request itself. 65 | KeepContext bool 66 | 67 | // Slice of middlewares to be called after a match is found 68 | middlewares []middleware 69 | 70 | // configuration shared with `Route` 71 | routeConf 72 | } 73 | 74 | // common route configuration shared between `Router` and `Route` 75 | type routeConf struct { 76 | // If true, "/path/foo%2Fbar/to" will match the path "/path/{var}/to" 77 | useEncodedPath bool 78 | 79 | // If true, when the path pattern is "/path/", accessing "/path" will 80 | // redirect to the former and vice versa. 81 | strictSlash bool 82 | 83 | // If true, when the path pattern is "/path//to", accessing "/path//to" 84 | // will not redirect 85 | skipClean bool 86 | 87 | // Manager for the variables from host and path. 88 | regexp routeRegexpGroup 89 | 90 | // List of matchers. 91 | matchers []matcher 92 | 93 | // The scheme used when building URLs. 94 | buildScheme string 95 | 96 | buildVarsFunc BuildVarsFunc 97 | } 98 | 99 | // returns an effective deep copy of `routeConf` 100 | func copyRouteConf(r routeConf) routeConf { 101 | c := r 102 | 103 | if r.regexp.path != nil { 104 | c.regexp.path = copyRouteRegexp(r.regexp.path) 105 | } 106 | 107 | if r.regexp.host != nil { 108 | c.regexp.host = copyRouteRegexp(r.regexp.host) 109 | } 110 | 111 | c.regexp.queries = make([]*routeRegexp, 0, len(r.regexp.queries)) 112 | for _, q := range r.regexp.queries { 113 | c.regexp.queries = append(c.regexp.queries, copyRouteRegexp(q)) 114 | } 115 | 116 | c.matchers = make([]matcher, len(r.matchers)) 117 | copy(c.matchers, r.matchers) 118 | 119 | return c 120 | } 121 | 122 | func copyRouteRegexp(r *routeRegexp) *routeRegexp { 123 | c := *r 124 | return &c 125 | } 126 | 127 | // Match attempts to match the given request against the router's registered routes. 128 | // 129 | // If the request matches a route of this router or one of its subrouters the Route, 130 | // Handler, and Vars fields of the the match argument are filled and this function 131 | // returns true. 132 | // 133 | // If the request does not match any of this router's or its subrouters' routes 134 | // then this function returns false. If available, a reason for the match failure 135 | // will be filled in the match argument's MatchErr field. If the match failure type 136 | // (eg: not found) has a registered handler, the handler is assigned to the Handler 137 | // field of the match argument. 138 | func (r *Router) Match(req *http.Request, match *RouteMatch) bool { 139 | for _, route := range r.routes { 140 | if route.Match(req, match) { 141 | // Build middleware chain if no error was found 142 | if match.MatchErr == nil { 143 | for i := len(r.middlewares) - 1; i >= 0; i-- { 144 | match.Handler = r.middlewares[i].Middleware(match.Handler) 145 | } 146 | } 147 | return true 148 | } 149 | } 150 | 151 | if match.MatchErr == ErrMethodMismatch { 152 | if r.MethodNotAllowedHandler != nil { 153 | match.Handler = r.MethodNotAllowedHandler 154 | return true 155 | } 156 | 157 | return false 158 | } 159 | 160 | // Closest match for a router (includes sub-routers) 161 | if r.NotFoundHandler != nil { 162 | match.Handler = r.NotFoundHandler 163 | match.MatchErr = ErrNotFound 164 | return true 165 | } 166 | 167 | match.MatchErr = ErrNotFound 168 | return false 169 | } 170 | 171 | // ServeHTTP dispatches the handler registered in the matched route. 172 | // 173 | // When there is a match, the route variables can be retrieved calling 174 | // mux.Vars(request). 175 | func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { 176 | if !r.skipClean { 177 | path := req.URL.Path 178 | if r.useEncodedPath { 179 | path = req.URL.EscapedPath() 180 | } 181 | // Clean path to canonical form and redirect. 182 | if p := cleanPath(path); p != path { 183 | 184 | // Added 3 lines (Philip Schlump) - It was dropping the query string and #whatever from query. 185 | // This matches with fix in go 1.2 r.c. 4 for same problem. Go Issue: 186 | // http://code.google.com/p/go/issues/detail?id=5252 187 | url := *req.URL 188 | url.Path = p 189 | p = url.String() 190 | 191 | w.Header().Set("Location", p) 192 | w.WriteHeader(http.StatusMovedPermanently) 193 | return 194 | } 195 | } 196 | var match RouteMatch 197 | var handler http.Handler 198 | if r.Match(req, &match) { 199 | handler = match.Handler 200 | req = requestWithVars(req, match.Vars) 201 | req = requestWithRoute(req, match.Route) 202 | } 203 | 204 | if handler == nil && match.MatchErr == ErrMethodMismatch { 205 | handler = methodNotAllowedHandler() 206 | } 207 | 208 | if handler == nil { 209 | handler = http.NotFoundHandler() 210 | } 211 | 212 | handler.ServeHTTP(w, req) 213 | } 214 | 215 | // Get returns a route registered with the given name. 216 | func (r *Router) Get(name string) *Route { 217 | return r.namedRoutes[name] 218 | } 219 | 220 | // GetRoute returns a route registered with the given name. This method 221 | // was renamed to Get() and remains here for backwards compatibility. 222 | func (r *Router) GetRoute(name string) *Route { 223 | return r.namedRoutes[name] 224 | } 225 | 226 | // StrictSlash defines the trailing slash behavior for new routes. The initial 227 | // value is false. 228 | // 229 | // When true, if the route path is "/path/", accessing "/path" will perform a redirect 230 | // to the former and vice versa. In other words, your application will always 231 | // see the path as specified in the route. 232 | // 233 | // When false, if the route path is "/path", accessing "/path/" will not match 234 | // this route and vice versa. 235 | // 236 | // The re-direct is a HTTP 301 (Moved Permanently). Note that when this is set for 237 | // routes with a non-idempotent method (e.g. POST, PUT), the subsequent re-directed 238 | // request will be made as a GET by most clients. Use middleware or client settings 239 | // to modify this behaviour as needed. 240 | // 241 | // Special case: when a route sets a path prefix using the PathPrefix() method, 242 | // strict slash is ignored for that route because the redirect behavior can't 243 | // be determined from a prefix alone. However, any subrouters created from that 244 | // route inherit the original StrictSlash setting. 245 | func (r *Router) StrictSlash(value bool) *Router { 246 | r.strictSlash = value 247 | return r 248 | } 249 | 250 | // SkipClean defines the path cleaning behaviour for new routes. The initial 251 | // value is false. Users should be careful about which routes are not cleaned 252 | // 253 | // When true, if the route path is "/path//to", it will remain with the double 254 | // slash. This is helpful if you have a route like: /fetch/http://xkcd.com/534/ 255 | // 256 | // When false, the path will be cleaned, so /fetch/http://xkcd.com/534/ will 257 | // become /fetch/http/xkcd.com/534 258 | func (r *Router) SkipClean(value bool) *Router { 259 | r.skipClean = value 260 | return r 261 | } 262 | 263 | // UseEncodedPath tells the router to match the encoded original path 264 | // to the routes. 265 | // For eg. "/path/foo%2Fbar/to" will match the path "/path/{var}/to". 266 | // 267 | // If not called, the router will match the unencoded path to the routes. 268 | // For eg. "/path/foo%2Fbar/to" will match the path "/path/foo/bar/to" 269 | func (r *Router) UseEncodedPath() *Router { 270 | r.useEncodedPath = true 271 | return r 272 | } 273 | 274 | // ---------------------------------------------------------------------------- 275 | // Route factories 276 | // ---------------------------------------------------------------------------- 277 | 278 | // NewRoute registers an empty route. 279 | func (r *Router) NewRoute() *Route { 280 | // initialize a route with a copy of the parent router's configuration 281 | route := &Route{routeConf: copyRouteConf(r.routeConf), namedRoutes: r.namedRoutes} 282 | r.routes = append(r.routes, route) 283 | return route 284 | } 285 | 286 | // Name registers a new route with a name. 287 | // See Route.Name(). 288 | func (r *Router) Name(name string) *Route { 289 | return r.NewRoute().Name(name) 290 | } 291 | 292 | // Handle registers a new route with a matcher for the URL path. 293 | // See Route.Path() and Route.Handler(). 294 | func (r *Router) Handle(path string, handler http.Handler) *Route { 295 | return r.NewRoute().Path(path).Handler(handler) 296 | } 297 | 298 | // HandleFunc registers a new route with a matcher for the URL path. 299 | // See Route.Path() and Route.HandlerFunc(). 300 | func (r *Router) HandleFunc(path string, f func(http.ResponseWriter, 301 | *http.Request)) *Route { 302 | return r.NewRoute().Path(path).HandlerFunc(f) 303 | } 304 | 305 | // Headers registers a new route with a matcher for request header values. 306 | // See Route.Headers(). 307 | func (r *Router) Headers(pairs ...string) *Route { 308 | return r.NewRoute().Headers(pairs...) 309 | } 310 | 311 | // Host registers a new route with a matcher for the URL host. 312 | // See Route.Host(). 313 | func (r *Router) Host(tpl string) *Route { 314 | return r.NewRoute().Host(tpl) 315 | } 316 | 317 | // MatcherFunc registers a new route with a custom matcher function. 318 | // See Route.MatcherFunc(). 319 | func (r *Router) MatcherFunc(f MatcherFunc) *Route { 320 | return r.NewRoute().MatcherFunc(f) 321 | } 322 | 323 | // Methods registers a new route with a matcher for HTTP methods. 324 | // See Route.Methods(). 325 | func (r *Router) Methods(methods ...string) *Route { 326 | return r.NewRoute().Methods(methods...) 327 | } 328 | 329 | // Path registers a new route with a matcher for the URL path. 330 | // See Route.Path(). 331 | func (r *Router) Path(tpl string) *Route { 332 | return r.NewRoute().Path(tpl) 333 | } 334 | 335 | // PathPrefix registers a new route with a matcher for the URL path prefix. 336 | // See Route.PathPrefix(). 337 | func (r *Router) PathPrefix(tpl string) *Route { 338 | return r.NewRoute().PathPrefix(tpl) 339 | } 340 | 341 | // Queries registers a new route with a matcher for URL query values. 342 | // See Route.Queries(). 343 | func (r *Router) Queries(pairs ...string) *Route { 344 | return r.NewRoute().Queries(pairs...) 345 | } 346 | 347 | // Schemes registers a new route with a matcher for URL schemes. 348 | // See Route.Schemes(). 349 | func (r *Router) Schemes(schemes ...string) *Route { 350 | return r.NewRoute().Schemes(schemes...) 351 | } 352 | 353 | // BuildVarsFunc registers a new route with a custom function for modifying 354 | // route variables before building a URL. 355 | func (r *Router) BuildVarsFunc(f BuildVarsFunc) *Route { 356 | return r.NewRoute().BuildVarsFunc(f) 357 | } 358 | 359 | // Walk walks the router and all its sub-routers, calling walkFn for each route 360 | // in the tree. The routes are walked in the order they were added. Sub-routers 361 | // are explored depth-first. 362 | func (r *Router) Walk(walkFn WalkFunc) error { 363 | return r.walk(walkFn, []*Route{}) 364 | } 365 | 366 | // SkipRouter is used as a return value from WalkFuncs to indicate that the 367 | // router that walk is about to descend down to should be skipped. 368 | var SkipRouter = errors.New("skip this router") 369 | 370 | // WalkFunc is the type of the function called for each route visited by Walk. 371 | // At every invocation, it is given the current route, and the current router, 372 | // and a list of ancestor routes that lead to the current route. 373 | type WalkFunc func(route *Route, router *Router, ancestors []*Route) error 374 | 375 | func (r *Router) walk(walkFn WalkFunc, ancestors []*Route) error { 376 | for _, t := range r.routes { 377 | err := walkFn(t, r, ancestors) 378 | if err == SkipRouter { 379 | continue 380 | } 381 | if err != nil { 382 | return err 383 | } 384 | for _, sr := range t.matchers { 385 | if h, ok := sr.(*Router); ok { 386 | ancestors = append(ancestors, t) 387 | err := h.walk(walkFn, ancestors) 388 | if err != nil { 389 | return err 390 | } 391 | ancestors = ancestors[:len(ancestors)-1] 392 | } 393 | } 394 | if h, ok := t.handler.(*Router); ok { 395 | ancestors = append(ancestors, t) 396 | err := h.walk(walkFn, ancestors) 397 | if err != nil { 398 | return err 399 | } 400 | ancestors = ancestors[:len(ancestors)-1] 401 | } 402 | } 403 | return nil 404 | } 405 | 406 | // ---------------------------------------------------------------------------- 407 | // Context 408 | // ---------------------------------------------------------------------------- 409 | 410 | // RouteMatch stores information about a matched route. 411 | type RouteMatch struct { 412 | Route *Route 413 | Handler http.Handler 414 | Vars map[string]string 415 | 416 | // MatchErr is set to appropriate matching error 417 | // It is set to ErrMethodMismatch if there is a mismatch in 418 | // the request method and route method 419 | MatchErr error 420 | } 421 | 422 | type contextKey int 423 | 424 | const ( 425 | varsKey contextKey = iota 426 | routeKey 427 | ) 428 | 429 | // Vars returns the route variables for the current request, if any. 430 | func Vars(r *http.Request) map[string]string { 431 | if rv := r.Context().Value(varsKey); rv != nil { 432 | return rv.(map[string]string) 433 | } 434 | return nil 435 | } 436 | 437 | // CurrentRoute returns the matched route for the current request, if any. 438 | // This only works when called inside the handler of the matched route 439 | // because the matched route is stored in the request context which is cleared 440 | // after the handler returns. 441 | func CurrentRoute(r *http.Request) *Route { 442 | if rv := r.Context().Value(routeKey); rv != nil { 443 | return rv.(*Route) 444 | } 445 | return nil 446 | } 447 | 448 | func requestWithVars(r *http.Request, vars map[string]string) *http.Request { 449 | ctx := context.WithValue(r.Context(), varsKey, vars) 450 | return r.WithContext(ctx) 451 | } 452 | 453 | func requestWithRoute(r *http.Request, route *Route) *http.Request { 454 | ctx := context.WithValue(r.Context(), routeKey, route) 455 | return r.WithContext(ctx) 456 | } 457 | 458 | // ---------------------------------------------------------------------------- 459 | // Helpers 460 | // ---------------------------------------------------------------------------- 461 | 462 | // cleanPath returns the canonical path for p, eliminating . and .. elements. 463 | // Borrowed from the net/http package. 464 | func cleanPath(p string) string { 465 | if p == "" { 466 | return "/" 467 | } 468 | if p[0] != '/' { 469 | p = "/" + p 470 | } 471 | np := path.Clean(p) 472 | // path.Clean removes trailing slash except for root; 473 | // put the trailing slash back if necessary. 474 | if p[len(p)-1] == '/' && np != "/" { 475 | np += "/" 476 | } 477 | 478 | return np 479 | } 480 | 481 | // uniqueVars returns an error if two slices contain duplicated strings. 482 | func uniqueVars(s1, s2 []string) error { 483 | for _, v1 := range s1 { 484 | for _, v2 := range s2 { 485 | if v1 == v2 { 486 | return fmt.Errorf("mux: duplicated route variable %q", v2) 487 | } 488 | } 489 | } 490 | return nil 491 | } 492 | 493 | // checkPairs returns the count of strings passed in, and an error if 494 | // the count is not an even number. 495 | func checkPairs(pairs ...string) (int, error) { 496 | length := len(pairs) 497 | if length%2 != 0 { 498 | return length, fmt.Errorf( 499 | "mux: number of parameters must be multiple of 2, got %v", pairs) 500 | } 501 | return length, nil 502 | } 503 | 504 | // mapFromPairsToString converts variadic string parameters to a 505 | // string to string map. 506 | func mapFromPairsToString(pairs ...string) (map[string]string, error) { 507 | length, err := checkPairs(pairs...) 508 | if err != nil { 509 | return nil, err 510 | } 511 | m := make(map[string]string, length/2) 512 | for i := 0; i < length; i += 2 { 513 | m[pairs[i]] = pairs[i+1] 514 | } 515 | return m, nil 516 | } 517 | 518 | // mapFromPairsToRegex converts variadic string parameters to a 519 | // string to regex map. 520 | func mapFromPairsToRegex(pairs ...string) (map[string]*regexp.Regexp, error) { 521 | length, err := checkPairs(pairs...) 522 | if err != nil { 523 | return nil, err 524 | } 525 | m := make(map[string]*regexp.Regexp, length/2) 526 | for i := 0; i < length; i += 2 { 527 | regex, err := regexp.Compile(pairs[i+1]) 528 | if err != nil { 529 | return nil, err 530 | } 531 | m[pairs[i]] = regex 532 | } 533 | return m, nil 534 | } 535 | 536 | // matchInArray returns true if the given string value is in the array. 537 | func matchInArray(arr []string, value string) bool { 538 | for _, v := range arr { 539 | if v == value { 540 | return true 541 | } 542 | } 543 | return false 544 | } 545 | 546 | // matchMapWithString returns true if the given key/value pairs exist in a given map. 547 | func matchMapWithString(toCheck map[string]string, toMatch map[string][]string, canonicalKey bool) bool { 548 | for k, v := range toCheck { 549 | // Check if key exists. 550 | if canonicalKey { 551 | k = http.CanonicalHeaderKey(k) 552 | } 553 | if values := toMatch[k]; values == nil { 554 | return false 555 | } else if v != "" { 556 | // If value was defined as an empty string we only check that the 557 | // key exists. Otherwise we also check for equality. 558 | valueExists := false 559 | for _, value := range values { 560 | if v == value { 561 | valueExists = true 562 | break 563 | } 564 | } 565 | if !valueExists { 566 | return false 567 | } 568 | } 569 | } 570 | return true 571 | } 572 | 573 | // matchMapWithRegex returns true if the given key/value pairs exist in a given map compiled against 574 | // the given regex 575 | func matchMapWithRegex(toCheck map[string]*regexp.Regexp, toMatch map[string][]string, canonicalKey bool) bool { 576 | for k, v := range toCheck { 577 | // Check if key exists. 578 | if canonicalKey { 579 | k = http.CanonicalHeaderKey(k) 580 | } 581 | if values := toMatch[k]; values == nil { 582 | return false 583 | } else if v != nil { 584 | // If value was defined as an empty string we only check that the 585 | // key exists. Otherwise we also check for equality. 586 | valueExists := false 587 | for _, value := range values { 588 | if v.MatchString(value) { 589 | valueExists = true 590 | break 591 | } 592 | } 593 | if !valueExists { 594 | return false 595 | } 596 | } 597 | } 598 | return true 599 | } 600 | 601 | // methodNotAllowed replies to the request with an HTTP status code 405. 602 | func methodNotAllowed(w http.ResponseWriter, r *http.Request) { 603 | w.WriteHeader(http.StatusMethodNotAllowed) 604 | } 605 | 606 | // methodNotAllowedHandler returns a simple request handler 607 | // that replies to each request with a status code 405. 608 | func methodNotAllowedHandler() http.Handler { return http.HandlerFunc(methodNotAllowed) } 609 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/mux/regexp.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Gorilla Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package mux 6 | 7 | import ( 8 | "bytes" 9 | "fmt" 10 | "net/http" 11 | "net/url" 12 | "regexp" 13 | "strconv" 14 | "strings" 15 | ) 16 | 17 | type routeRegexpOptions struct { 18 | strictSlash bool 19 | useEncodedPath bool 20 | } 21 | 22 | type regexpType int 23 | 24 | const ( 25 | regexpTypePath regexpType = iota 26 | regexpTypeHost 27 | regexpTypePrefix 28 | regexpTypeQuery 29 | ) 30 | 31 | // newRouteRegexp parses a route template and returns a routeRegexp, 32 | // used to match a host, a path or a query string. 33 | // 34 | // It will extract named variables, assemble a regexp to be matched, create 35 | // a "reverse" template to build URLs and compile regexps to validate variable 36 | // values used in URL building. 37 | // 38 | // Previously we accepted only Python-like identifiers for variable 39 | // names ([a-zA-Z_][a-zA-Z0-9_]*), but currently the only restriction is that 40 | // name and pattern can't be empty, and names can't contain a colon. 41 | func newRouteRegexp(tpl string, typ regexpType, options routeRegexpOptions) (*routeRegexp, error) { 42 | // Check if it is well-formed. 43 | idxs, errBraces := braceIndices(tpl) 44 | if errBraces != nil { 45 | return nil, errBraces 46 | } 47 | // Backup the original. 48 | template := tpl 49 | // Now let's parse it. 50 | defaultPattern := "[^/]+" 51 | if typ == regexpTypeQuery { 52 | defaultPattern = ".*" 53 | } else if typ == regexpTypeHost { 54 | defaultPattern = "[^.]+" 55 | } 56 | // Only match strict slash if not matching 57 | if typ != regexpTypePath { 58 | options.strictSlash = false 59 | } 60 | // Set a flag for strictSlash. 61 | endSlash := false 62 | if options.strictSlash && strings.HasSuffix(tpl, "/") { 63 | tpl = tpl[:len(tpl)-1] 64 | endSlash = true 65 | } 66 | varsN := make([]string, len(idxs)/2) 67 | varsR := make([]*regexp.Regexp, len(idxs)/2) 68 | pattern := bytes.NewBufferString("") 69 | pattern.WriteByte('^') 70 | reverse := bytes.NewBufferString("") 71 | var end int 72 | var err error 73 | for i := 0; i < len(idxs); i += 2 { 74 | // Set all values we are interested in. 75 | raw := tpl[end:idxs[i]] 76 | end = idxs[i+1] 77 | parts := strings.SplitN(tpl[idxs[i]+1:end-1], ":", 2) 78 | name := parts[0] 79 | patt := defaultPattern 80 | if len(parts) == 2 { 81 | patt = parts[1] 82 | } 83 | // Name or pattern can't be empty. 84 | if name == "" || patt == "" { 85 | return nil, fmt.Errorf("mux: missing name or pattern in %q", 86 | tpl[idxs[i]:end]) 87 | } 88 | // Build the regexp pattern. 89 | fmt.Fprintf(pattern, "%s(?P<%s>%s)", regexp.QuoteMeta(raw), varGroupName(i/2), patt) 90 | 91 | // Build the reverse template. 92 | fmt.Fprintf(reverse, "%s%%s", raw) 93 | 94 | // Append variable name and compiled pattern. 95 | varsN[i/2] = name 96 | varsR[i/2], err = regexp.Compile(fmt.Sprintf("^%s$", patt)) 97 | if err != nil { 98 | return nil, err 99 | } 100 | } 101 | // Add the remaining. 102 | raw := tpl[end:] 103 | pattern.WriteString(regexp.QuoteMeta(raw)) 104 | if options.strictSlash { 105 | pattern.WriteString("[/]?") 106 | } 107 | if typ == regexpTypeQuery { 108 | // Add the default pattern if the query value is empty 109 | if queryVal := strings.SplitN(template, "=", 2)[1]; queryVal == "" { 110 | pattern.WriteString(defaultPattern) 111 | } 112 | } 113 | if typ != regexpTypePrefix { 114 | pattern.WriteByte('$') 115 | } 116 | 117 | var wildcardHostPort bool 118 | if typ == regexpTypeHost { 119 | if !strings.Contains(pattern.String(), ":") { 120 | wildcardHostPort = true 121 | } 122 | } 123 | reverse.WriteString(raw) 124 | if endSlash { 125 | reverse.WriteByte('/') 126 | } 127 | // Compile full regexp. 128 | reg, errCompile := regexp.Compile(pattern.String()) 129 | if errCompile != nil { 130 | return nil, errCompile 131 | } 132 | 133 | // Check for capturing groups which used to work in older versions 134 | if reg.NumSubexp() != len(idxs)/2 { 135 | panic(fmt.Sprintf("route %s contains capture groups in its regexp. ", template) + 136 | "Only non-capturing groups are accepted: e.g. (?:pattern) instead of (pattern)") 137 | } 138 | 139 | // Done! 140 | return &routeRegexp{ 141 | template: template, 142 | regexpType: typ, 143 | options: options, 144 | regexp: reg, 145 | reverse: reverse.String(), 146 | varsN: varsN, 147 | varsR: varsR, 148 | wildcardHostPort: wildcardHostPort, 149 | }, nil 150 | } 151 | 152 | // routeRegexp stores a regexp to match a host or path and information to 153 | // collect and validate route variables. 154 | type routeRegexp struct { 155 | // The unmodified template. 156 | template string 157 | // The type of match 158 | regexpType regexpType 159 | // Options for matching 160 | options routeRegexpOptions 161 | // Expanded regexp. 162 | regexp *regexp.Regexp 163 | // Reverse template. 164 | reverse string 165 | // Variable names. 166 | varsN []string 167 | // Variable regexps (validators). 168 | varsR []*regexp.Regexp 169 | // Wildcard host-port (no strict port match in hostname) 170 | wildcardHostPort bool 171 | } 172 | 173 | // Match matches the regexp against the URL host or path. 174 | func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool { 175 | if r.regexpType == regexpTypeHost { 176 | host := getHost(req) 177 | if r.wildcardHostPort { 178 | // Don't be strict on the port match 179 | if i := strings.Index(host, ":"); i != -1 { 180 | host = host[:i] 181 | } 182 | } 183 | return r.regexp.MatchString(host) 184 | } 185 | 186 | if r.regexpType == regexpTypeQuery { 187 | return r.matchQueryString(req) 188 | } 189 | path := req.URL.Path 190 | if r.options.useEncodedPath { 191 | path = req.URL.EscapedPath() 192 | } 193 | return r.regexp.MatchString(path) 194 | } 195 | 196 | // url builds a URL part using the given values. 197 | func (r *routeRegexp) url(values map[string]string) (string, error) { 198 | urlValues := make([]interface{}, len(r.varsN)) 199 | for k, v := range r.varsN { 200 | value, ok := values[v] 201 | if !ok { 202 | return "", fmt.Errorf("mux: missing route variable %q", v) 203 | } 204 | if r.regexpType == regexpTypeQuery { 205 | value = url.QueryEscape(value) 206 | } 207 | urlValues[k] = value 208 | } 209 | rv := fmt.Sprintf(r.reverse, urlValues...) 210 | if !r.regexp.MatchString(rv) { 211 | // The URL is checked against the full regexp, instead of checking 212 | // individual variables. This is faster but to provide a good error 213 | // message, we check individual regexps if the URL doesn't match. 214 | for k, v := range r.varsN { 215 | if !r.varsR[k].MatchString(values[v]) { 216 | return "", fmt.Errorf( 217 | "mux: variable %q doesn't match, expected %q", values[v], 218 | r.varsR[k].String()) 219 | } 220 | } 221 | } 222 | return rv, nil 223 | } 224 | 225 | // getURLQuery returns a single query parameter from a request URL. 226 | // For a URL with foo=bar&baz=ding, we return only the relevant key 227 | // value pair for the routeRegexp. 228 | func (r *routeRegexp) getURLQuery(req *http.Request) string { 229 | if r.regexpType != regexpTypeQuery { 230 | return "" 231 | } 232 | templateKey := strings.SplitN(r.template, "=", 2)[0] 233 | val, ok := findFirstQueryKey(req.URL.RawQuery, templateKey) 234 | if ok { 235 | return templateKey + "=" + val 236 | } 237 | return "" 238 | } 239 | 240 | // findFirstQueryKey returns the same result as (*url.URL).Query()[key][0]. 241 | // If key was not found, empty string and false is returned. 242 | func findFirstQueryKey(rawQuery, key string) (value string, ok bool) { 243 | query := []byte(rawQuery) 244 | for len(query) > 0 { 245 | foundKey := query 246 | if i := bytes.IndexAny(foundKey, "&;"); i >= 0 { 247 | foundKey, query = foundKey[:i], foundKey[i+1:] 248 | } else { 249 | query = query[:0] 250 | } 251 | if len(foundKey) == 0 { 252 | continue 253 | } 254 | var value []byte 255 | if i := bytes.IndexByte(foundKey, '='); i >= 0 { 256 | foundKey, value = foundKey[:i], foundKey[i+1:] 257 | } 258 | if len(foundKey) < len(key) { 259 | // Cannot possibly be key. 260 | continue 261 | } 262 | keyString, err := url.QueryUnescape(string(foundKey)) 263 | if err != nil { 264 | continue 265 | } 266 | if keyString != key { 267 | continue 268 | } 269 | valueString, err := url.QueryUnescape(string(value)) 270 | if err != nil { 271 | continue 272 | } 273 | return valueString, true 274 | } 275 | return "", false 276 | } 277 | 278 | func (r *routeRegexp) matchQueryString(req *http.Request) bool { 279 | return r.regexp.MatchString(r.getURLQuery(req)) 280 | } 281 | 282 | // braceIndices returns the first level curly brace indices from a string. 283 | // It returns an error in case of unbalanced braces. 284 | func braceIndices(s string) ([]int, error) { 285 | var level, idx int 286 | var idxs []int 287 | for i := 0; i < len(s); i++ { 288 | switch s[i] { 289 | case '{': 290 | if level++; level == 1 { 291 | idx = i 292 | } 293 | case '}': 294 | if level--; level == 0 { 295 | idxs = append(idxs, idx, i+1) 296 | } else if level < 0 { 297 | return nil, fmt.Errorf("mux: unbalanced braces in %q", s) 298 | } 299 | } 300 | } 301 | if level != 0 { 302 | return nil, fmt.Errorf("mux: unbalanced braces in %q", s) 303 | } 304 | return idxs, nil 305 | } 306 | 307 | // varGroupName builds a capturing group name for the indexed variable. 308 | func varGroupName(idx int) string { 309 | return "v" + strconv.Itoa(idx) 310 | } 311 | 312 | // ---------------------------------------------------------------------------- 313 | // routeRegexpGroup 314 | // ---------------------------------------------------------------------------- 315 | 316 | // routeRegexpGroup groups the route matchers that carry variables. 317 | type routeRegexpGroup struct { 318 | host *routeRegexp 319 | path *routeRegexp 320 | queries []*routeRegexp 321 | } 322 | 323 | // setMatch extracts the variables from the URL once a route matches. 324 | func (v routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route) { 325 | // Store host variables. 326 | if v.host != nil { 327 | host := getHost(req) 328 | if v.host.wildcardHostPort { 329 | // Don't be strict on the port match 330 | if i := strings.Index(host, ":"); i != -1 { 331 | host = host[:i] 332 | } 333 | } 334 | matches := v.host.regexp.FindStringSubmatchIndex(host) 335 | if len(matches) > 0 { 336 | extractVars(host, matches, v.host.varsN, m.Vars) 337 | } 338 | } 339 | path := req.URL.Path 340 | if r.useEncodedPath { 341 | path = req.URL.EscapedPath() 342 | } 343 | // Store path variables. 344 | if v.path != nil { 345 | matches := v.path.regexp.FindStringSubmatchIndex(path) 346 | if len(matches) > 0 { 347 | extractVars(path, matches, v.path.varsN, m.Vars) 348 | // Check if we should redirect. 349 | if v.path.options.strictSlash { 350 | p1 := strings.HasSuffix(path, "/") 351 | p2 := strings.HasSuffix(v.path.template, "/") 352 | if p1 != p2 { 353 | u, _ := url.Parse(req.URL.String()) 354 | if p1 { 355 | u.Path = u.Path[:len(u.Path)-1] 356 | } else { 357 | u.Path += "/" 358 | } 359 | m.Handler = http.RedirectHandler(u.String(), http.StatusMovedPermanently) 360 | } 361 | } 362 | } 363 | } 364 | // Store query string variables. 365 | for _, q := range v.queries { 366 | queryURL := q.getURLQuery(req) 367 | matches := q.regexp.FindStringSubmatchIndex(queryURL) 368 | if len(matches) > 0 { 369 | extractVars(queryURL, matches, q.varsN, m.Vars) 370 | } 371 | } 372 | } 373 | 374 | // getHost tries its best to return the request host. 375 | // According to section 14.23 of RFC 2616 the Host header 376 | // can include the port number if the default value of 80 is not used. 377 | func getHost(r *http.Request) string { 378 | if r.URL.IsAbs() { 379 | return r.URL.Host 380 | } 381 | return r.Host 382 | } 383 | 384 | func extractVars(input string, matches []int, names []string, output map[string]string) { 385 | for i, name := range names { 386 | output[name] = input[matches[2*i+2]:matches[2*i+3]] 387 | } 388 | } 389 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/mux/route.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Gorilla Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package mux 6 | 7 | import ( 8 | "errors" 9 | "fmt" 10 | "net/http" 11 | "net/url" 12 | "regexp" 13 | "strings" 14 | ) 15 | 16 | // Route stores information to match a request and build URLs. 17 | type Route struct { 18 | // Request handler for the route. 19 | handler http.Handler 20 | // If true, this route never matches: it is only used to build URLs. 21 | buildOnly bool 22 | // The name used to build URLs. 23 | name string 24 | // Error resulted from building a route. 25 | err error 26 | 27 | // "global" reference to all named routes 28 | namedRoutes map[string]*Route 29 | 30 | // config possibly passed in from `Router` 31 | routeConf 32 | } 33 | 34 | // SkipClean reports whether path cleaning is enabled for this route via 35 | // Router.SkipClean. 36 | func (r *Route) SkipClean() bool { 37 | return r.skipClean 38 | } 39 | 40 | // Match matches the route against the request. 41 | func (r *Route) Match(req *http.Request, match *RouteMatch) bool { 42 | if r.buildOnly || r.err != nil { 43 | return false 44 | } 45 | 46 | var matchErr error 47 | 48 | // Match everything. 49 | for _, m := range r.matchers { 50 | if matched := m.Match(req, match); !matched { 51 | if _, ok := m.(methodMatcher); ok { 52 | matchErr = ErrMethodMismatch 53 | continue 54 | } 55 | 56 | // Ignore ErrNotFound errors. These errors arise from match call 57 | // to Subrouters. 58 | // 59 | // This prevents subsequent matching subrouters from failing to 60 | // run middleware. If not ignored, the middleware would see a 61 | // non-nil MatchErr and be skipped, even when there was a 62 | // matching route. 63 | if match.MatchErr == ErrNotFound { 64 | match.MatchErr = nil 65 | } 66 | 67 | matchErr = nil // nolint:ineffassign 68 | return false 69 | } else { 70 | // Multiple routes may share the same path but use different HTTP methods. For instance: 71 | // Route 1: POST "/users/{id}". 72 | // Route 2: GET "/users/{id}", parameters: "id": "[0-9]+". 73 | // 74 | // The router must handle these cases correctly. For a GET request to "/users/abc" with "id" as "-2", 75 | // The router should return a "Not Found" error as no route fully matches this request. 76 | if match.MatchErr == ErrMethodMismatch { 77 | match.MatchErr = nil 78 | } 79 | } 80 | } 81 | 82 | if matchErr != nil { 83 | match.MatchErr = matchErr 84 | return false 85 | } 86 | 87 | if match.MatchErr == ErrMethodMismatch && r.handler != nil { 88 | // We found a route which matches request method, clear MatchErr 89 | match.MatchErr = nil 90 | // Then override the mis-matched handler 91 | match.Handler = r.handler 92 | } 93 | 94 | // Yay, we have a match. Let's collect some info about it. 95 | if match.Route == nil { 96 | match.Route = r 97 | } 98 | if match.Handler == nil { 99 | match.Handler = r.handler 100 | } 101 | if match.Vars == nil { 102 | match.Vars = make(map[string]string) 103 | } 104 | 105 | // Set variables. 106 | r.regexp.setMatch(req, match, r) 107 | return true 108 | } 109 | 110 | // ---------------------------------------------------------------------------- 111 | // Route attributes 112 | // ---------------------------------------------------------------------------- 113 | 114 | // GetError returns an error resulted from building the route, if any. 115 | func (r *Route) GetError() error { 116 | return r.err 117 | } 118 | 119 | // BuildOnly sets the route to never match: it is only used to build URLs. 120 | func (r *Route) BuildOnly() *Route { 121 | r.buildOnly = true 122 | return r 123 | } 124 | 125 | // Handler -------------------------------------------------------------------- 126 | 127 | // Handler sets a handler for the route. 128 | func (r *Route) Handler(handler http.Handler) *Route { 129 | if r.err == nil { 130 | r.handler = handler 131 | } 132 | return r 133 | } 134 | 135 | // HandlerFunc sets a handler function for the route. 136 | func (r *Route) HandlerFunc(f func(http.ResponseWriter, *http.Request)) *Route { 137 | return r.Handler(http.HandlerFunc(f)) 138 | } 139 | 140 | // GetHandler returns the handler for the route, if any. 141 | func (r *Route) GetHandler() http.Handler { 142 | return r.handler 143 | } 144 | 145 | // Name ----------------------------------------------------------------------- 146 | 147 | // Name sets the name for the route, used to build URLs. 148 | // It is an error to call Name more than once on a route. 149 | func (r *Route) Name(name string) *Route { 150 | if r.name != "" { 151 | r.err = fmt.Errorf("mux: route already has name %q, can't set %q", 152 | r.name, name) 153 | } 154 | if r.err == nil { 155 | r.name = name 156 | r.namedRoutes[name] = r 157 | } 158 | return r 159 | } 160 | 161 | // GetName returns the name for the route, if any. 162 | func (r *Route) GetName() string { 163 | return r.name 164 | } 165 | 166 | // ---------------------------------------------------------------------------- 167 | // Matchers 168 | // ---------------------------------------------------------------------------- 169 | 170 | // matcher types try to match a request. 171 | type matcher interface { 172 | Match(*http.Request, *RouteMatch) bool 173 | } 174 | 175 | // addMatcher adds a matcher to the route. 176 | func (r *Route) addMatcher(m matcher) *Route { 177 | if r.err == nil { 178 | r.matchers = append(r.matchers, m) 179 | } 180 | return r 181 | } 182 | 183 | // addRegexpMatcher adds a host or path matcher and builder to a route. 184 | func (r *Route) addRegexpMatcher(tpl string, typ regexpType) error { 185 | if r.err != nil { 186 | return r.err 187 | } 188 | if typ == regexpTypePath || typ == regexpTypePrefix { 189 | if len(tpl) > 0 && tpl[0] != '/' { 190 | return fmt.Errorf("mux: path must start with a slash, got %q", tpl) 191 | } 192 | if r.regexp.path != nil { 193 | tpl = strings.TrimRight(r.regexp.path.template, "/") + tpl 194 | } 195 | } 196 | rr, err := newRouteRegexp(tpl, typ, routeRegexpOptions{ 197 | strictSlash: r.strictSlash, 198 | useEncodedPath: r.useEncodedPath, 199 | }) 200 | if err != nil { 201 | return err 202 | } 203 | for _, q := range r.regexp.queries { 204 | if err = uniqueVars(rr.varsN, q.varsN); err != nil { 205 | return err 206 | } 207 | } 208 | if typ == regexpTypeHost { 209 | if r.regexp.path != nil { 210 | if err = uniqueVars(rr.varsN, r.regexp.path.varsN); err != nil { 211 | return err 212 | } 213 | } 214 | r.regexp.host = rr 215 | } else { 216 | if r.regexp.host != nil { 217 | if err = uniqueVars(rr.varsN, r.regexp.host.varsN); err != nil { 218 | return err 219 | } 220 | } 221 | if typ == regexpTypeQuery { 222 | r.regexp.queries = append(r.regexp.queries, rr) 223 | } else { 224 | r.regexp.path = rr 225 | } 226 | } 227 | r.addMatcher(rr) 228 | return nil 229 | } 230 | 231 | // Headers -------------------------------------------------------------------- 232 | 233 | // headerMatcher matches the request against header values. 234 | type headerMatcher map[string]string 235 | 236 | func (m headerMatcher) Match(r *http.Request, match *RouteMatch) bool { 237 | return matchMapWithString(m, r.Header, true) 238 | } 239 | 240 | // Headers adds a matcher for request header values. 241 | // It accepts a sequence of key/value pairs to be matched. For example: 242 | // 243 | // r := mux.NewRouter().NewRoute() 244 | // r.Headers("Content-Type", "application/json", 245 | // "X-Requested-With", "XMLHttpRequest") 246 | // 247 | // The above route will only match if both request header values match. 248 | // If the value is an empty string, it will match any value if the key is set. 249 | func (r *Route) Headers(pairs ...string) *Route { 250 | if r.err == nil { 251 | var headers map[string]string 252 | headers, r.err = mapFromPairsToString(pairs...) 253 | return r.addMatcher(headerMatcher(headers)) 254 | } 255 | return r 256 | } 257 | 258 | // headerRegexMatcher matches the request against the route given a regex for the header 259 | type headerRegexMatcher map[string]*regexp.Regexp 260 | 261 | func (m headerRegexMatcher) Match(r *http.Request, match *RouteMatch) bool { 262 | return matchMapWithRegex(m, r.Header, true) 263 | } 264 | 265 | // HeadersRegexp accepts a sequence of key/value pairs, where the value has regex 266 | // support. For example: 267 | // 268 | // r := mux.NewRouter().NewRoute() 269 | // r.HeadersRegexp("Content-Type", "application/(text|json)", 270 | // "X-Requested-With", "XMLHttpRequest") 271 | // 272 | // The above route will only match if both the request header matches both regular expressions. 273 | // If the value is an empty string, it will match any value if the key is set. 274 | // Use the start and end of string anchors (^ and $) to match an exact value. 275 | func (r *Route) HeadersRegexp(pairs ...string) *Route { 276 | if r.err == nil { 277 | var headers map[string]*regexp.Regexp 278 | headers, r.err = mapFromPairsToRegex(pairs...) 279 | return r.addMatcher(headerRegexMatcher(headers)) 280 | } 281 | return r 282 | } 283 | 284 | // Host ----------------------------------------------------------------------- 285 | 286 | // Host adds a matcher for the URL host. 287 | // It accepts a template with zero or more URL variables enclosed by {}. 288 | // Variables can define an optional regexp pattern to be matched: 289 | // 290 | // - {name} matches anything until the next dot. 291 | // 292 | // - {name:pattern} matches the given regexp pattern. 293 | // 294 | // For example: 295 | // 296 | // r := mux.NewRouter().NewRoute() 297 | // r.Host("www.example.com") 298 | // r.Host("{subdomain}.domain.com") 299 | // r.Host("{subdomain:[a-z]+}.domain.com") 300 | // 301 | // Variable names must be unique in a given route. They can be retrieved 302 | // calling mux.Vars(request). 303 | func (r *Route) Host(tpl string) *Route { 304 | r.err = r.addRegexpMatcher(tpl, regexpTypeHost) 305 | return r 306 | } 307 | 308 | // MatcherFunc ---------------------------------------------------------------- 309 | 310 | // MatcherFunc is the function signature used by custom matchers. 311 | type MatcherFunc func(*http.Request, *RouteMatch) bool 312 | 313 | // Match returns the match for a given request. 314 | func (m MatcherFunc) Match(r *http.Request, match *RouteMatch) bool { 315 | return m(r, match) 316 | } 317 | 318 | // MatcherFunc adds a custom function to be used as request matcher. 319 | func (r *Route) MatcherFunc(f MatcherFunc) *Route { 320 | return r.addMatcher(f) 321 | } 322 | 323 | // Methods -------------------------------------------------------------------- 324 | 325 | // methodMatcher matches the request against HTTP methods. 326 | type methodMatcher []string 327 | 328 | func (m methodMatcher) Match(r *http.Request, match *RouteMatch) bool { 329 | return matchInArray(m, r.Method) 330 | } 331 | 332 | // Methods adds a matcher for HTTP methods. 333 | // It accepts a sequence of one or more methods to be matched, e.g.: 334 | // "GET", "POST", "PUT". 335 | func (r *Route) Methods(methods ...string) *Route { 336 | for k, v := range methods { 337 | methods[k] = strings.ToUpper(v) 338 | } 339 | return r.addMatcher(methodMatcher(methods)) 340 | } 341 | 342 | // Path ----------------------------------------------------------------------- 343 | 344 | // Path adds a matcher for the URL path. 345 | // It accepts a template with zero or more URL variables enclosed by {}. The 346 | // template must start with a "/". 347 | // Variables can define an optional regexp pattern to be matched: 348 | // 349 | // - {name} matches anything until the next slash. 350 | // 351 | // - {name:pattern} matches the given regexp pattern. 352 | // 353 | // For example: 354 | // 355 | // r := mux.NewRouter().NewRoute() 356 | // r.Path("/products/").Handler(ProductsHandler) 357 | // r.Path("/products/{key}").Handler(ProductsHandler) 358 | // r.Path("/articles/{category}/{id:[0-9]+}"). 359 | // Handler(ArticleHandler) 360 | // 361 | // Variable names must be unique in a given route. They can be retrieved 362 | // calling mux.Vars(request). 363 | func (r *Route) Path(tpl string) *Route { 364 | r.err = r.addRegexpMatcher(tpl, regexpTypePath) 365 | return r 366 | } 367 | 368 | // PathPrefix ----------------------------------------------------------------- 369 | 370 | // PathPrefix adds a matcher for the URL path prefix. This matches if the given 371 | // template is a prefix of the full URL path. See Route.Path() for details on 372 | // the tpl argument. 373 | // 374 | // Note that it does not treat slashes specially ("/foobar/" will be matched by 375 | // the prefix "/foo") so you may want to use a trailing slash here. 376 | // 377 | // Also note that the setting of Router.StrictSlash() has no effect on routes 378 | // with a PathPrefix matcher. 379 | func (r *Route) PathPrefix(tpl string) *Route { 380 | r.err = r.addRegexpMatcher(tpl, regexpTypePrefix) 381 | return r 382 | } 383 | 384 | // Query ---------------------------------------------------------------------- 385 | 386 | // Queries adds a matcher for URL query values. 387 | // It accepts a sequence of key/value pairs. Values may define variables. 388 | // For example: 389 | // 390 | // r := mux.NewRouter().NewRoute() 391 | // r.Queries("foo", "bar", "id", "{id:[0-9]+}") 392 | // 393 | // The above route will only match if the URL contains the defined queries 394 | // values, e.g.: ?foo=bar&id=42. 395 | // 396 | // If the value is an empty string, it will match any value if the key is set. 397 | // 398 | // Variables can define an optional regexp pattern to be matched: 399 | // 400 | // - {name} matches anything until the next slash. 401 | // 402 | // - {name:pattern} matches the given regexp pattern. 403 | func (r *Route) Queries(pairs ...string) *Route { 404 | length := len(pairs) 405 | if length%2 != 0 { 406 | r.err = fmt.Errorf( 407 | "mux: number of parameters must be multiple of 2, got %v", pairs) 408 | return nil 409 | } 410 | for i := 0; i < length; i += 2 { 411 | if r.err = r.addRegexpMatcher(pairs[i]+"="+pairs[i+1], regexpTypeQuery); r.err != nil { 412 | return r 413 | } 414 | } 415 | 416 | return r 417 | } 418 | 419 | // Schemes -------------------------------------------------------------------- 420 | 421 | // schemeMatcher matches the request against URL schemes. 422 | type schemeMatcher []string 423 | 424 | func (m schemeMatcher) Match(r *http.Request, match *RouteMatch) bool { 425 | scheme := r.URL.Scheme 426 | // https://golang.org/pkg/net/http/#Request 427 | // "For [most] server requests, fields other than Path and RawQuery will be 428 | // empty." 429 | // Since we're an http muxer, the scheme is either going to be http or https 430 | // though, so we can just set it based on the tls termination state. 431 | if scheme == "" { 432 | if r.TLS == nil { 433 | scheme = "http" 434 | } else { 435 | scheme = "https" 436 | } 437 | } 438 | return matchInArray(m, scheme) 439 | } 440 | 441 | // Schemes adds a matcher for URL schemes. 442 | // It accepts a sequence of schemes to be matched, e.g.: "http", "https". 443 | // If the request's URL has a scheme set, it will be matched against. 444 | // Generally, the URL scheme will only be set if a previous handler set it, 445 | // such as the ProxyHeaders handler from gorilla/handlers. 446 | // If unset, the scheme will be determined based on the request's TLS 447 | // termination state. 448 | // The first argument to Schemes will be used when constructing a route URL. 449 | func (r *Route) Schemes(schemes ...string) *Route { 450 | for k, v := range schemes { 451 | schemes[k] = strings.ToLower(v) 452 | } 453 | if len(schemes) > 0 { 454 | r.buildScheme = schemes[0] 455 | } 456 | return r.addMatcher(schemeMatcher(schemes)) 457 | } 458 | 459 | // BuildVarsFunc -------------------------------------------------------------- 460 | 461 | // BuildVarsFunc is the function signature used by custom build variable 462 | // functions (which can modify route variables before a route's URL is built). 463 | type BuildVarsFunc func(map[string]string) map[string]string 464 | 465 | // BuildVarsFunc adds a custom function to be used to modify build variables 466 | // before a route's URL is built. 467 | func (r *Route) BuildVarsFunc(f BuildVarsFunc) *Route { 468 | if r.buildVarsFunc != nil { 469 | // compose the old and new functions 470 | old := r.buildVarsFunc 471 | r.buildVarsFunc = func(m map[string]string) map[string]string { 472 | return f(old(m)) 473 | } 474 | } else { 475 | r.buildVarsFunc = f 476 | } 477 | return r 478 | } 479 | 480 | // Subrouter ------------------------------------------------------------------ 481 | 482 | // Subrouter creates a subrouter for the route. 483 | // 484 | // It will test the inner routes only if the parent route matched. For example: 485 | // 486 | // r := mux.NewRouter().NewRoute() 487 | // s := r.Host("www.example.com").Subrouter() 488 | // s.HandleFunc("/products/", ProductsHandler) 489 | // s.HandleFunc("/products/{key}", ProductHandler) 490 | // s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler) 491 | // 492 | // Here, the routes registered in the subrouter won't be tested if the host 493 | // doesn't match. 494 | func (r *Route) Subrouter() *Router { 495 | // initialize a subrouter with a copy of the parent route's configuration 496 | router := &Router{routeConf: copyRouteConf(r.routeConf), namedRoutes: r.namedRoutes} 497 | r.addMatcher(router) 498 | return router 499 | } 500 | 501 | // ---------------------------------------------------------------------------- 502 | // URL building 503 | // ---------------------------------------------------------------------------- 504 | 505 | // URL builds a URL for the route. 506 | // 507 | // It accepts a sequence of key/value pairs for the route variables. For 508 | // example, given this route: 509 | // 510 | // r := mux.NewRouter() 511 | // r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler). 512 | // Name("article") 513 | // 514 | // ...a URL for it can be built using: 515 | // 516 | // url, err := r.Get("article").URL("category", "technology", "id", "42") 517 | // 518 | // ...which will return an url.URL with the following path: 519 | // 520 | // "/articles/technology/42" 521 | // 522 | // This also works for host variables: 523 | // 524 | // r := mux.NewRouter() 525 | // r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler). 526 | // Host("{subdomain}.domain.com"). 527 | // Name("article") 528 | // 529 | // // url.String() will be "http://news.domain.com/articles/technology/42" 530 | // url, err := r.Get("article").URL("subdomain", "news", 531 | // "category", "technology", 532 | // "id", "42") 533 | // 534 | // The scheme of the resulting url will be the first argument that was passed to Schemes: 535 | // 536 | // // url.String() will be "https://example.com" 537 | // r := mux.NewRouter().NewRoute() 538 | // url, err := r.Host("example.com") 539 | // .Schemes("https", "http").URL() 540 | // 541 | // All variables defined in the route are required, and their values must 542 | // conform to the corresponding patterns. 543 | func (r *Route) URL(pairs ...string) (*url.URL, error) { 544 | if r.err != nil { 545 | return nil, r.err 546 | } 547 | values, err := r.prepareVars(pairs...) 548 | if err != nil { 549 | return nil, err 550 | } 551 | var scheme, host, path string 552 | queries := make([]string, 0, len(r.regexp.queries)) 553 | if r.regexp.host != nil { 554 | if host, err = r.regexp.host.url(values); err != nil { 555 | return nil, err 556 | } 557 | scheme = "http" 558 | if r.buildScheme != "" { 559 | scheme = r.buildScheme 560 | } 561 | } 562 | if r.regexp.path != nil { 563 | if path, err = r.regexp.path.url(values); err != nil { 564 | return nil, err 565 | } 566 | } 567 | for _, q := range r.regexp.queries { 568 | var query string 569 | if query, err = q.url(values); err != nil { 570 | return nil, err 571 | } 572 | queries = append(queries, query) 573 | } 574 | return &url.URL{ 575 | Scheme: scheme, 576 | Host: host, 577 | Path: path, 578 | RawQuery: strings.Join(queries, "&"), 579 | }, nil 580 | } 581 | 582 | // URLHost builds the host part of the URL for a route. See Route.URL(). 583 | // 584 | // The route must have a host defined. 585 | func (r *Route) URLHost(pairs ...string) (*url.URL, error) { 586 | if r.err != nil { 587 | return nil, r.err 588 | } 589 | if r.regexp.host == nil { 590 | return nil, errors.New("mux: route doesn't have a host") 591 | } 592 | values, err := r.prepareVars(pairs...) 593 | if err != nil { 594 | return nil, err 595 | } 596 | host, err := r.regexp.host.url(values) 597 | if err != nil { 598 | return nil, err 599 | } 600 | u := &url.URL{ 601 | Scheme: "http", 602 | Host: host, 603 | } 604 | if r.buildScheme != "" { 605 | u.Scheme = r.buildScheme 606 | } 607 | return u, nil 608 | } 609 | 610 | // URLPath builds the path part of the URL for a route. See Route.URL(). 611 | // 612 | // The route must have a path defined. 613 | func (r *Route) URLPath(pairs ...string) (*url.URL, error) { 614 | if r.err != nil { 615 | return nil, r.err 616 | } 617 | if r.regexp.path == nil { 618 | return nil, errors.New("mux: route doesn't have a path") 619 | } 620 | values, err := r.prepareVars(pairs...) 621 | if err != nil { 622 | return nil, err 623 | } 624 | path, err := r.regexp.path.url(values) 625 | if err != nil { 626 | return nil, err 627 | } 628 | return &url.URL{ 629 | Path: path, 630 | }, nil 631 | } 632 | 633 | // GetPathTemplate returns the template used to build the 634 | // route match. 635 | // This is useful for building simple REST API documentation and for instrumentation 636 | // against third-party services. 637 | // An error will be returned if the route does not define a path. 638 | func (r *Route) GetPathTemplate() (string, error) { 639 | if r.err != nil { 640 | return "", r.err 641 | } 642 | if r.regexp.path == nil { 643 | return "", errors.New("mux: route doesn't have a path") 644 | } 645 | return r.regexp.path.template, nil 646 | } 647 | 648 | // GetPathRegexp returns the expanded regular expression used to match route path. 649 | // This is useful for building simple REST API documentation and for instrumentation 650 | // against third-party services. 651 | // An error will be returned if the route does not define a path. 652 | func (r *Route) GetPathRegexp() (string, error) { 653 | if r.err != nil { 654 | return "", r.err 655 | } 656 | if r.regexp.path == nil { 657 | return "", errors.New("mux: route does not have a path") 658 | } 659 | return r.regexp.path.regexp.String(), nil 660 | } 661 | 662 | // GetQueriesRegexp returns the expanded regular expressions used to match the 663 | // route queries. 664 | // This is useful for building simple REST API documentation and for instrumentation 665 | // against third-party services. 666 | // An error will be returned if the route does not have queries. 667 | func (r *Route) GetQueriesRegexp() ([]string, error) { 668 | if r.err != nil { 669 | return nil, r.err 670 | } 671 | if r.regexp.queries == nil { 672 | return nil, errors.New("mux: route doesn't have queries") 673 | } 674 | queries := make([]string, 0, len(r.regexp.queries)) 675 | for _, query := range r.regexp.queries { 676 | queries = append(queries, query.regexp.String()) 677 | } 678 | return queries, nil 679 | } 680 | 681 | // GetQueriesTemplates returns the templates used to build the 682 | // query matching. 683 | // This is useful for building simple REST API documentation and for instrumentation 684 | // against third-party services. 685 | // An error will be returned if the route does not define queries. 686 | func (r *Route) GetQueriesTemplates() ([]string, error) { 687 | if r.err != nil { 688 | return nil, r.err 689 | } 690 | if r.regexp.queries == nil { 691 | return nil, errors.New("mux: route doesn't have queries") 692 | } 693 | queries := make([]string, 0, len(r.regexp.queries)) 694 | for _, query := range r.regexp.queries { 695 | queries = append(queries, query.template) 696 | } 697 | return queries, nil 698 | } 699 | 700 | // GetMethods returns the methods the route matches against 701 | // This is useful for building simple REST API documentation and for instrumentation 702 | // against third-party services. 703 | // An error will be returned if route does not have methods. 704 | func (r *Route) GetMethods() ([]string, error) { 705 | if r.err != nil { 706 | return nil, r.err 707 | } 708 | for _, m := range r.matchers { 709 | if methods, ok := m.(methodMatcher); ok { 710 | return []string(methods), nil 711 | } 712 | } 713 | return nil, errors.New("mux: route doesn't have methods") 714 | } 715 | 716 | // GetHostTemplate returns the template used to build the 717 | // route match. 718 | // This is useful for building simple REST API documentation and for instrumentation 719 | // against third-party services. 720 | // An error will be returned if the route does not define a host. 721 | func (r *Route) GetHostTemplate() (string, error) { 722 | if r.err != nil { 723 | return "", r.err 724 | } 725 | if r.regexp.host == nil { 726 | return "", errors.New("mux: route doesn't have a host") 727 | } 728 | return r.regexp.host.template, nil 729 | } 730 | 731 | // GetVarNames returns the names of all variables added by regexp matchers 732 | // These can be used to know which route variables should be passed into r.URL() 733 | func (r *Route) GetVarNames() ([]string, error) { 734 | if r.err != nil { 735 | return nil, r.err 736 | } 737 | var varNames []string 738 | if r.regexp.host != nil { 739 | varNames = append(varNames, r.regexp.host.varsN...) 740 | } 741 | if r.regexp.path != nil { 742 | varNames = append(varNames, r.regexp.path.varsN...) 743 | } 744 | for _, regx := range r.regexp.queries { 745 | varNames = append(varNames, regx.varsN...) 746 | } 747 | return varNames, nil 748 | } 749 | 750 | // prepareVars converts the route variable pairs into a map. If the route has a 751 | // BuildVarsFunc, it is invoked. 752 | func (r *Route) prepareVars(pairs ...string) (map[string]string, error) { 753 | m, err := mapFromPairsToString(pairs...) 754 | if err != nil { 755 | return nil, err 756 | } 757 | return r.buildVars(m), nil 758 | } 759 | 760 | func (r *Route) buildVars(m map[string]string) map[string]string { 761 | if r.buildVarsFunc != nil { 762 | m = r.buildVarsFunc(m) 763 | } 764 | return m 765 | } 766 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/mux/test_helpers.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Gorilla Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package mux 6 | 7 | import "net/http" 8 | 9 | // SetURLVars sets the URL variables for the given request, to be accessed via 10 | // mux.Vars for testing route behaviour. Arguments are not modified, a shallow 11 | // copy is returned. 12 | // 13 | // This API should only be used for testing purposes; it provides a way to 14 | // inject variables into the request context. Alternatively, URL variables 15 | // can be set by making a route that captures the required variables, 16 | // starting a server and sending the request to that server. 17 | func SetURLVars(r *http.Request, val map[string]string) *http.Request { 18 | return requestWithVars(r, val) 19 | } 20 | -------------------------------------------------------------------------------- /vendor/modules.txt: -------------------------------------------------------------------------------- 1 | # github.com/gorilla/context v1.1.2 2 | ## explicit; go 1.20 3 | github.com/gorilla/context 4 | # github.com/gorilla/mux v1.8.1 5 | ## explicit; go 1.20 6 | github.com/gorilla/mux 7 | --------------------------------------------------------------------------------