[
  {
    "path": ".editorconfig",
    "content": "; https://editorconfig.org/\n\nroot = true\n\n[*]\ninsert_final_newline = true\ncharset = utf-8\ntrim_trailing_whitespace = true\nindent_style = space\nindent_size = 2\n\n[{Makefile,go.mod,go.sum,*.go,.gitmodules}]\nindent_style = tab\nindent_size = 4\n\n[*.md]\nindent_size = 4\ntrim_trailing_whitespace = false\n\neclint_indent_style = unset"
  },
  {
    "path": ".github/workflows/issues.yml",
    "content": "# Add all the issues created to the project.\nname: Add issue or pull request to Project\n\non:\n  issues:\n    types:\n      - opened\n  pull_request_target:\n    types:\n      - opened\n      - reopened\n\njobs:\n  add-to-project:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Add issue to project\n        uses: actions/add-to-project@v0.5.0\n        with:\n          project-url: https://github.com/orgs/gorilla/projects/4\n          github-token: ${{ secrets.ADD_TO_PROJECT_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/security.yml",
    "content": "name: Security\non:\n  push:\n    branches:\n      - main\n  pull_request:\n    branches:\n      - main\npermissions:\n  contents: read\njobs:\n  scan:\n    strategy:\n      matrix:\n        go: ['1.20','1.21']\n      fail-fast: true\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout Code\n        uses: actions/checkout@v3\n\n      - name: Setup Go ${{ matrix.go }}\n        uses: actions/setup-go@v4\n        with:\n          go-version: ${{ matrix.go }}\n          cache: false\n\n      - name: Run GoSec\n        uses: securego/gosec@master\n        with:\n          args: -exclude-dir examples ./...\n\n      - name: Run GoVulnCheck\n        uses: golang/govulncheck-action@v1\n        with:\n          go-version-input: ${{ matrix.go }}\n          go-package: ./...\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: Test\non:\n  push:\n    branches:\n      - main\n  pull_request:\n    branches:\n      - main\npermissions:\n  contents: read\njobs:\n  unit:\n    strategy:\n      matrix:\n        go: ['1.20','1.21']\n        os: [ubuntu-latest, macos-latest, windows-latest]\n      fail-fast: true\n    runs-on: ${{ matrix.os }}\n    steps:\n      - name: Checkout Code\n        uses: actions/checkout@v3\n\n      - name: Setup Go ${{ matrix.go }}\n        uses: actions/setup-go@v4\n        with:\n          go-version: ${{ matrix.go }}\n          cache: false\n\n      - name: Run Tests\n        run: go test -race -cover -coverprofile=coverage -covermode=atomic -v ./...\n\n      - name: Upload coverage to Codecov\n        uses: codecov/codecov-action@v3\n        with:\n          files: ./coverage\n"
  },
  {
    "path": ".github/workflows/verify.yml",
    "content": "name: Verify\non:\n  push:\n    branches:\n      - main\n  pull_request:\n    branches:\n      - main\npermissions:\n  contents: read\njobs:\n  lint:\n    strategy:\n      matrix:\n        go: ['1.20','1.21']\n      fail-fast: true\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout Code\n        uses: actions/checkout@v3\n\n      - name: Setup Go ${{ matrix.go }}\n        uses: actions/setup-go@v4\n        with:\n          go-version: ${{ matrix.go }}\n          cache: false\n\n      - name: Run GolangCI-Lint\n        uses: golangci/golangci-lint-action@v3\n        with:\n          version: v1.53\n          args: --timeout=5m\n"
  },
  {
    "path": ".gitignore",
    "content": "coverage.coverprofile\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2023 The Gorilla Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n\t * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n\t * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n\t * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "Makefile",
    "content": "GO_LINT=$(shell which golangci-lint 2> /dev/null || echo '')\nGO_LINT_URI=github.com/golangci/golangci-lint/cmd/golangci-lint@latest\n\nGO_SEC=$(shell which gosec 2> /dev/null || echo '')\nGO_SEC_URI=github.com/securego/gosec/v2/cmd/gosec@latest\n\nGO_VULNCHECK=$(shell which govulncheck 2> /dev/null || echo '')\nGO_VULNCHECK_URI=golang.org/x/vuln/cmd/govulncheck@latest\n\n.PHONY: golangci-lint\ngolangci-lint:\n\t$(if $(GO_LINT), ,go install $(GO_LINT_URI))\n\t@echo \"##### Running golangci-lint\"\n\tgolangci-lint run -v\n\t\n.PHONY: gosec\ngosec:\n\t$(if $(GO_SEC), ,go install $(GO_SEC_URI))\n\t@echo \"##### Running gosec\"\n\tgosec ./...\n\n.PHONY: govulncheck\ngovulncheck:\n\t$(if $(GO_VULNCHECK), ,go install $(GO_VULNCHECK_URI))\n\t@echo \"##### Running govulncheck\"\n\tgovulncheck ./...\n\n.PHONY: verify\nverify: golangci-lint gosec govulncheck\n\n.PHONY: test\ntest:\n\t@echo \"##### Running tests\"\n\tgo test -race -cover -coverprofile=coverage.coverprofile -covermode=atomic -v ./..."
  },
  {
    "path": "README.md",
    "content": "# gorilla/mux\n\n![testing](https://github.com/gorilla/mux/actions/workflows/test.yml/badge.svg)\n[![codecov](https://codecov.io/github/gorilla/mux/branch/main/graph/badge.svg)](https://codecov.io/github/gorilla/mux)\n[![godoc](https://godoc.org/github.com/gorilla/mux?status.svg)](https://godoc.org/github.com/gorilla/mux)\n[![sourcegraph](https://sourcegraph.com/github.com/gorilla/mux/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/mux?badge)\n\n\n![Gorilla Logo](https://github.com/gorilla/.github/assets/53367916/d92caabf-98e0-473e-bfbf-ab554ba435e5)\n\nPackage `gorilla/mux` implements a request router and dispatcher for matching incoming requests to\ntheir respective handler.\n\nThe 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:\n\n* It implements the `http.Handler` interface so it is compatible with the standard `http.ServeMux`.\n* Requests can be matched based on URL host, path, path prefix, schemes, header and query values, HTTP methods or using custom matchers.\n* URL hosts, paths and query values can have variables with an optional regular expression.\n* Registered URLs can be built, or \"reversed\", which helps maintaining references to resources.\n* 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.\n\n---\n\n* [Install](#install)\n* [Examples](#examples)\n* [Matching Routes](#matching-routes)\n* [Static Files](#static-files)\n* [Serving Single Page Applications](#serving-single-page-applications) (e.g. React, Vue, Ember.js, etc.)\n* [Registered URLs](#registered-urls)\n* [Walking Routes](#walking-routes)\n* [Graceful Shutdown](#graceful-shutdown)\n* [Middleware](#middleware)\n* [Handling CORS Requests](#handling-cors-requests)\n* [Testing Handlers](#testing-handlers)\n* [Full Example](#full-example)\n\n---\n\n## Install\n\nWith a [correctly configured](https://golang.org/doc/install#testing) Go toolchain:\n\n```sh\ngo get -u github.com/gorilla/mux\n```\n\n## Examples\n\nLet's start registering a couple of URL paths and handlers:\n\n```go\nfunc main() {\n    r := mux.NewRouter()\n    r.HandleFunc(\"/\", HomeHandler)\n    r.HandleFunc(\"/products\", ProductsHandler)\n    r.HandleFunc(\"/articles\", ArticlesHandler)\n    http.Handle(\"/\", r)\n}\n```\n\nHere 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.\n\nPaths 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:\n\n```go\nr := mux.NewRouter()\nr.HandleFunc(\"/products/{key}\", ProductHandler)\nr.HandleFunc(\"/articles/{category}/\", ArticlesCategoryHandler)\nr.HandleFunc(\"/articles/{category}/{id:[0-9]+}\", ArticleHandler)\n```\n\nThe names are used to create a map of route variables which can be retrieved calling `mux.Vars()`:\n\n```go\nfunc ArticlesCategoryHandler(w http.ResponseWriter, r *http.Request) {\n    vars := mux.Vars(r)\n    w.WriteHeader(http.StatusOK)\n    fmt.Fprintf(w, \"Category: %v\\n\", vars[\"category\"])\n}\n```\n\nAnd this is all you need to know about the basic usage. More advanced options are explained below.\n\n### Matching Routes\n\nRoutes can also be restricted to a domain or subdomain. Just define a host pattern to be matched. They can also have variables:\n\n```go\nr := mux.NewRouter()\n// Only matches if domain is \"www.example.com\".\nr.Host(\"www.example.com\")\n// Matches a dynamic subdomain.\nr.Host(\"{subdomain:[a-z]+}.example.com\")\n```\n\nThere are several other matchers that can be added. To match path prefixes:\n\n```go\nr.PathPrefix(\"/products/\")\n```\n\n...or HTTP methods:\n\n```go\nr.Methods(\"GET\", \"POST\")\n```\n\n...or URL schemes:\n\n```go\nr.Schemes(\"https\")\n```\n\n...or header values:\n\n```go\nr.Headers(\"X-Requested-With\", \"XMLHttpRequest\")\n```\n\n...or query values:\n\n```go\nr.Queries(\"key\", \"value\")\n```\n\n...or to use a custom matcher function:\n\n```go\nr.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool {\n    return r.ProtoMajor == 0\n})\n```\n\n...and finally, it is possible to combine several matchers in a single route:\n\n```go\nr.HandleFunc(\"/products\", ProductsHandler).\n  Host(\"www.example.com\").\n  Methods(\"GET\").\n  Schemes(\"http\")\n```\n\nRoutes are tested in the order they were added to the router. If two routes match, the first one wins:\n\n```go\nr := mux.NewRouter()\nr.HandleFunc(\"/specific\", specificHandler)\nr.PathPrefix(\"/\").Handler(catchAllHandler)\n```\n\nSetting 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\".\n\nFor 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:\n\n```go\nr := mux.NewRouter()\ns := r.Host(\"www.example.com\").Subrouter()\n```\n\nThen register routes in the subrouter:\n\n```go\ns.HandleFunc(\"/products/\", ProductsHandler)\ns.HandleFunc(\"/products/{key}\", ProductHandler)\ns.HandleFunc(\"/articles/{category}/{id:[0-9]+}\", ArticleHandler)\n```\n\nThe 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.\n\nSubrouters 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.\n\nThere's one more thing about subroutes. When a subrouter has a path prefix, the inner routes use it as base for their paths:\n\n```go\nr := mux.NewRouter()\ns := r.PathPrefix(\"/products\").Subrouter()\n// \"/products/\"\ns.HandleFunc(\"/\", ProductsHandler)\n// \"/products/{key}/\"\ns.HandleFunc(\"/{key}/\", ProductHandler)\n// \"/products/{key}/details\"\ns.HandleFunc(\"/{key}/details\", ProductDetailsHandler)\n```\n\n\n### Static Files\n\nNote that the path provided to `PathPrefix()` represents a \"wildcard\": calling\n`PathPrefix(\"/static/\").Handler(...)` means that the handler will be passed any\nrequest that matches \"/static/\\*\". This makes it easy to serve static files with mux:\n\n```go\nfunc main() {\n    var dir string\n\n    flag.StringVar(&dir, \"dir\", \".\", \"the directory to serve files from. Defaults to the current dir\")\n    flag.Parse()\n    r := mux.NewRouter()\n\n    // This will serve files under http://localhost:8000/static/<filename>\n    r.PathPrefix(\"/static/\").Handler(http.StripPrefix(\"/static/\", http.FileServer(http.Dir(dir))))\n\n    srv := &http.Server{\n        Handler:      r,\n        Addr:         \"127.0.0.1:8000\",\n        // Good practice: enforce timeouts for servers you create!\n        WriteTimeout: 15 * time.Second,\n        ReadTimeout:  15 * time.Second,\n    }\n\n    log.Fatal(srv.ListenAndServe())\n}\n```\n\n### Serving Single Page Applications\n\nMost of the time it makes sense to serve your SPA on a separate web server from your API,\nbut sometimes it's desirable to serve them both from one place. It's possible to write a simple\nhandler for serving your SPA (for use with React Router's [BrowserRouter](https://reacttraining.com/react-router/web/api/BrowserRouter) for example), and leverage\nmux's powerful routing for your API endpoints.\n\n```go\npackage main\n\nimport (\n\t\"encoding/json\"\n\t\"log\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"time\"\n\n\t\"github.com/gorilla/mux\"\n)\n\n// spaHandler implements the http.Handler interface, so we can use it\n// to respond to HTTP requests. The path to the static directory and\n// path to the index file within that static directory are used to\n// serve the SPA in the given static directory.\ntype spaHandler struct {\n\tstaticPath string\n\tindexPath  string\n}\n\n// ServeHTTP inspects the URL path to locate a file within the static dir\n// on the SPA handler. If a file is found, it will be served. If not, the\n// file located at the index path on the SPA handler will be served. This\n// is suitable behavior for serving an SPA (single page application).\nfunc (h spaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n\t// Join internally call path.Clean to prevent directory traversal\n\tpath := filepath.Join(h.staticPath, r.URL.Path)\n\n\t// check whether a file exists or is a directory at the given path\n\tfi, err := os.Stat(path)\n\tif os.IsNotExist(err) || fi.IsDir() {\n\t\t// file does not exist or path is a directory, serve index.html\n\t\thttp.ServeFile(w, r, filepath.Join(h.staticPath, h.indexPath))\n\t\treturn\n\t}\n\n\tif err != nil {\n\t\t// if we got an error (that wasn't that the file doesn't exist) stating the\n\t\t// file, return a 500 internal server error and stop\n\t\thttp.Error(w, err.Error(), http.StatusInternalServerError)\n        return\n\t}\n\n\t// otherwise, use http.FileServer to serve the static file\n\thttp.FileServer(http.Dir(h.staticPath)).ServeHTTP(w, r)\n}\n\nfunc main() {\n\trouter := mux.NewRouter()\n\n\trouter.HandleFunc(\"/api/health\", func(w http.ResponseWriter, r *http.Request) {\n\t\t// an example API handler\n\t\tjson.NewEncoder(w).Encode(map[string]bool{\"ok\": true})\n\t})\n\n\tspa := spaHandler{staticPath: \"build\", indexPath: \"index.html\"}\n\trouter.PathPrefix(\"/\").Handler(spa)\n\n\tsrv := &http.Server{\n\t\tHandler: router,\n\t\tAddr:    \"127.0.0.1:8000\",\n\t\t// Good practice: enforce timeouts for servers you create!\n\t\tWriteTimeout: 15 * time.Second,\n\t\tReadTimeout:  15 * time.Second,\n\t}\n\n\tlog.Fatal(srv.ListenAndServe())\n}\n```\n\n### Registered URLs\n\nNow let's see how to build registered URLs.\n\nRoutes 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:\n\n```go\nr := mux.NewRouter()\nr.HandleFunc(\"/articles/{category}/{id:[0-9]+}\", ArticleHandler).\n  Name(\"article\")\n```\n\nTo 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:\n\n```go\nurl, err := r.Get(\"article\").URL(\"category\", \"technology\", \"id\", \"42\")\n```\n\n...and the result will be a `url.URL` with the following path:\n\n```\n\"/articles/technology/42\"\n```\n\nThis also works for host and query value variables:\n\n```go\nr := mux.NewRouter()\nr.Host(\"{subdomain}.example.com\").\n  Path(\"/articles/{category}/{id:[0-9]+}\").\n  Queries(\"filter\", \"{filter}\").\n  HandlerFunc(ArticleHandler).\n  Name(\"article\")\n\n// url.String() will be \"http://news.example.com/articles/technology/42?filter=gorilla\"\nurl, err := r.Get(\"article\").URL(\"subdomain\", \"news\",\n                                 \"category\", \"technology\",\n                                 \"id\", \"42\",\n                                 \"filter\", \"gorilla\")\n```\n\nAll 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.\n\nRegex support also exists for matching Headers within a route. For example, we could do:\n\n```go\nr.HeadersRegexp(\"Content-Type\", \"application/(text|json)\")\n```\n\n...and the route will match both requests with a Content-Type of `application/json` as well as `application/text`\n\nThere'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:\n\n```go\n// \"http://news.example.com/\"\nhost, err := r.Get(\"article\").URLHost(\"subdomain\", \"news\")\n\n// \"/articles/technology/42\"\npath, err := r.Get(\"article\").URLPath(\"category\", \"technology\", \"id\", \"42\")\n```\n\nAnd if you use subrouters, host and path defined separately can be built as well:\n\n```go\nr := mux.NewRouter()\ns := r.Host(\"{subdomain}.example.com\").Subrouter()\ns.Path(\"/articles/{category}/{id:[0-9]+}\").\n  HandlerFunc(ArticleHandler).\n  Name(\"article\")\n\n// \"http://news.example.com/articles/technology/42\"\nurl, err := r.Get(\"article\").URL(\"subdomain\", \"news\",\n                                 \"category\", \"technology\",\n                                 \"id\", \"42\")\n```\n\nTo find all the required variables for a given route when calling `URL()`, the method `GetVarNames()` is available:\n```go\nr := mux.NewRouter()\nr.Host(\"{domain}\").\n    Path(\"/{group}/{item_id}\").\n    Queries(\"some_data1\", \"{some_data1}\").\n    Queries(\"some_data2\", \"{some_data2}\").\n    Name(\"article\")\n\n// Will print [domain group item_id some_data1 some_data2] <nil>\nfmt.Println(r.Get(\"article\").GetVarNames())\n\n```\n### Walking Routes\n\nThe `Walk` function on `mux.Router` can be used to visit all of the routes that are registered on a router. For example,\nthe following prints all of the registered routes:\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/gorilla/mux\"\n)\n\nfunc handler(w http.ResponseWriter, r *http.Request) {\n\treturn\n}\n\nfunc main() {\n\tr := mux.NewRouter()\n\tr.HandleFunc(\"/\", handler)\n\tr.HandleFunc(\"/products\", handler).Methods(\"POST\")\n\tr.HandleFunc(\"/articles\", handler).Methods(\"GET\")\n\tr.HandleFunc(\"/articles/{id}\", handler).Methods(\"GET\", \"PUT\")\n\tr.HandleFunc(\"/authors\", handler).Queries(\"surname\", \"{surname}\")\n\terr := r.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {\n\t\tpathTemplate, err := route.GetPathTemplate()\n\t\tif err == nil {\n\t\t\tfmt.Println(\"ROUTE:\", pathTemplate)\n\t\t}\n\t\tpathRegexp, err := route.GetPathRegexp()\n\t\tif err == nil {\n\t\t\tfmt.Println(\"Path regexp:\", pathRegexp)\n\t\t}\n\t\tqueriesTemplates, err := route.GetQueriesTemplates()\n\t\tif err == nil {\n\t\t\tfmt.Println(\"Queries templates:\", strings.Join(queriesTemplates, \",\"))\n\t\t}\n\t\tqueriesRegexps, err := route.GetQueriesRegexp()\n\t\tif err == nil {\n\t\t\tfmt.Println(\"Queries regexps:\", strings.Join(queriesRegexps, \",\"))\n\t\t}\n\t\tmethods, err := route.GetMethods()\n\t\tif err == nil {\n\t\t\tfmt.Println(\"Methods:\", strings.Join(methods, \",\"))\n\t\t}\n\t\tfmt.Println()\n\t\treturn nil\n\t})\n\n\tif err != nil {\n\t\tfmt.Println(err)\n\t}\n\n\thttp.Handle(\"/\", r)\n}\n```\n\n### Graceful Shutdown\n\nGo 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`:\n\n```go\npackage main\n\nimport (\n    \"context\"\n    \"flag\"\n    \"log\"\n    \"net/http\"\n    \"os\"\n    \"os/signal\"\n    \"time\"\n\n    \"github.com/gorilla/mux\"\n)\n\nfunc main() {\n    var wait time.Duration\n    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\")\n    flag.Parse()\n\n    r := mux.NewRouter()\n    // Add your routes as needed\n\n    srv := &http.Server{\n        Addr:         \"0.0.0.0:8080\",\n        // Good practice to set timeouts to avoid Slowloris attacks.\n        WriteTimeout: time.Second * 15,\n        ReadTimeout:  time.Second * 15,\n        IdleTimeout:  time.Second * 60,\n        Handler: r, // Pass our instance of gorilla/mux in.\n    }\n\n    // Run our server in a goroutine so that it doesn't block.\n    go func() {\n        if err := srv.ListenAndServe(); err != nil {\n            log.Println(err)\n        }\n    }()\n\n    c := make(chan os.Signal, 1)\n    // We'll accept graceful shutdowns when quit via SIGINT (Ctrl+C)\n    // SIGKILL, SIGQUIT or SIGTERM (Ctrl+/) will not be caught.\n    signal.Notify(c, os.Interrupt)\n\n    // Block until we receive our signal.\n    <-c\n\n    // Create a deadline to wait for.\n    ctx, cancel := context.WithTimeout(context.Background(), wait)\n    defer cancel()\n    // Doesn't block if no connections, but will otherwise wait\n    // until the timeout deadline.\n    srv.Shutdown(ctx)\n    // Optionally, you could run srv.Shutdown in a goroutine and block on\n    // <-ctx.Done() if your application should wait for other services\n    // to finalize based on context cancellation.\n    log.Println(\"shutting down\")\n    os.Exit(0)\n}\n```\n\n### Middleware\n\nMux 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.\nMiddlewares 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.\n\nMux middlewares are defined using the de facto standard type:\n\n```go\ntype MiddlewareFunc func(http.Handler) http.Handler\n```\n\nTypically, 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.\n\nA very basic middleware which logs the URI of the request being handled could be written as:\n\n```go\nfunc loggingMiddleware(next http.Handler) http.Handler {\n    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n        // Do stuff here\n        log.Println(r.RequestURI)\n        // Call the next handler, which can be another middleware in the chain, or the final handler.\n        next.ServeHTTP(w, r)\n    })\n}\n```\n\nMiddlewares can be added to a router using `Router.Use()`:\n\n```go\nr := mux.NewRouter()\nr.HandleFunc(\"/\", handler)\nr.Use(loggingMiddleware)\n```\n\nA more complex authentication middleware, which maps session token to users, could be written as:\n\n```go\n// Define our struct\ntype authenticationMiddleware struct {\n\ttokenUsers map[string]string\n}\n\n// Initialize it somewhere\nfunc (amw *authenticationMiddleware) Populate() {\n\tamw.tokenUsers[\"00000000\"] = \"user0\"\n\tamw.tokenUsers[\"aaaaaaaa\"] = \"userA\"\n\tamw.tokenUsers[\"05f717e5\"] = \"randomUser\"\n\tamw.tokenUsers[\"deadbeef\"] = \"user0\"\n}\n\n// Middleware function, which will be called for each request\nfunc (amw *authenticationMiddleware) Middleware(next http.Handler) http.Handler {\n    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n        token := r.Header.Get(\"X-Session-Token\")\n\n        if user, found := amw.tokenUsers[token]; found {\n        \t// We found the token in our map\n        \tlog.Printf(\"Authenticated user %s\\n\", user)\n        \t// Pass down the request to the next middleware (or final handler)\n        \tnext.ServeHTTP(w, r)\n        } else {\n        \t// Write an error and stop the handler chain\n        \thttp.Error(w, \"Forbidden\", http.StatusForbidden)\n        }\n    })\n}\n```\n\n```go\nr := mux.NewRouter()\nr.HandleFunc(\"/\", handler)\n\namw := authenticationMiddleware{tokenUsers: make(map[string]string)}\namw.Populate()\n\nr.Use(amw.Middleware)\n```\n\nNote: 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.\n\n### Handling CORS Requests\n\n[CORSMethodMiddleware](https://godoc.org/github.com/gorilla/mux#CORSMethodMiddleware) intends to make it easier to strictly set the `Access-Control-Allow-Methods` response header.\n\n* You will still need to use your own CORS handler to set the other CORS headers such as `Access-Control-Allow-Origin`\n* 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\n* If you do not specify any methods, then:\n> _Important_: there must be an `OPTIONS` method matcher for the middleware to set the headers.\n\nHere is an example of using `CORSMethodMiddleware` along with a custom `OPTIONS` handler to set all the required CORS headers:\n\n```go\npackage main\n\nimport (\n\t\"net/http\"\n\t\"github.com/gorilla/mux\"\n)\n\nfunc main() {\n    r := mux.NewRouter()\n\n    // IMPORTANT: you must specify an OPTIONS method matcher for the middleware to set CORS headers\n    r.HandleFunc(\"/foo\", fooHandler).Methods(http.MethodGet, http.MethodPut, http.MethodPatch, http.MethodOptions)\n    r.Use(mux.CORSMethodMiddleware(r))\n    \n    http.ListenAndServe(\":8080\", r)\n}\n\nfunc fooHandler(w http.ResponseWriter, r *http.Request) {\n    w.Header().Set(\"Access-Control-Allow-Origin\", \"*\")\n    if r.Method == http.MethodOptions {\n        return\n    }\n\n    w.Write([]byte(\"foo\"))\n}\n```\n\nAnd an request to `/foo` using something like:\n\n```bash\ncurl localhost:8080/foo -v\n```\n\nWould look like:\n\n```bash\n*   Trying ::1...\n* TCP_NODELAY set\n* Connected to localhost (::1) port 8080 (#0)\n> GET /foo HTTP/1.1\n> Host: localhost:8080\n> User-Agent: curl/7.59.0\n> Accept: */*\n> \n< HTTP/1.1 200 OK\n< Access-Control-Allow-Methods: GET,PUT,PATCH,OPTIONS\n< Access-Control-Allow-Origin: *\n< Date: Fri, 28 Jun 2019 20:13:30 GMT\n< Content-Length: 3\n< Content-Type: text/plain; charset=utf-8\n< \n* Connection #0 to host localhost left intact\nfoo\n```\n\n### Testing Handlers\n\nTesting 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_.\n\nFirst, our simple HTTP handler:\n\n```go\n// endpoints.go\npackage main\n\nfunc HealthCheckHandler(w http.ResponseWriter, r *http.Request) {\n    // A very simple health check.\n    w.Header().Set(\"Content-Type\", \"application/json\")\n    w.WriteHeader(http.StatusOK)\n\n    // In the future we could report back on the status of our DB, or our cache\n    // (e.g. Redis) by performing a simple PING, and include them in the response.\n    io.WriteString(w, `{\"alive\": true}`)\n}\n\nfunc main() {\n    r := mux.NewRouter()\n    r.HandleFunc(\"/health\", HealthCheckHandler)\n\n    log.Fatal(http.ListenAndServe(\"localhost:8080\", r))\n}\n```\n\nOur test code:\n\n```go\n// endpoints_test.go\npackage main\n\nimport (\n    \"net/http\"\n    \"net/http/httptest\"\n    \"testing\"\n)\n\nfunc TestHealthCheckHandler(t *testing.T) {\n    // Create a request to pass to our handler. We don't have any query parameters for now, so we'll\n    // pass 'nil' as the third parameter.\n    req, err := http.NewRequest(\"GET\", \"/health\", nil)\n    if err != nil {\n        t.Fatal(err)\n    }\n\n    // We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response.\n    rr := httptest.NewRecorder()\n    handler := http.HandlerFunc(HealthCheckHandler)\n\n    // Our handlers satisfy http.Handler, so we can call their ServeHTTP method\n    // directly and pass in our Request and ResponseRecorder.\n    handler.ServeHTTP(rr, req)\n\n    // Check the status code is what we expect.\n    if status := rr.Code; status != http.StatusOK {\n        t.Errorf(\"handler returned wrong status code: got %v want %v\",\n            status, http.StatusOK)\n    }\n\n    // Check the response body is what we expect.\n    expected := `{\"alive\": true}`\n    if rr.Body.String() != expected {\n        t.Errorf(\"handler returned unexpected body: got %v want %v\",\n            rr.Body.String(), expected)\n    }\n}\n```\n\nIn the case that our routes have [variables](#examples), we can pass those in the request. We could write\n[table-driven tests](https://dave.cheney.net/2013/06/09/writing-table-driven-tests-in-go) to test multiple\npossible route variables as needed.\n\n```go\n// endpoints.go\nfunc main() {\n    r := mux.NewRouter()\n    // A route with a route variable:\n    r.HandleFunc(\"/metrics/{type}\", MetricsHandler)\n\n    log.Fatal(http.ListenAndServe(\"localhost:8080\", r))\n}\n```\n\nOur test file, with a table-driven test of `routeVariables`:\n\n```go\n// endpoints_test.go\nfunc TestMetricsHandler(t *testing.T) {\n    tt := []struct{\n        routeVariable string\n        shouldPass bool\n    }{\n        {\"goroutines\", true},\n        {\"heap\", true},\n        {\"counters\", true},\n        {\"queries\", true},\n        {\"adhadaeqm3k\", false},\n    }\n\n    for _, tc := range tt {\n        path := fmt.Sprintf(\"/metrics/%s\", tc.routeVariable)\n        req, err := http.NewRequest(\"GET\", path, nil)\n        if err != nil {\n            t.Fatal(err)\n        }\n\n        rr := httptest.NewRecorder()\n\t\n\t// To add the vars to the context, \n\t// we need to create a router through which we can pass the request.\n\trouter := mux.NewRouter()\n        router.HandleFunc(\"/metrics/{type}\", MetricsHandler)\n        router.ServeHTTP(rr, req)\n\n        // In this case, our MetricsHandler returns a non-200 response\n        // for a route variable it doesn't know about.\n        if rr.Code == http.StatusOK && !tc.shouldPass {\n            t.Errorf(\"handler should have failed on routeVariable %s: got %v want %v\",\n                tc.routeVariable, rr.Code, http.StatusOK)\n        }\n    }\n}\n```\n\n## Full Example\n\nHere's a complete, runnable example of a small `mux` based server:\n\n```go\npackage main\n\nimport (\n    \"net/http\"\n    \"log\"\n    \"github.com/gorilla/mux\"\n)\n\nfunc YourHandler(w http.ResponseWriter, r *http.Request) {\n    w.Write([]byte(\"Gorilla!\\n\"))\n}\n\nfunc main() {\n    r := mux.NewRouter()\n    // Routes consist of a path and a handler function.\n    r.HandleFunc(\"/\", YourHandler)\n\n    // Bind to a port and pass our router in\n    log.Fatal(http.ListenAndServe(\":8000\", r))\n}\n```\n\n## License\n\nBSD licensed. See the LICENSE file for details.\n"
  },
  {
    "path": "bench_test.go",
    "content": "// Copyright 2012 The Gorilla Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\npackage mux\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n)\n\nfunc BenchmarkMux(b *testing.B) {\n\trouter := new(Router)\n\thandler := func(w http.ResponseWriter, r *http.Request) {}\n\trouter.HandleFunc(\"/v1/{v1}\", handler)\n\n\trequest, _ := http.NewRequest(\"GET\", \"/v1/anything\", nil)\n\tfor i := 0; i < b.N; i++ {\n\t\trouter.ServeHTTP(nil, request)\n\t}\n}\n\nfunc BenchmarkMuxSimple(b *testing.B) {\n\trouter := new(Router)\n\thandler := func(w http.ResponseWriter, r *http.Request) {}\n\trouter.HandleFunc(\"/status\", handler)\n\n\ttestCases := []struct {\n\t\tname                 string\n\t\tomitRouteFromContext bool\n\t}{\n\t\t{\n\t\t\tname:                 \"default\",\n\t\t\tomitRouteFromContext: false,\n\t\t},\n\t\t{\n\t\t\tname:                 \"omit route from ctx\",\n\t\t\tomitRouteFromContext: true,\n\t\t},\n\t}\n\tfor _, tc := range testCases {\n\t\tb.Run(tc.name, func(b *testing.B) {\n\t\t\trouter.OmitRouteFromContext(tc.omitRouteFromContext)\n\n\t\t\trequest, _ := http.NewRequest(\"GET\", \"/status\", nil)\n\t\t\tb.ReportAllocs()\n\t\t\tb.ResetTimer()\n\t\t\tfor i := 0; i < b.N; i++ {\n\t\t\t\trouter.ServeHTTP(nil, request)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkMuxAlternativeInRegexp(b *testing.B) {\n\trouter := new(Router)\n\thandler := func(w http.ResponseWriter, r *http.Request) {}\n\trouter.HandleFunc(\"/v1/{v1:(?:a|b)}\", handler)\n\n\trequestA, _ := http.NewRequest(\"GET\", \"/v1/a\", nil)\n\trequestB, _ := http.NewRequest(\"GET\", \"/v1/b\", nil)\n\tfor i := 0; i < b.N; i++ {\n\t\trouter.ServeHTTP(nil, requestA)\n\t\trouter.ServeHTTP(nil, requestB)\n\t}\n}\n\nfunc BenchmarkManyPathVariables(b *testing.B) {\n\trouter := new(Router)\n\thandler := func(w http.ResponseWriter, r *http.Request) {}\n\trouter.HandleFunc(\"/v1/{v1}/{v2}/{v3}/{v4}/{v5}\", handler)\n\n\tmatchingRequest, _ := http.NewRequest(\"GET\", \"/v1/1/2/3/4/5\", nil)\n\tnotMatchingRequest, _ := http.NewRequest(\"GET\", \"/v1/1/2/3/4\", nil)\n\trecorder := httptest.NewRecorder()\n\tfor i := 0; i < b.N; i++ {\n\t\trouter.ServeHTTP(nil, matchingRequest)\n\t\trouter.ServeHTTP(recorder, notMatchingRequest)\n\t}\n}\n"
  },
  {
    "path": "doc.go",
    "content": "// Copyright 2012 The Gorilla Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\n/*\nPackage mux implements a request router and dispatcher.\n\nThe name mux stands for \"HTTP request multiplexer\". Like the standard\nhttp.ServeMux, mux.Router matches incoming requests against a list of\nregistered routes and calls a handler for the route that matches the URL\nor other conditions. The main features are:\n\n  - Requests can be matched based on URL host, path, path prefix, schemes,\n    header and query values, HTTP methods or using custom matchers.\n  - URL hosts, paths and query values can have variables with an optional\n    regular expression.\n  - Registered URLs can be built, or \"reversed\", which helps maintaining\n    references to resources.\n  - Routes can be used as subrouters: nested routes are only tested if the\n    parent route matches. This is useful to define groups of routes that\n    share common conditions like a host, a path prefix or other repeated\n    attributes. As a bonus, this optimizes request matching.\n  - It implements the http.Handler interface so it is compatible with the\n    standard http.ServeMux.\n\nLet's start registering a couple of URL paths and handlers:\n\n\tfunc main() {\n\t\tr := mux.NewRouter()\n\t\tr.HandleFunc(\"/\", HomeHandler)\n\t\tr.HandleFunc(\"/products\", ProductsHandler)\n\t\tr.HandleFunc(\"/articles\", ArticlesHandler)\n\t\thttp.Handle(\"/\", r)\n\t}\n\nHere we register three routes mapping URL paths to handlers. This is\nequivalent to how http.HandleFunc() works: if an incoming request URL matches\none of the paths, the corresponding handler is called passing\n(http.ResponseWriter, *http.Request) as parameters.\n\nPaths can have variables. They are defined using the format {name} or\n{name:pattern}. If a regular expression pattern is not defined, the matched\nvariable will be anything until the next slash. For example:\n\n\tr := mux.NewRouter()\n\tr.HandleFunc(\"/products/{key}\", ProductHandler)\n\tr.HandleFunc(\"/articles/{category}/\", ArticlesCategoryHandler)\n\tr.HandleFunc(\"/articles/{category}/{id:[0-9]+}\", ArticleHandler)\n\nGroups can be used inside patterns, as long as they are non-capturing (?:re). For example:\n\n\tr.HandleFunc(\"/articles/{category}/{sort:(?:asc|desc|new)}\", ArticlesCategoryHandler)\n\nThe names are used to create a map of route variables which can be retrieved\ncalling mux.Vars():\n\n\tvars := mux.Vars(request)\n\tcategory := vars[\"category\"]\n\nNote that if any capturing groups are present, mux will panic() during parsing. To prevent\nthis, convert any capturing groups to non-capturing, e.g. change \"/{sort:(asc|desc)}\" to\n\"/{sort:(?:asc|desc)}\". This is a change from prior versions which behaved unpredictably\nwhen capturing groups were present.\n\nAnd this is all you need to know about the basic usage. More advanced options\nare explained below.\n\nRoutes can also be restricted to a domain or subdomain. Just define a host\npattern to be matched. They can also have variables:\n\n\tr := mux.NewRouter()\n\t// Only matches if domain is \"www.example.com\".\n\tr.Host(\"www.example.com\")\n\t// Matches a dynamic subdomain.\n\tr.Host(\"{subdomain:[a-z]+}.domain.com\")\n\nThere are several other matchers that can be added. To match path prefixes:\n\n\tr.PathPrefix(\"/products/\")\n\n...or HTTP methods:\n\n\tr.Methods(\"GET\", \"POST\")\n\n...or URL schemes:\n\n\tr.Schemes(\"https\")\n\n...or header values:\n\n\tr.Headers(\"X-Requested-With\", \"XMLHttpRequest\")\n\n...or query values:\n\n\tr.Queries(\"key\", \"value\")\n\n...or to use a custom matcher function:\n\n\tr.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool {\n\t\treturn r.ProtoMajor == 0\n\t})\n\n...and finally, it is possible to combine several matchers in a single route:\n\n\tr.HandleFunc(\"/products\", ProductsHandler).\n\t  Host(\"www.example.com\").\n\t  Methods(\"GET\").\n\t  Schemes(\"http\")\n\nSetting the same matching conditions again and again can be boring, so we have\na way to group several routes that share the same requirements.\nWe call it \"subrouting\".\n\nFor example, let's say we have several URLs that should only match when the\nhost is \"www.example.com\". Create a route for that host and get a \"subrouter\"\nfrom it:\n\n\tr := mux.NewRouter()\n\ts := r.Host(\"www.example.com\").Subrouter()\n\nThen register routes in the subrouter:\n\n\ts.HandleFunc(\"/products/\", ProductsHandler)\n\ts.HandleFunc(\"/products/{key}\", ProductHandler)\n\ts.HandleFunc(\"/articles/{category}/{id:[0-9]+}\"), ArticleHandler)\n\nThe three URL paths we registered above will only be tested if the domain is\n\"www.example.com\", because the subrouter is tested first. This is not\nonly convenient, but also optimizes request matching. You can create\nsubrouters combining any attribute matchers accepted by a route.\n\nSubrouters can be used to create domain or path \"namespaces\": you define\nsubrouters in a central place and then parts of the app can register its\npaths relatively to a given subrouter.\n\nThere's one more thing about subroutes. When a subrouter has a path prefix,\nthe inner routes use it as base for their paths:\n\n\tr := mux.NewRouter()\n\ts := r.PathPrefix(\"/products\").Subrouter()\n\t// \"/products/\"\n\ts.HandleFunc(\"/\", ProductsHandler)\n\t// \"/products/{key}/\"\n\ts.HandleFunc(\"/{key}/\", ProductHandler)\n\t// \"/products/{key}/details\"\n\ts.HandleFunc(\"/{key}/details\", ProductDetailsHandler)\n\nNote that the path provided to PathPrefix() represents a \"wildcard\": calling\nPathPrefix(\"/static/\").Handler(...) means that the handler will be passed any\nrequest that matches \"/static/*\". This makes it easy to serve static files with mux:\n\n\tfunc main() {\n\t\tvar dir string\n\n\t\tflag.StringVar(&dir, \"dir\", \".\", \"the directory to serve files from. Defaults to the current dir\")\n\t\tflag.Parse()\n\t\tr := mux.NewRouter()\n\n\t\t// This will serve files under http://localhost:8000/static/<filename>\n\t\tr.PathPrefix(\"/static/\").Handler(http.StripPrefix(\"/static/\", http.FileServer(http.Dir(dir))))\n\n\t\tsrv := &http.Server{\n\t\t\tHandler:      r,\n\t\t\tAddr:         \"127.0.0.1:8000\",\n\t\t\t// Good practice: enforce timeouts for servers you create!\n\t\t\tWriteTimeout: 15 * time.Second,\n\t\t\tReadTimeout:  15 * time.Second,\n\t\t}\n\n\t\tlog.Fatal(srv.ListenAndServe())\n\t}\n\nNow let's see how to build registered URLs.\n\nRoutes can be named. All routes that define a name can have their URLs built,\nor \"reversed\". We define a name calling Name() on a route. For example:\n\n\tr := mux.NewRouter()\n\tr.HandleFunc(\"/articles/{category}/{id:[0-9]+}\", ArticleHandler).\n\t  Name(\"article\")\n\nTo build a URL, get the route and call the URL() method, passing a sequence of\nkey/value pairs for the route variables. For the previous route, we would do:\n\n\turl, err := r.Get(\"article\").URL(\"category\", \"technology\", \"id\", \"42\")\n\n...and the result will be a url.URL with the following path:\n\n\t\"/articles/technology/42\"\n\nThis also works for host and query value variables:\n\n\tr := mux.NewRouter()\n\tr.Host(\"{subdomain}.domain.com\").\n\t  Path(\"/articles/{category}/{id:[0-9]+}\").\n\t  Queries(\"filter\", \"{filter}\").\n\t  HandlerFunc(ArticleHandler).\n\t  Name(\"article\")\n\n\t// url.String() will be \"http://news.domain.com/articles/technology/42?filter=gorilla\"\n\turl, err := r.Get(\"article\").URL(\"subdomain\", \"news\",\n\t                                 \"category\", \"technology\",\n\t                                 \"id\", \"42\",\n\t                                 \"filter\", \"gorilla\")\n\nAll variables defined in the route are required, and their values must\nconform to the corresponding patterns. These requirements guarantee that a\ngenerated URL will always match a registered route -- the only exception is\nfor explicitly defined \"build-only\" routes which never match.\n\nRegex support also exists for matching Headers within a route. For example, we could do:\n\n\tr.HeadersRegexp(\"Content-Type\", \"application/(text|json)\")\n\n...and the route will match both requests with a Content-Type of `application/json` as well as\n`application/text`\n\nThere's also a way to build only the URL host or path for a route:\nuse the methods URLHost() or URLPath() instead. For the previous route,\nwe would do:\n\n\t// \"http://news.domain.com/\"\n\thost, err := r.Get(\"article\").URLHost(\"subdomain\", \"news\")\n\n\t// \"/articles/technology/42\"\n\tpath, err := r.Get(\"article\").URLPath(\"category\", \"technology\", \"id\", \"42\")\n\nAnd if you use subrouters, host and path defined separately can be built\nas well:\n\n\tr := mux.NewRouter()\n\ts := r.Host(\"{subdomain}.domain.com\").Subrouter()\n\ts.Path(\"/articles/{category}/{id:[0-9]+}\").\n\t  HandlerFunc(ArticleHandler).\n\t  Name(\"article\")\n\n\t// \"http://news.domain.com/articles/technology/42\"\n\turl, err := r.Get(\"article\").URL(\"subdomain\", \"news\",\n\t                                 \"category\", \"technology\",\n\t                                 \"id\", \"42\")\n\nMux 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.\n\n\ttype MiddlewareFunc func(http.Handler) http.Handler\n\nTypically, 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).\n\nA very basic middleware which logs the URI of the request being handled could be written as:\n\n\tfunc simpleMw(next http.Handler) http.Handler {\n\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t// Do stuff here\n\t\t\tlog.Println(r.RequestURI)\n\t\t\t// Call the next handler, which can be another middleware in the chain, or the final handler.\n\t\t\tnext.ServeHTTP(w, r)\n\t\t})\n\t}\n\nMiddlewares can be added to a router using `Router.Use()`:\n\n\tr := mux.NewRouter()\n\tr.HandleFunc(\"/\", handler)\n\tr.Use(simpleMw)\n\nA more complex authentication middleware, which maps session token to users, could be written as:\n\n\t// Define our struct\n\ttype authenticationMiddleware struct {\n\t\ttokenUsers map[string]string\n\t}\n\n\t// Initialize it somewhere\n\tfunc (amw *authenticationMiddleware) Populate() {\n\t\tamw.tokenUsers[\"00000000\"] = \"user0\"\n\t\tamw.tokenUsers[\"aaaaaaaa\"] = \"userA\"\n\t\tamw.tokenUsers[\"05f717e5\"] = \"randomUser\"\n\t\tamw.tokenUsers[\"deadbeef\"] = \"user0\"\n\t}\n\n\t// Middleware function, which will be called for each request\n\tfunc (amw *authenticationMiddleware) Middleware(next http.Handler) http.Handler {\n\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\ttoken := r.Header.Get(\"X-Session-Token\")\n\n\t\t\tif user, found := amw.tokenUsers[token]; found {\n\t\t\t\t// We found the token in our map\n\t\t\t\tlog.Printf(\"Authenticated user %s\\n\", user)\n\t\t\t\tnext.ServeHTTP(w, r)\n\t\t\t} else {\n\t\t\t\thttp.Error(w, \"Forbidden\", http.StatusForbidden)\n\t\t\t}\n\t\t})\n\t}\n\n\tr := mux.NewRouter()\n\tr.HandleFunc(\"/\", handler)\n\n\tamw := authenticationMiddleware{tokenUsers: make(map[string]string)}\n\tamw.Populate()\n\n\tr.Use(amw.Middleware)\n\nNote: 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.\n*/\npackage mux\n"
  },
  {
    "path": "example_authentication_middleware_test.go",
    "content": "package mux_test\n\nimport (\n\t\"log\"\n\t\"net/http\"\n\n\t\"github.com/gorilla/mux\"\n)\n\n// Define our struct\ntype authenticationMiddleware struct {\n\ttokenUsers map[string]string\n}\n\n// Initialize it somewhere\nfunc (amw *authenticationMiddleware) Populate() {\n\tamw.tokenUsers[\"00000000\"] = \"user0\"\n\tamw.tokenUsers[\"aaaaaaaa\"] = \"userA\"\n\tamw.tokenUsers[\"05f717e5\"] = \"randomUser\"\n\tamw.tokenUsers[\"deadbeef\"] = \"user0\"\n}\n\n// Middleware function, which will be called for each request\nfunc (amw *authenticationMiddleware) Middleware(next http.Handler) http.Handler {\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\ttoken := r.Header.Get(\"X-Session-Token\")\n\n\t\tif user, found := amw.tokenUsers[token]; found {\n\t\t\t// We found the token in our map\n\t\t\tlog.Printf(\"Authenticated user %s\\n\", user)\n\t\t\tnext.ServeHTTP(w, r)\n\t\t} else {\n\t\t\thttp.Error(w, \"Forbidden\", http.StatusForbidden)\n\t\t}\n\t})\n}\n\nfunc Example_authenticationMiddleware() {\n\tr := mux.NewRouter()\n\tr.HandleFunc(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\t// Do something here\n\t})\n\tamw := authenticationMiddleware{make(map[string]string)}\n\tamw.Populate()\n\tr.Use(amw.Middleware)\n}\n"
  },
  {
    "path": "example_cors_method_middleware_test.go",
    "content": "package mux_test\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\n\t\"github.com/gorilla/mux\"\n)\n\nfunc ExampleCORSMethodMiddleware() {\n\tr := mux.NewRouter()\n\n\tr.HandleFunc(\"/foo\", func(w http.ResponseWriter, r *http.Request) {\n\t\t// Handle the request\n\t}).Methods(http.MethodGet, http.MethodPut, http.MethodPatch)\n\tr.HandleFunc(\"/foo\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Set(\"Access-Control-Allow-Origin\", \"http://example.com\")\n\t\tw.Header().Set(\"Access-Control-Max-Age\", \"86400\")\n\t}).Methods(http.MethodOptions)\n\n\tr.Use(mux.CORSMethodMiddleware(r))\n\n\trw := httptest.NewRecorder()\n\treq, _ := http.NewRequest(\"OPTIONS\", \"/foo\", nil)                 // needs to be OPTIONS\n\treq.Header.Set(\"Access-Control-Request-Method\", \"POST\")           // needs to be non-empty\n\treq.Header.Set(\"Access-Control-Request-Headers\", \"Authorization\") // needs to be non-empty\n\treq.Header.Set(\"Origin\", \"http://example.com\")                    // needs to be non-empty\n\n\tr.ServeHTTP(rw, req)\n\n\tfmt.Println(rw.Header().Get(\"Access-Control-Allow-Methods\"))\n\tfmt.Println(rw.Header().Get(\"Access-Control-Allow-Origin\"))\n\t// Output:\n\t// GET,PUT,PATCH,OPTIONS\n\t// http://example.com\n}\n"
  },
  {
    "path": "example_route_test.go",
    "content": "package mux_test\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/gorilla/mux\"\n)\n\n// This example demonstrates setting a regular expression matcher for\n// the header value. A plain word will match any value that contains a\n// matching substring as if the pattern was wrapped with `.*`.\nfunc ExampleRoute_HeadersRegexp() {\n\tr := mux.NewRouter()\n\troute := r.NewRoute().HeadersRegexp(\"Accept\", \"html\")\n\n\treq1, _ := http.NewRequest(\"GET\", \"example.com\", nil)\n\treq1.Header.Add(\"Accept\", \"text/plain\")\n\treq1.Header.Add(\"Accept\", \"text/html\")\n\n\treq2, _ := http.NewRequest(\"GET\", \"example.com\", nil)\n\treq2.Header.Set(\"Accept\", \"application/xhtml+xml\")\n\n\tmatchInfo := &mux.RouteMatch{}\n\tfmt.Printf(\"Match: %v %q\\n\", route.Match(req1, matchInfo), req1.Header[\"Accept\"])\n\tfmt.Printf(\"Match: %v %q\\n\", route.Match(req2, matchInfo), req2.Header[\"Accept\"])\n\t// Output:\n\t// Match: true [\"text/plain\" \"text/html\"]\n\t// Match: true [\"application/xhtml+xml\"]\n}\n\n// This example demonstrates setting a strict regular expression matcher\n// for the header value. Using the start and end of string anchors, the\n// value must be an exact match.\nfunc ExampleRoute_HeadersRegexp_exactMatch() {\n\tr := mux.NewRouter()\n\troute := r.NewRoute().HeadersRegexp(\"Origin\", \"^https://example.co$\")\n\n\tyes, _ := http.NewRequest(\"GET\", \"example.co\", nil)\n\tyes.Header.Set(\"Origin\", \"https://example.co\")\n\n\tno, _ := http.NewRequest(\"GET\", \"example.co.uk\", nil)\n\tno.Header.Set(\"Origin\", \"https://example.co.uk\")\n\n\tmatchInfo := &mux.RouteMatch{}\n\tfmt.Printf(\"Match: %v %q\\n\", route.Match(yes, matchInfo), yes.Header[\"Origin\"])\n\tfmt.Printf(\"Match: %v %q\\n\", route.Match(no, matchInfo), no.Header[\"Origin\"])\n\t// Output:\n\t// Match: true [\"https://example.co\"]\n\t// Match: false [\"https://example.co.uk\"]\n}\n"
  },
  {
    "path": "example_route_vars_test.go",
    "content": "package mux_test\n\nimport (\n\t\"fmt\"\n\t\"github.com/gorilla/mux\"\n)\n\n// This example demonstrates building a dynamic URL using\n// required vars and values retrieve from another source\nfunc ExampleRoute_GetVarNames() {\n\tr := mux.NewRouter()\n\n\troute := r.Host(\"{domain}\").\n\t\tPath(\"/{group}/{item_id}\").\n\t\tQueries(\"some_data1\", \"{some_data1}\").\n\t\tQueries(\"some_data2_and_3\", \"{some_data2}.{some_data3}\")\n\n\tdataSource := func(key string) string {\n\t\treturn \"my_value_for_\" + key\n\t}\n\n\tvarNames, _ := route.GetVarNames()\n\n\tpairs := make([]string, 0, len(varNames)*2)\n\n\tfor _, varName := range varNames {\n\t\tpairs = append(pairs, varName, dataSource(varName))\n\t}\n\n\turl, err := route.URL(pairs...)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(url.String())\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/gorilla/mux\n\ngo 1.20\n"
  },
  {
    "path": "middleware.go",
    "content": "package mux\n\nimport (\n\t\"net/http\"\n\t\"strings\"\n)\n\n// MiddlewareFunc is a function which receives an http.Handler and returns another http.Handler.\n// Typically, the returned handler is a closure which does something with the http.ResponseWriter and http.Request passed\n// to it, and then calls the handler passed as parameter to the MiddlewareFunc.\ntype MiddlewareFunc func(http.Handler) http.Handler\n\n// middleware interface is anything which implements a MiddlewareFunc named Middleware.\ntype middleware interface {\n\tMiddleware(handler http.Handler) http.Handler\n}\n\n// Middleware allows MiddlewareFunc to implement the middleware interface.\nfunc (mw MiddlewareFunc) Middleware(handler http.Handler) http.Handler {\n\treturn mw(handler)\n}\n\n// 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.\nfunc (r *Router) Use(mwf ...MiddlewareFunc) {\n\tfor _, fn := range mwf {\n\t\tr.middlewares = append(r.middlewares, fn)\n\t}\n}\n\n// 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.\nfunc (r *Router) useInterface(mw middleware) {\n\tr.middlewares = append(r.middlewares, mw)\n}\n\n// RouteMiddleware -------------------------------------------------------------\n\n// 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 Route. Route middleware are executed after the Router middleware but before the Route handler.\nfunc (r *Route) Use(mwf ...MiddlewareFunc) *Route {\n\tfor _, fn := range mwf {\n\t\tr.middlewares = append(r.middlewares, fn)\n\t}\n\n\treturn r\n}\n\n// useInterface 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 Route. Route middleware are executed after the Router middleware but before the Route handler.\nfunc (r *Route) useInterface(mw middleware) {\n\tr.middlewares = append(r.middlewares, mw)\n}\n\n// CORSMethodMiddleware automatically sets the Access-Control-Allow-Methods response header\n// on requests for routes that have an OPTIONS method matcher to all the method matchers on\n// the route. Routes that do not explicitly handle OPTIONS requests will not be processed\n// by the middleware. See examples for usage.\nfunc CORSMethodMiddleware(r *Router) MiddlewareFunc {\n\treturn func(next http.Handler) http.Handler {\n\t\treturn http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {\n\t\t\tallMethods, err := getAllMethodsForRoute(r, req)\n\t\t\tif err == nil {\n\t\t\t\tfor _, v := range allMethods {\n\t\t\t\t\tif v == http.MethodOptions {\n\t\t\t\t\t\tw.Header().Set(\"Access-Control-Allow-Methods\", strings.Join(allMethods, \",\"))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tnext.ServeHTTP(w, req)\n\t\t})\n\t}\n}\n\n// getAllMethodsForRoute returns all the methods from method matchers matching a given\n// request.\nfunc getAllMethodsForRoute(r *Router, req *http.Request) ([]string, error) {\n\tvar allMethods []string\n\n\tfor _, route := range r.routes {\n\t\tvar match RouteMatch\n\t\tif route.Match(req, &match) || match.MatchErr == ErrMethodMismatch {\n\t\t\tmethods, err := route.GetMethods()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tallMethods = append(allMethods, methods...)\n\t\t}\n\t}\n\n\treturn allMethods, nil\n}\n"
  },
  {
    "path": "middleware_test.go",
    "content": "package mux\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"testing\"\n)\n\ntype testMiddleware struct {\n\ttimesCalled uint\n}\n\nfunc (tm *testMiddleware) Middleware(h http.Handler) http.Handler {\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\ttm.timesCalled++\n\t\th.ServeHTTP(w, r)\n\t})\n}\n\nfunc dummyHandler(w http.ResponseWriter, r *http.Request) {}\n\nfunc TestMiddlewareAdd(t *testing.T) {\n\trouter := NewRouter()\n\trouter.HandleFunc(\"/\", dummyHandler).Methods(\"GET\")\n\n\tmw := &testMiddleware{}\n\n\trouter.useInterface(mw)\n\tif len(router.middlewares) != 1 || router.middlewares[0] != mw {\n\t\tt.Fatal(\"Middleware interface was not added correctly\")\n\t}\n\n\trouter.Use(mw.Middleware)\n\tif len(router.middlewares) != 2 {\n\t\tt.Fatal(\"Middleware method was not added correctly\")\n\t}\n\n\tbanalMw := func(handler http.Handler) http.Handler {\n\t\treturn handler\n\t}\n\trouter.Use(banalMw)\n\tif len(router.middlewares) != 3 {\n\t\tt.Fatal(\"Middleware function was not added correctly\")\n\t}\n\n\troute := router.HandleFunc(\"/route\", dummyHandler)\n\troute.useInterface(mw)\n\tif len(route.middlewares) != 1 {\n\t\tt.Fatal(\"Route middleware function was not added correctly\")\n\t}\n\n\troute.Use(banalMw)\n\tif len(route.middlewares) != 2 {\n\t\tt.Fatal(\"Route middleware function was not added correctly\")\n\t}\n}\n\nfunc TestMiddleware(t *testing.T) {\n\trouter := NewRouter()\n\trouter.HandleFunc(\"/\", dummyHandler).Methods(\"GET\")\n\n\tmw := &testMiddleware{}\n\trouter.useInterface(mw)\n\n\trw := NewRecorder()\n\treq := newRequest(\"GET\", \"/\")\n\n\tt.Run(\"regular middleware call\", func(t *testing.T) {\n\t\trouter.ServeHTTP(rw, req)\n\t\tif mw.timesCalled != 1 {\n\t\t\tt.Fatalf(\"Expected %d calls, but got only %d\", 1, mw.timesCalled)\n\t\t}\n\t})\n\n\tt.Run(\"not called for 404\", func(t *testing.T) {\n\t\treq = newRequest(\"GET\", \"/not/found\")\n\t\trouter.ServeHTTP(rw, req)\n\t\tif mw.timesCalled != 1 {\n\t\t\tt.Fatalf(\"Expected %d calls, but got only %d\", 1, mw.timesCalled)\n\t\t}\n\t})\n\n\tt.Run(\"not called for method mismatch\", func(t *testing.T) {\n\t\treq = newRequest(\"POST\", \"/\")\n\t\trouter.ServeHTTP(rw, req)\n\t\tif mw.timesCalled != 1 {\n\t\t\tt.Fatalf(\"Expected %d calls, but got only %d\", 1, mw.timesCalled)\n\t\t}\n\t})\n\n\tt.Run(\"regular call using function middleware\", func(t *testing.T) {\n\t\trouter.Use(mw.Middleware)\n\t\treq = newRequest(\"GET\", \"/\")\n\t\trouter.ServeHTTP(rw, req)\n\t\tif mw.timesCalled != 3 {\n\t\t\tt.Fatalf(\"Expected %d calls, but got only %d\", 3, mw.timesCalled)\n\t\t}\n\t})\n\n\tt.Run(\"regular call using route middleware func\", func(t *testing.T) {\n\t\trouter.HandleFunc(\"/route\", dummyHandler).Use(mw.Middleware)\n\t\treq = newRequest(\"GET\", \"/route\")\n\t\trouter.ServeHTTP(rw, req)\n\t\tif mw.timesCalled != 6 {\n\t\t\tt.Fatalf(\"Expected %d calls, but got only %d\", 6, mw.timesCalled)\n\t\t}\n\t})\n\n\tt.Run(\"regular call using route middleware interface\", func(t *testing.T) {\n\t\trouter.HandleFunc(\"/route\", dummyHandler).useInterface(mw)\n\t\treq = newRequest(\"GET\", \"/route\")\n\t\trouter.ServeHTTP(rw, req)\n\t\tif mw.timesCalled != 9 {\n\t\t\tt.Fatalf(\"Expected %d calls, but got only %d\", 9, mw.timesCalled)\n\t\t}\n\t})\n}\n\nfunc TestMiddlewareSubrouter(t *testing.T) {\n\trouter := NewRouter()\n\trouter.HandleFunc(\"/\", dummyHandler).Methods(\"GET\")\n\n\tsubrouter := router.PathPrefix(\"/sub\").Subrouter()\n\tsubrouter.HandleFunc(\"/x\", dummyHandler).Methods(\"GET\")\n\n\tmw := &testMiddleware{}\n\tsubrouter.useInterface(mw)\n\n\trw := NewRecorder()\n\treq := newRequest(\"GET\", \"/\")\n\n\tt.Run(\"not called for route outside subrouter\", func(t *testing.T) {\n\t\trouter.ServeHTTP(rw, req)\n\t\tif mw.timesCalled != 0 {\n\t\t\tt.Fatalf(\"Expected %d calls, but got only %d\", 0, mw.timesCalled)\n\t\t}\n\t})\n\n\tt.Run(\"not called for subrouter root 404\", func(t *testing.T) {\n\t\treq = newRequest(\"GET\", \"/sub/\")\n\t\trouter.ServeHTTP(rw, req)\n\t\tif mw.timesCalled != 0 {\n\t\t\tt.Fatalf(\"Expected %d calls, but got only %d\", 0, mw.timesCalled)\n\t\t}\n\t})\n\n\tt.Run(\"called once for route inside subrouter\", func(t *testing.T) {\n\t\treq = newRequest(\"GET\", \"/sub/x\")\n\t\trouter.ServeHTTP(rw, req)\n\t\tif mw.timesCalled != 1 {\n\t\t\tt.Fatalf(\"Expected %d calls, but got only %d\", 1, mw.timesCalled)\n\t\t}\n\t})\n\n\tt.Run(\"not called for 404 inside subrouter\", func(t *testing.T) {\n\t\treq = newRequest(\"GET\", \"/sub/not/found\")\n\t\trouter.ServeHTTP(rw, req)\n\t\tif mw.timesCalled != 1 {\n\t\t\tt.Fatalf(\"Expected %d calls, but got only %d\", 1, mw.timesCalled)\n\t\t}\n\t})\n\n\tt.Run(\"middleware added to router\", func(t *testing.T) {\n\t\trouter.useInterface(mw)\n\n\t\tt.Run(\"called once for route outside subrouter\", func(t *testing.T) {\n\t\t\treq = newRequest(\"GET\", \"/\")\n\t\t\trouter.ServeHTTP(rw, req)\n\t\t\tif mw.timesCalled != 2 {\n\t\t\t\tt.Fatalf(\"Expected %d calls, but got only %d\", 2, mw.timesCalled)\n\t\t\t}\n\t\t})\n\n\t\tt.Run(\"called twice for route inside subrouter\", func(t *testing.T) {\n\t\t\treq = newRequest(\"GET\", \"/sub/x\")\n\t\t\trouter.ServeHTTP(rw, req)\n\t\t\tif mw.timesCalled != 4 {\n\t\t\t\tt.Fatalf(\"Expected %d calls, but got only %d\", 4, mw.timesCalled)\n\t\t\t}\n\t\t})\n\t})\n}\n\nfunc TestMiddlewareExecution(t *testing.T) {\n\tmwStr := []byte(\"Middleware\\n\")\n\thandlerStr := []byte(\"Logic\\n\")\n\n\thandlerFunc := func(w http.ResponseWriter, e *http.Request) {\n\t\t_, err := w.Write(handlerStr)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Failed writing HTTP response: %v\", err)\n\t\t}\n\t}\n\n\trouter := NewRouter()\n\trouter.HandleFunc(\"/\", handlerFunc)\n\n\tt.Run(\"responds normally without middleware\", func(t *testing.T) {\n\t\trw := NewRecorder()\n\t\treq := newRequest(\"GET\", \"/\")\n\n\t\trouter.ServeHTTP(rw, req)\n\n\t\tif !bytes.Equal(rw.Body.Bytes(), handlerStr) {\n\t\t\tt.Fatal(\"Handler response is not what it should be\")\n\t\t}\n\t})\n\n\tt.Run(\"responds with handler and middleware response\", func(t *testing.T) {\n\t\trw := NewRecorder()\n\t\treq := newRequest(\"GET\", \"/\")\n\n\t\trouter.Use(func(h http.Handler) http.Handler {\n\t\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\t_, err := w.Write(mwStr)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"Failed writing HTTP response: %v\", err)\n\t\t\t\t}\n\t\t\t\th.ServeHTTP(w, r)\n\t\t\t})\n\t\t})\n\n\t\trouter.ServeHTTP(rw, req)\n\t\tif !bytes.Equal(rw.Body.Bytes(), append(mwStr, handlerStr...)) {\n\t\t\tt.Fatal(\"Middleware + handler response is not what it should be\")\n\t\t}\n\t})\n\n\tt.Run(\"responds with handler, middleware and route middleware response\", func(t *testing.T) {\n\t\trouteMwStr := []byte(\"Route Middleware\\n\")\n\t\trw := NewRecorder()\n\t\treq := newRequest(\"GET\", \"/route\")\n\n\t\trouter.HandleFunc(\"/route\", handlerFunc).Use(func(h http.Handler) http.Handler {\n\t\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\t_, err := w.Write(routeMwStr)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"Failed writing HTTP response: %v\", err)\n\t\t\t\t}\n\t\t\t\th.ServeHTTP(w, r)\n\t\t\t})\n\t\t})\n\n\t\trouter.ServeHTTP(rw, req)\n\t\texpectedString := append(append(mwStr, routeMwStr...), handlerStr...)\n\t\tif !bytes.Equal(rw.Body.Bytes(), expectedString) {\n\t\t\tfmt.Println(rw.Body.String())\n\t\t\tt.Fatal(\"Middleware + handler response is not what it should be\")\n\t\t}\n\t})\n}\n\nfunc TestMiddlewareNotFound(t *testing.T) {\n\tmwStr := []byte(\"Middleware\\n\")\n\thandlerStr := []byte(\"Logic\\n\")\n\n\trouter := NewRouter()\n\trouter.HandleFunc(\"/\", func(w http.ResponseWriter, e *http.Request) {\n\t\t_, err := w.Write(handlerStr)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Failed writing HTTP response: %v\", err)\n\t\t}\n\t})\n\trouter.Use(func(h http.Handler) http.Handler {\n\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t_, err := w.Write(mwStr)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Failed writing HTTP response: %v\", err)\n\t\t\t}\n\t\t\th.ServeHTTP(w, r)\n\t\t})\n\t})\n\n\t// Test not found call with default handler\n\tt.Run(\"not called\", func(t *testing.T) {\n\t\trw := NewRecorder()\n\t\treq := newRequest(\"GET\", \"/notfound\")\n\n\t\trouter.ServeHTTP(rw, req)\n\t\tif bytes.Contains(rw.Body.Bytes(), mwStr) {\n\t\t\tt.Fatal(\"Middleware was called for a 404\")\n\t\t}\n\t})\n\n\tt.Run(\"not called with custom not found handler\", func(t *testing.T) {\n\t\trw := NewRecorder()\n\t\treq := newRequest(\"GET\", \"/notfound\")\n\n\t\trouter.NotFoundHandler = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {\n\t\t\t_, err := rw.Write([]byte(\"Custom 404 handler\"))\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Failed writing HTTP response: %v\", err)\n\t\t\t}\n\t\t})\n\t\trouter.ServeHTTP(rw, req)\n\n\t\tif bytes.Contains(rw.Body.Bytes(), mwStr) {\n\t\t\tt.Fatal(\"Middleware was called for a custom 404\")\n\t\t}\n\t})\n}\n\nfunc TestMiddlewareMethodMismatch(t *testing.T) {\n\tmwStr := []byte(\"Middleware\\n\")\n\thandlerStr := []byte(\"Logic\\n\")\n\n\trouter := NewRouter()\n\trouter.HandleFunc(\"/\", func(w http.ResponseWriter, e *http.Request) {\n\t\t_, err := w.Write(handlerStr)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Failed writing HTTP response: %v\", err)\n\t\t}\n\t}).Methods(\"GET\")\n\n\trouter.Use(func(h http.Handler) http.Handler {\n\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t_, err := w.Write(mwStr)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Failed writing HTTP response: %v\", err)\n\t\t\t}\n\t\t\th.ServeHTTP(w, r)\n\t\t})\n\t})\n\n\tt.Run(\"not called\", func(t *testing.T) {\n\t\trw := NewRecorder()\n\t\treq := newRequest(\"POST\", \"/\")\n\n\t\trouter.ServeHTTP(rw, req)\n\t\tif bytes.Contains(rw.Body.Bytes(), mwStr) {\n\t\t\tt.Fatal(\"Middleware was called for a method mismatch\")\n\t\t}\n\t})\n\n\tt.Run(\"not called with custom method not allowed handler\", func(t *testing.T) {\n\t\trw := NewRecorder()\n\t\treq := newRequest(\"POST\", \"/\")\n\n\t\trouter.MethodNotAllowedHandler = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {\n\t\t\t_, err := rw.Write([]byte(\"Method not allowed\"))\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Failed writing HTTP response: %v\", err)\n\t\t\t}\n\t\t})\n\t\trouter.ServeHTTP(rw, req)\n\n\t\tif bytes.Contains(rw.Body.Bytes(), mwStr) {\n\t\t\tt.Fatal(\"Middleware was called for a method mismatch\")\n\t\t}\n\t})\n}\n\nfunc TestMiddlewareNotFoundSubrouter(t *testing.T) {\n\tmwStr := []byte(\"Middleware\\n\")\n\thandlerStr := []byte(\"Logic\\n\")\n\n\trouter := NewRouter()\n\trouter.HandleFunc(\"/\", func(w http.ResponseWriter, e *http.Request) {\n\t\t_, err := w.Write(handlerStr)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Failed writing HTTP response: %v\", err)\n\t\t}\n\t})\n\n\tsubrouter := router.PathPrefix(\"/sub/\").Subrouter()\n\tsubrouter.HandleFunc(\"/\", func(w http.ResponseWriter, e *http.Request) {\n\t\t_, err := w.Write(handlerStr)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Failed writing HTTP response: %v\", err)\n\t\t}\n\t})\n\n\trouter.Use(func(h http.Handler) http.Handler {\n\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t_, err := w.Write(mwStr)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Failed writing HTTP response: %v\", err)\n\t\t\t}\n\t\t\th.ServeHTTP(w, r)\n\t\t})\n\t})\n\n\tt.Run(\"not called\", func(t *testing.T) {\n\t\trw := NewRecorder()\n\t\treq := newRequest(\"GET\", \"/sub/notfound\")\n\n\t\trouter.ServeHTTP(rw, req)\n\t\tif bytes.Contains(rw.Body.Bytes(), mwStr) {\n\t\t\tt.Fatal(\"Middleware was called for a 404\")\n\t\t}\n\t})\n\n\tt.Run(\"not called with custom not found handler\", func(t *testing.T) {\n\t\trw := NewRecorder()\n\t\treq := newRequest(\"GET\", \"/sub/notfound\")\n\n\t\tsubrouter.NotFoundHandler = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {\n\t\t\t_, err := rw.Write([]byte(\"Custom 404 handler\"))\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Failed writing HTTP response: %v\", err)\n\t\t\t}\n\t\t})\n\t\trouter.ServeHTTP(rw, req)\n\n\t\tif bytes.Contains(rw.Body.Bytes(), mwStr) {\n\t\t\tt.Fatal(\"Middleware was called for a custom 404\")\n\t\t}\n\t})\n}\n\nfunc TestMiddlewareMethodMismatchSubrouter(t *testing.T) {\n\tmwStr := []byte(\"Middleware\\n\")\n\thandlerStr := []byte(\"Logic\\n\")\n\n\trouter := NewRouter()\n\trouter.HandleFunc(\"/\", func(w http.ResponseWriter, e *http.Request) {\n\t\t_, err := w.Write(handlerStr)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Failed writing HTTP response: %v\", err)\n\t\t}\n\t})\n\n\tsubrouter := router.PathPrefix(\"/sub/\").Subrouter()\n\tsubrouter.HandleFunc(\"/\", func(w http.ResponseWriter, e *http.Request) {\n\t\t_, err := w.Write(handlerStr)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Failed writing HTTP response: %v\", err)\n\t\t}\n\t}).Methods(\"GET\")\n\n\trouter.Use(func(h http.Handler) http.Handler {\n\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t_, err := w.Write(mwStr)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Failed writing HTTP response: %v\", err)\n\t\t\t}\n\t\t\th.ServeHTTP(w, r)\n\t\t})\n\t})\n\n\tt.Run(\"not called\", func(t *testing.T) {\n\t\trw := NewRecorder()\n\t\treq := newRequest(\"POST\", \"/sub/\")\n\n\t\trouter.ServeHTTP(rw, req)\n\t\tif bytes.Contains(rw.Body.Bytes(), mwStr) {\n\t\t\tt.Fatal(\"Middleware was called for a method mismatch\")\n\t\t}\n\t})\n\n\tt.Run(\"not called with custom method not allowed handler\", func(t *testing.T) {\n\t\trw := NewRecorder()\n\t\treq := newRequest(\"POST\", \"/sub/\")\n\n\t\trouter.MethodNotAllowedHandler = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {\n\t\t\t_, err := rw.Write([]byte(\"Method not allowed\"))\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Failed writing HTTP response: %v\", err)\n\t\t\t}\n\t\t})\n\t\trouter.ServeHTTP(rw, req)\n\n\t\tif bytes.Contains(rw.Body.Bytes(), mwStr) {\n\t\t\tt.Fatal(\"Middleware was called for a method mismatch\")\n\t\t}\n\t})\n}\n\nfunc TestCORSMethodMiddleware(t *testing.T) {\n\ttestCases := []struct {\n\t\tname                                    string\n\t\tregisterRoutes                          func(r *Router)\n\t\trequestHeader                           http.Header\n\t\trequestMethod                           string\n\t\trequestPath                             string\n\t\texpectedAccessControlAllowMethodsHeader string\n\t\texpectedResponse                        string\n\t}{\n\t\t{\n\t\t\tname: \"does not set without OPTIONS matcher\",\n\t\t\tregisterRoutes: func(r *Router) {\n\t\t\t\tr.HandleFunc(\"/foo\", stringHandler(\"a\")).Methods(http.MethodGet, http.MethodPut, http.MethodPatch)\n\t\t\t},\n\t\t\trequestMethod:                           \"GET\",\n\t\t\trequestPath:                             \"/foo\",\n\t\t\texpectedAccessControlAllowMethodsHeader: \"\",\n\t\t\texpectedResponse:                        \"a\",\n\t\t},\n\t\t{\n\t\t\tname: \"sets on non OPTIONS\",\n\t\t\tregisterRoutes: func(r *Router) {\n\t\t\t\tr.HandleFunc(\"/foo\", stringHandler(\"a\")).Methods(http.MethodGet, http.MethodPut, http.MethodPatch)\n\t\t\t\tr.HandleFunc(\"/foo\", stringHandler(\"b\")).Methods(http.MethodOptions)\n\t\t\t},\n\t\t\trequestMethod:                           \"GET\",\n\t\t\trequestPath:                             \"/foo\",\n\t\t\texpectedAccessControlAllowMethodsHeader: \"GET,PUT,PATCH,OPTIONS\",\n\t\t\texpectedResponse:                        \"a\",\n\t\t},\n\t\t{\n\t\t\tname: \"sets without preflight headers\",\n\t\t\tregisterRoutes: func(r *Router) {\n\t\t\t\tr.HandleFunc(\"/foo\", stringHandler(\"a\")).Methods(http.MethodGet, http.MethodPut, http.MethodPatch)\n\t\t\t\tr.HandleFunc(\"/foo\", stringHandler(\"b\")).Methods(http.MethodOptions)\n\t\t\t},\n\t\t\trequestMethod:                           \"OPTIONS\",\n\t\t\trequestPath:                             \"/foo\",\n\t\t\texpectedAccessControlAllowMethodsHeader: \"GET,PUT,PATCH,OPTIONS\",\n\t\t\texpectedResponse:                        \"b\",\n\t\t},\n\t\t{\n\t\t\tname: \"does not set on error\",\n\t\t\tregisterRoutes: func(r *Router) {\n\t\t\t\tr.HandleFunc(\"/foo\", stringHandler(\"a\"))\n\t\t\t},\n\t\t\trequestMethod:                           \"OPTIONS\",\n\t\t\trequestPath:                             \"/foo\",\n\t\t\texpectedAccessControlAllowMethodsHeader: \"\",\n\t\t\texpectedResponse:                        \"a\",\n\t\t},\n\t\t{\n\t\t\tname: \"sets header on valid preflight\",\n\t\t\tregisterRoutes: func(r *Router) {\n\t\t\t\tr.HandleFunc(\"/foo\", stringHandler(\"a\")).Methods(http.MethodGet, http.MethodPut, http.MethodPatch)\n\t\t\t\tr.HandleFunc(\"/foo\", stringHandler(\"b\")).Methods(http.MethodOptions)\n\t\t\t},\n\t\t\trequestMethod: \"OPTIONS\",\n\t\t\trequestPath:   \"/foo\",\n\t\t\trequestHeader: http.Header{\n\t\t\t\t\"Access-Control-Request-Method\":  []string{\"GET\"},\n\t\t\t\t\"Access-Control-Request-Headers\": []string{\"Authorization\"},\n\t\t\t\t\"Origin\":                         []string{\"http://example.com\"},\n\t\t\t},\n\t\t\texpectedAccessControlAllowMethodsHeader: \"GET,PUT,PATCH,OPTIONS\",\n\t\t\texpectedResponse:                        \"b\",\n\t\t},\n\t\t{\n\t\t\tname: \"does not set methods from unmatching routes\",\n\t\t\tregisterRoutes: func(r *Router) {\n\t\t\t\tr.HandleFunc(\"/foo\", stringHandler(\"c\")).Methods(http.MethodDelete)\n\t\t\t\tr.HandleFunc(\"/foo/bar\", stringHandler(\"a\")).Methods(http.MethodGet, http.MethodPut, http.MethodPatch)\n\t\t\t\tr.HandleFunc(\"/foo/bar\", stringHandler(\"b\")).Methods(http.MethodOptions)\n\t\t\t},\n\t\t\trequestMethod: \"OPTIONS\",\n\t\t\trequestPath:   \"/foo/bar\",\n\t\t\trequestHeader: http.Header{\n\t\t\t\t\"Access-Control-Request-Method\":  []string{\"GET\"},\n\t\t\t\t\"Access-Control-Request-Headers\": []string{\"Authorization\"},\n\t\t\t\t\"Origin\":                         []string{\"http://example.com\"},\n\t\t\t},\n\t\t\texpectedAccessControlAllowMethodsHeader: \"GET,PUT,PATCH,OPTIONS\",\n\t\t\texpectedResponse:                        \"b\",\n\t\t},\n\t}\n\n\tfor _, tt := range testCases {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\trouter := NewRouter()\n\n\t\t\ttt.registerRoutes(router)\n\n\t\t\trouter.Use(CORSMethodMiddleware(router))\n\n\t\t\trw := NewRecorder()\n\t\t\treq := newRequest(tt.requestMethod, tt.requestPath)\n\t\t\treq.Header = tt.requestHeader\n\n\t\t\trouter.ServeHTTP(rw, req)\n\n\t\t\tactualMethodsHeader := rw.Header().Get(\"Access-Control-Allow-Methods\")\n\t\t\tif actualMethodsHeader != tt.expectedAccessControlAllowMethodsHeader {\n\t\t\t\tt.Fatalf(\"Expected Access-Control-Allow-Methods to equal %s but got %s\", tt.expectedAccessControlAllowMethodsHeader, actualMethodsHeader)\n\t\t\t}\n\n\t\t\tactualResponse := rw.Body.String()\n\t\t\tif actualResponse != tt.expectedResponse {\n\t\t\t\tt.Fatalf(\"Expected response to equal %s but got %s\", tt.expectedResponse, actualResponse)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestCORSMethodMiddlewareSubrouter(t *testing.T) {\n\trouter := NewRouter().StrictSlash(true)\n\n\tsubrouter := router.PathPrefix(\"/test\").Subrouter()\n\tsubrouter.HandleFunc(\"/hello\", stringHandler(\"a\")).Methods(http.MethodGet, http.MethodOptions, http.MethodPost)\n\tsubrouter.HandleFunc(\"/hello/{name}\", stringHandler(\"b\")).Methods(http.MethodGet, http.MethodOptions)\n\n\tsubrouter.Use(CORSMethodMiddleware(subrouter))\n\n\trw := NewRecorder()\n\treq := newRequest(\"GET\", \"/test/hello/asdf\")\n\trouter.ServeHTTP(rw, req)\n\n\tactualMethods := rw.Header().Get(\"Access-Control-Allow-Methods\")\n\texpectedMethods := \"GET,OPTIONS\"\n\tif actualMethods != expectedMethods {\n\t\tt.Fatalf(\"expected methods %q but got: %q\", expectedMethods, actualMethods)\n\t}\n}\n\nfunc TestMiddlewareOnMultiSubrouter(t *testing.T) {\n\tfirst := \"first\"\n\tsecond := \"second\"\n\tnotFound := \"404 not found\"\n\n\trouter := NewRouter()\n\tfirstSubRouter := router.PathPrefix(\"/\").Subrouter()\n\tsecondSubRouter := router.PathPrefix(\"/\").Subrouter()\n\n\trouter.NotFoundHandler = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {\n\t\t_, err := rw.Write([]byte(notFound))\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Failed writing HTTP response: %v\", err)\n\t\t}\n\t})\n\n\tfirstSubRouter.HandleFunc(\"/first\", func(w http.ResponseWriter, r *http.Request) {\n\n\t})\n\n\tsecondSubRouter.HandleFunc(\"/second\", func(w http.ResponseWriter, r *http.Request) {\n\n\t})\n\n\tfirstSubRouter.Use(func(h http.Handler) http.Handler {\n\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t_, err := w.Write([]byte(first))\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Failed writing HTTP response: %v\", err)\n\t\t\t}\n\t\t\th.ServeHTTP(w, r)\n\t\t})\n\t})\n\n\tsecondSubRouter.Use(func(h http.Handler) http.Handler {\n\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t_, err := w.Write([]byte(second))\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Failed writing HTTP response: %v\", err)\n\t\t\t}\n\t\t\th.ServeHTTP(w, r)\n\t\t})\n\t})\n\n\tt.Run(\"/first uses first middleware\", func(t *testing.T) {\n\t\trw := NewRecorder()\n\t\treq := newRequest(\"GET\", \"/first\")\n\n\t\trouter.ServeHTTP(rw, req)\n\t\tif rw.Body.String() != first {\n\t\t\tt.Fatalf(\"Middleware did not run: expected %s middleware to write a response (got %s)\", first, rw.Body.String())\n\t\t}\n\t})\n\n\tt.Run(\"/second uses second middleware\", func(t *testing.T) {\n\t\trw := NewRecorder()\n\t\treq := newRequest(\"GET\", \"/second\")\n\n\t\trouter.ServeHTTP(rw, req)\n\t\tif rw.Body.String() != second {\n\t\t\tt.Fatalf(\"Middleware did not run: expected %s middleware to write a response (got %s)\", second, rw.Body.String())\n\t\t}\n\t})\n\n\tt.Run(\"uses not found handler\", func(t *testing.T) {\n\t\trw := NewRecorder()\n\t\treq := newRequest(\"GET\", \"/second/not-exist\")\n\n\t\trouter.ServeHTTP(rw, req)\n\t\tif rw.Body.String() != notFound {\n\t\t\tt.Fatalf(\"Notfound handler did not run: expected %s for not-exist, (got %s)\", notFound, rw.Body.String())\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "mux.go",
    "content": "// Copyright 2012 The Gorilla Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\npackage mux\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"path\"\n\t\"regexp\"\n)\n\nvar (\n\t// ErrMethodMismatch is returned when the method in the request does not match\n\t// the method defined against the route.\n\tErrMethodMismatch = errors.New(\"method is not allowed\")\n\t// ErrNotFound is returned when no route match is found.\n\tErrNotFound = errors.New(\"no matching route was found\")\n\t// RegexpCompileFunc aliases regexp.Compile and enables overriding it.\n\t// Do not run this function from `init()` in importable packages.\n\t// Changing this value is not safe for concurrent use.\n\tRegexpCompileFunc = regexp.Compile\n\t// ErrMetadataKeyNotFound is returned when the specified metadata key is not present in the map\n\tErrMetadataKeyNotFound = errors.New(\"key not found in metadata\")\n)\n\n// NewRouter returns a new router instance.\nfunc NewRouter() *Router {\n\treturn &Router{namedRoutes: make(map[string]*Route)}\n}\n\n// Router registers routes to be matched and dispatches a handler.\n//\n// It implements the http.Handler interface, so it can be registered to serve\n// requests:\n//\n//\tvar router = mux.NewRouter()\n//\n//\tfunc main() {\n//\t    http.Handle(\"/\", router)\n//\t}\n//\n// Or, for Google App Engine, register it in a init() function:\n//\n//\tfunc init() {\n//\t    http.Handle(\"/\", router)\n//\t}\n//\n// This will send all incoming requests to the router.\ntype Router struct {\n\t// Configurable Handler to be used when no route matches.\n\t// This can be used to render your own 404 Not Found errors.\n\tNotFoundHandler http.Handler\n\n\t// Configurable Handler to be used when the request method does not match the route.\n\t// This can be used to render your own 405 Method Not Allowed errors.\n\tMethodNotAllowedHandler http.Handler\n\n\t// Routes to be matched, in order.\n\troutes []*Route\n\n\t// Routes by name for URL building.\n\tnamedRoutes map[string]*Route\n\n\t// If true, do not clear the request context after handling the request.\n\t//\n\t// Deprecated: No effect, since the context is stored on the request itself.\n\tKeepContext bool\n\n\t// Slice of middlewares to be called after a match is found\n\tmiddlewares []middleware\n\n\t// configuration shared with `Route`\n\trouteConf\n}\n\n// common route configuration shared between `Router` and `Route`\ntype routeConf struct {\n\t// If true, \"/path/foo%2Fbar/to\" will match the path \"/path/{var}/to\"\n\tuseEncodedPath bool\n\n\t// If true, when the path pattern is \"/path/\", accessing \"/path\" will\n\t// redirect to the former and vice versa.\n\tstrictSlash bool\n\n\t// If true, when the path pattern is \"/path//to\", accessing \"/path//to\"\n\t// will not redirect\n\tskipClean bool\n\n\t// If true, the http.Request context will not contain the Route.\n\tomitRouteFromContext bool\n\n\t// if true, the the http.Request context will not contain the router\n\tomitRouterFromContext bool\n\n\t// Manager for the variables from host and path.\n\tregexp routeRegexpGroup\n\n\t// List of matchers.\n\tmatchers []matcher\n\n\t// The scheme used when building URLs.\n\tbuildScheme string\n\n\tbuildVarsFunc BuildVarsFunc\n}\n\n// returns an effective deep copy of `routeConf`\nfunc copyRouteConf(r routeConf) routeConf {\n\tc := r\n\n\tif r.regexp.path != nil {\n\t\tc.regexp.path = copyRouteRegexp(r.regexp.path)\n\t}\n\n\tif r.regexp.host != nil {\n\t\tc.regexp.host = copyRouteRegexp(r.regexp.host)\n\t}\n\n\tc.regexp.queries = make([]*routeRegexp, 0, len(r.regexp.queries))\n\tfor _, q := range r.regexp.queries {\n\t\tc.regexp.queries = append(c.regexp.queries, copyRouteRegexp(q))\n\t}\n\n\tc.matchers = make([]matcher, len(r.matchers))\n\tcopy(c.matchers, r.matchers)\n\n\treturn c\n}\n\nfunc copyRouteRegexp(r *routeRegexp) *routeRegexp {\n\tc := *r\n\treturn &c\n}\n\n// Match attempts to match the given request against the router's registered routes.\n//\n// If the request matches a route of this router or one of its subrouters the Route,\n// Handler, and Vars fields of the the match argument are filled and this function\n// returns true.\n//\n// If the request does not match any of this router's or its subrouters' routes\n// then this function returns false. If available, a reason for the match failure\n// will be filled in the match argument's MatchErr field. If the match failure type\n// (eg: not found) has a registered handler, the handler is assigned to the Handler\n// field of the match argument.\nfunc (r *Router) Match(req *http.Request, match *RouteMatch) bool {\n\tfor _, route := range r.routes {\n\t\tif route.Match(req, match) {\n\t\t\t// Build middleware chain if no error was found\n\t\t\tif match.MatchErr == nil {\n\t\t\t\tfor i := len(r.middlewares) - 1; i >= 0; i-- {\n\t\t\t\t\tmatch.Handler = r.middlewares[i].Middleware(match.Handler)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true\n\t\t}\n\t}\n\n\tif match.MatchErr == ErrMethodMismatch {\n\t\tif r.MethodNotAllowedHandler != nil {\n\t\t\tmatch.Handler = r.MethodNotAllowedHandler\n\t\t\treturn true\n\t\t}\n\n\t\treturn false\n\t}\n\n\t// Closest match for a router (includes sub-routers)\n\tif r.NotFoundHandler != nil {\n\t\tmatch.Handler = r.NotFoundHandler\n\t\tmatch.MatchErr = ErrNotFound\n\t\treturn true\n\t}\n\n\tmatch.MatchErr = ErrNotFound\n\treturn false\n}\n\n// ServeHTTP dispatches the handler registered in the matched route.\n//\n// When there is a match, the route variables can be retrieved calling\n// mux.Vars(request).\nfunc (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {\n\tif !r.skipClean {\n\t\tpath := req.URL.Path\n\t\tif r.useEncodedPath {\n\t\t\tpath = req.URL.EscapedPath()\n\t\t}\n\t\t// Clean path to canonical form and redirect.\n\t\tif p := cleanPath(path); p != path {\n\t\t\tw.Header().Set(\"Location\", replaceURLPath(req.URL, p))\n\t\t\tw.WriteHeader(http.StatusMovedPermanently)\n\t\t\treturn\n\t\t}\n\t}\n\tvar match RouteMatch\n\tvar handler http.Handler\n\tif r.Match(req, &match) {\n\t\thandler = match.Handler\n\t\tif handler != nil {\n\t\t\t// Populate context for custom handlers\n\t\t\tif r.omitRouteFromContext {\n\t\t\t\t// Only populate the match vars (if any) into the context.\n\t\t\t\treq = requestWithVars(req, match.Vars)\n\t\t\t} else {\n\t\t\t\treq = requestWithRouteAndVars(req, match.Route, match.Vars)\n\t\t\t}\n\n\t\t\tif !r.omitRouterFromContext {\n\t\t\t\treq = requestWithRouter(req, r)\n\t\t\t}\n\t\t}\n\t}\n\n\tif handler == nil && match.MatchErr == ErrMethodMismatch {\n\t\thandler = methodNotAllowedHandler()\n\t}\n\n\tif handler == nil {\n\t\thandler = http.NotFoundHandler()\n\t}\n\n\thandler.ServeHTTP(w, req)\n}\n\n// Get returns a route registered with the given name.\nfunc (r *Router) Get(name string) *Route {\n\treturn r.namedRoutes[name]\n}\n\n// GetRoute returns a route registered with the given name. This method\n// was renamed to Get() and remains here for backwards compatibility.\nfunc (r *Router) GetRoute(name string) *Route {\n\treturn r.namedRoutes[name]\n}\n\n// StrictSlash defines the trailing slash behavior for new routes. The initial\n// value is false.\n//\n// When true, if the route path is \"/path/\", accessing \"/path\" will perform a redirect\n// to the former and vice versa. In other words, your application will always\n// see the path as specified in the route.\n//\n// When false, if the route path is \"/path\", accessing \"/path/\" will not match\n// this route and vice versa.\n//\n// The redirect is a HTTP 301 (Moved Permanently). Note that when this is set for\n// routes with a non-idempotent method (e.g. POST, PUT), the subsequent redirected\n// request will be made as a GET by most clients. Use middleware or client settings\n// to modify this behaviour as needed.\n//\n// Special case: when a route sets a path prefix using the PathPrefix() method,\n// strict slash is ignored for that route because the redirect behavior can't\n// be determined from a prefix alone. However, any subrouters created from that\n// route inherit the original StrictSlash setting.\nfunc (r *Router) StrictSlash(value bool) *Router {\n\tr.strictSlash = value\n\treturn r\n}\n\n// SkipClean defines the path cleaning behaviour for new routes. The initial\n// value is false. Users should be careful about which routes are not cleaned\n//\n// When true, if the route path is \"/path//to\", it will remain with the double\n// slash. This is helpful if you have a route like: /fetch/http://xkcd.com/534/\n//\n// When false, the path will be cleaned, so /fetch/http://xkcd.com/534/ will\n// become /fetch/http/xkcd.com/534\nfunc (r *Router) SkipClean(value bool) *Router {\n\tr.skipClean = value\n\treturn r\n}\n\n// OmitRouteFromContext defines the behavior of omitting the Route from the\n//\n//\thttp.Request context.\n//\n// CurrentRoute will yield nil with this option.\nfunc (r *Router) OmitRouteFromContext(value bool) *Router {\n\tr.omitRouteFromContext = value\n\treturn r\n}\n\n// OmitRouterFromContext defines the behavior of omitting the Router from the\n// http.Request context.\n//\n// RouterFromRequest will yield nil with this option.\nfunc (r *Router) OmitRouterFromContext(value bool) *Router {\n\tr.omitRouterFromContext = value\n\treturn r\n}\n\n// UseEncodedPath tells the router to match the encoded original path\n// to the routes.\n// For eg. \"/path/foo%2Fbar/to\" will match the path \"/path/{var}/to\".\n//\n// If not called, the router will match the unencoded path to the routes.\n// For eg. \"/path/foo%2Fbar/to\" will match the path \"/path/foo/bar/to\"\nfunc (r *Router) UseEncodedPath() *Router {\n\tr.useEncodedPath = true\n\treturn r\n}\n\n// ----------------------------------------------------------------------------\n// Route factories\n// ----------------------------------------------------------------------------\n\n// NewRoute registers an empty route.\nfunc (r *Router) NewRoute() *Route {\n\t// initialize a route with a copy of the parent router's configuration\n\troute := &Route{routeConf: copyRouteConf(r.routeConf), namedRoutes: r.namedRoutes}\n\tr.routes = append(r.routes, route)\n\treturn route\n}\n\n// Name registers a new route with a name.\n// See Route.Name().\nfunc (r *Router) Name(name string) *Route {\n\treturn r.NewRoute().Name(name)\n}\n\n// Handle registers a new route with a matcher for the URL path.\n// See Route.Path() and Route.Handler().\nfunc (r *Router) Handle(path string, handler http.Handler) *Route {\n\treturn r.NewRoute().Path(path).Handler(handler)\n}\n\n// HandleFunc registers a new route with a matcher for the URL path.\n// See Route.Path() and Route.HandlerFunc().\nfunc (r *Router) HandleFunc(path string, f func(http.ResponseWriter,\n\t*http.Request)) *Route {\n\treturn r.NewRoute().Path(path).HandlerFunc(f)\n}\n\n// Headers registers a new route with a matcher for request header values.\n// See Route.Headers().\nfunc (r *Router) Headers(pairs ...string) *Route {\n\treturn r.NewRoute().Headers(pairs...)\n}\n\n// Host registers a new route with a matcher for the URL host.\n// See Route.Host().\nfunc (r *Router) Host(tpl string) *Route {\n\treturn r.NewRoute().Host(tpl)\n}\n\n// MatcherFunc registers a new route with a custom matcher function.\n// See Route.MatcherFunc().\nfunc (r *Router) MatcherFunc(f MatcherFunc) *Route {\n\treturn r.NewRoute().MatcherFunc(f)\n}\n\n// Methods registers a new route with a matcher for HTTP methods.\n// See Route.Methods().\nfunc (r *Router) Methods(methods ...string) *Route {\n\treturn r.NewRoute().Methods(methods...)\n}\n\n// Path registers a new route with a matcher for the URL path.\n// See Route.Path().\nfunc (r *Router) Path(tpl string) *Route {\n\treturn r.NewRoute().Path(tpl)\n}\n\n// PathPrefix registers a new route with a matcher for the URL path prefix.\n// See Route.PathPrefix().\nfunc (r *Router) PathPrefix(tpl string) *Route {\n\treturn r.NewRoute().PathPrefix(tpl)\n}\n\n// Queries registers a new route with a matcher for URL query values.\n// See Route.Queries().\nfunc (r *Router) Queries(pairs ...string) *Route {\n\treturn r.NewRoute().Queries(pairs...)\n}\n\n// Schemes registers a new route with a matcher for URL schemes.\n// See Route.Schemes().\nfunc (r *Router) Schemes(schemes ...string) *Route {\n\treturn r.NewRoute().Schemes(schemes...)\n}\n\n// BuildVarsFunc registers a new route with a custom function for modifying\n// route variables before building a URL.\nfunc (r *Router) BuildVarsFunc(f BuildVarsFunc) *Route {\n\treturn r.NewRoute().BuildVarsFunc(f)\n}\n\n// Walk walks the router and all its sub-routers, calling walkFn for each route\n// in the tree. The routes are walked in the order they were added. Sub-routers\n// are explored depth-first.\nfunc (r *Router) Walk(walkFn WalkFunc) error {\n\treturn r.walk(walkFn, []*Route{})\n}\n\n// SkipRouter is used as a return value from WalkFuncs to indicate that the\n// router that walk is about to descend down to should be skipped.\nvar SkipRouter = errors.New(\"skip this router\")\n\n// WalkFunc is the type of the function called for each route visited by Walk.\n// At every invocation, it is given the current route, and the current router,\n// and a list of ancestor routes that lead to the current route.\ntype WalkFunc func(route *Route, router *Router, ancestors []*Route) error\n\nfunc (r *Router) walk(walkFn WalkFunc, ancestors []*Route) error {\n\tfor _, t := range r.routes {\n\t\terr := walkFn(t, r, ancestors)\n\t\tif err == SkipRouter {\n\t\t\tcontinue\n\t\t}\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfor _, sr := range t.matchers {\n\t\t\tif h, ok := sr.(*Router); ok {\n\t\t\t\tancestors = append(ancestors, t)\n\t\t\t\terr := h.walk(walkFn, ancestors)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tancestors = ancestors[:len(ancestors)-1]\n\t\t\t}\n\t\t}\n\t\tif h, ok := t.handler.(*Router); ok {\n\t\t\tancestors = append(ancestors, t)\n\t\t\terr := h.walk(walkFn, ancestors)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tancestors = ancestors[:len(ancestors)-1]\n\t\t}\n\t}\n\treturn nil\n}\n\n// ----------------------------------------------------------------------------\n// Context\n// ----------------------------------------------------------------------------\n\n// RouteMatch stores information about a matched route.\ntype RouteMatch struct {\n\tRoute   *Route\n\tHandler http.Handler\n\tVars    map[string]string\n\n\t// MatchErr is set to appropriate matching error\n\t// It is set to ErrMethodMismatch if there is a mismatch in\n\t// the request method and route method\n\tMatchErr error\n}\n\ntype contextKey int\n\nconst (\n\tvarsKey contextKey = iota\n\trouteKey\n\trouterKey\n)\n\n// Vars returns the route variables for the current request, if any.\nfunc Vars(r *http.Request) map[string]string {\n\tif rv := r.Context().Value(varsKey); rv != nil {\n\t\treturn rv.(map[string]string)\n\t}\n\treturn nil\n}\n\n// CurrentRoute returns the matched route for the current request, if any.\n// This only works when called inside the handler of the matched route\n// because the matched route is stored in the request context which is cleared\n// after the handler returns.\nfunc CurrentRoute(r *http.Request) *Route {\n\tif rv := r.Context().Value(routeKey); rv != nil {\n\t\treturn rv.(*Route)\n\t}\n\treturn nil\n}\n\nfunc CurrentRouter(r *http.Request) *Router {\n\tif rv := r.Context().Value(routerKey); rv != nil {\n\t\treturn rv.(*Router)\n\t}\n\treturn nil\n}\n\n// requestWithVars adds the matched vars to the request ctx.\n// It shortcuts the operation when the vars are empty.\nfunc requestWithVars(r *http.Request, vars map[string]string) *http.Request {\n\tif len(vars) == 0 {\n\t\treturn r\n\t}\n\tctx := context.WithValue(r.Context(), varsKey, vars)\n\treturn r.WithContext(ctx)\n}\n\n// requestWithRouteAndVars adds the matched route and vars to the request ctx.\n// It saves extra allocations in cloning the request once and skipping the\n//\n//\tpopulation of empty vars, which in turn mux.Vars can handle gracefully.\nfunc requestWithRouteAndVars(r *http.Request, route *Route, vars map[string]string) *http.Request {\n\tctx := context.WithValue(r.Context(), routeKey, route)\n\tif len(vars) > 0 {\n\t\tctx = context.WithValue(ctx, varsKey, vars)\n\t}\n\treturn r.WithContext(ctx)\n}\n\nfunc requestWithRouter(r *http.Request, router *Router) *http.Request {\n\tctx := context.WithValue(r.Context(), routerKey, router)\n\treturn r.WithContext(ctx)\n}\n\n// ----------------------------------------------------------------------------\n// Helpers\n// ----------------------------------------------------------------------------\n\n// cleanPath returns the canonical path for p, eliminating . and .. elements.\n// Borrowed from the net/http package.\nfunc cleanPath(p string) string {\n\tif p == \"\" {\n\t\treturn \"/\"\n\t}\n\tif p[0] != '/' {\n\t\tp = \"/\" + p\n\t}\n\tnp := path.Clean(p)\n\t// path.Clean removes trailing slash except for root;\n\t// put the trailing slash back if necessary.\n\tif p[len(p)-1] == '/' && np != \"/\" {\n\t\tnp += \"/\"\n\t}\n\n\treturn np\n}\n\n// replaceURLPath prints an url.URL with a different path.\nfunc replaceURLPath(u *url.URL, p string) string {\n\t// Operate on a copy of the request url.\n\tu2 := *u\n\tu2.Path = p\n\treturn u2.String()\n}\n\n// uniqueVars returns an error if two slices contain duplicated strings.\nfunc uniqueVars(s1, s2 []string) error {\n\tfor _, v1 := range s1 {\n\t\tfor _, v2 := range s2 {\n\t\t\tif v1 == v2 {\n\t\t\t\treturn fmt.Errorf(\"mux: duplicated route variable %q\", v2)\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\n// checkPairs returns the count of strings passed in, and an error if\n// the count is not an even number.\nfunc checkPairs(pairs ...string) (int, error) {\n\tlength := len(pairs)\n\tif length%2 != 0 {\n\t\treturn length, fmt.Errorf(\n\t\t\t\"mux: number of parameters must be multiple of 2, got %v\", pairs)\n\t}\n\treturn length, nil\n}\n\n// mapFromPairsToString converts variadic string parameters to a\n// string to string map.\nfunc mapFromPairsToString(pairs ...string) (map[string]string, error) {\n\tlength, err := checkPairs(pairs...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tm := make(map[string]string, length/2)\n\tfor i := 0; i < length; i += 2 {\n\t\tm[pairs[i]] = pairs[i+1]\n\t}\n\treturn m, nil\n}\n\n// mapFromPairsToRegex converts variadic string parameters to a\n// string to regex map.\nfunc mapFromPairsToRegex(pairs ...string) (map[string]*regexp.Regexp, error) {\n\tlength, err := checkPairs(pairs...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tm := make(map[string]*regexp.Regexp, length/2)\n\tfor i := 0; i < length; i += 2 {\n\t\tregex, err := RegexpCompileFunc(pairs[i+1])\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tm[pairs[i]] = regex\n\t}\n\treturn m, nil\n}\n\n// matchInArray returns true if the given string value is in the array.\nfunc matchInArray(arr []string, value string) bool {\n\tfor _, v := range arr {\n\t\tif v == value {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// matchMapWithString returns true if the given key/value pairs exist in a given map.\nfunc matchMapWithString(toCheck map[string]string, toMatch map[string][]string, canonicalKey bool) bool {\n\tfor k, v := range toCheck {\n\t\t// Check if key exists.\n\t\tif canonicalKey {\n\t\t\tk = http.CanonicalHeaderKey(k)\n\t\t}\n\t\tif values := toMatch[k]; values == nil {\n\t\t\treturn false\n\t\t} else if v != \"\" {\n\t\t\t// If value was defined as an empty string we only check that the\n\t\t\t// key exists. Otherwise we also check for equality.\n\t\t\tvalueExists := false\n\t\t\tfor _, value := range values {\n\t\t\t\tif v == value {\n\t\t\t\t\tvalueExists = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !valueExists {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t}\n\treturn true\n}\n\n// matchMapWithRegex returns true if the given key/value pairs exist in a given map compiled against\n// the given regex\nfunc matchMapWithRegex(toCheck map[string]*regexp.Regexp, toMatch map[string][]string, canonicalKey bool) bool {\n\tfor k, v := range toCheck {\n\t\t// Check if key exists.\n\t\tif canonicalKey {\n\t\t\tk = http.CanonicalHeaderKey(k)\n\t\t}\n\t\tif values := toMatch[k]; values == nil {\n\t\t\treturn false\n\t\t} else if v != nil {\n\t\t\t// If value was defined as an empty string we only check that the\n\t\t\t// key exists. Otherwise we also check for equality.\n\t\t\tvalueExists := false\n\t\t\tfor _, value := range values {\n\t\t\t\tif v.MatchString(value) {\n\t\t\t\t\tvalueExists = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !valueExists {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t}\n\treturn true\n}\n\n// methodNotAllowed replies to the request with an HTTP status code 405.\nfunc methodNotAllowed(w http.ResponseWriter, r *http.Request) {\n\tw.WriteHeader(http.StatusMethodNotAllowed)\n}\n\n// methodNotAllowedHandler returns a simple request handler\n// that replies to each request with a status code 405.\nfunc methodNotAllowedHandler() http.Handler { return http.HandlerFunc(methodNotAllowed) }\n"
  },
  {
    "path": "mux_httpserver_test.go",
    "content": "//go:build go1.9\n// +build go1.9\n\npackage mux\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n)\n\nfunc TestSchemeMatchers(t *testing.T) {\n\trouter := NewRouter()\n\trouter.HandleFunc(\"/\", func(rw http.ResponseWriter, r *http.Request) {\n\t\t_, err := rw.Write([]byte(\"hello http world\"))\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Failed writing HTTP response: %v\", err)\n\t\t}\n\t}).Schemes(\"http\")\n\trouter.HandleFunc(\"/\", func(rw http.ResponseWriter, r *http.Request) {\n\t\t_, err := rw.Write([]byte(\"hello https world\"))\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Failed writing HTTP response: %v\", err)\n\t\t}\n\t}).Schemes(\"https\")\n\n\tassertResponseBody := func(t *testing.T, s *httptest.Server, expectedBody string) {\n\t\tresp, err := s.Client().Get(s.URL)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error getting from server: %v\", err)\n\t\t}\n\t\tif resp.StatusCode != 200 {\n\t\t\tt.Fatalf(\"expected a status code of 200, got %v\", resp.StatusCode)\n\t\t}\n\t\tbody, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error reading body: %v\", err)\n\t\t}\n\t\tif !bytes.Equal(body, []byte(expectedBody)) {\n\t\t\tt.Fatalf(\"response should be hello world, was: %q\", string(body))\n\t\t}\n\t}\n\n\tt.Run(\"httpServer\", func(t *testing.T) {\n\t\ts := httptest.NewServer(router)\n\t\tdefer s.Close()\n\t\tassertResponseBody(t, s, \"hello http world\")\n\t})\n\tt.Run(\"httpsServer\", func(t *testing.T) {\n\t\ts := httptest.NewTLSServer(router)\n\t\tdefer s.Close()\n\t\tassertResponseBody(t, s, \"hello https world\")\n\t})\n}\n"
  },
  {
    "path": "mux_test.go",
    "content": "// Copyright 2012 The Gorilla Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\npackage mux\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"reflect\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc (r *Route) GoString() string {\n\tmatchers := make([]string, len(r.matchers))\n\tfor i, m := range r.matchers {\n\t\tmatchers[i] = fmt.Sprintf(\"%#v\", m)\n\t}\n\treturn fmt.Sprintf(\"&Route{matchers:[]matcher{%s}}\", strings.Join(matchers, \", \"))\n}\n\nfunc (r *routeRegexp) GoString() string {\n\treturn fmt.Sprintf(\"&routeRegexp{template: %q, regexpType: %v, options: %v, regexp: regexp.MustCompile(%q), reverse: %q, varsN: %v, varsR: %v\", r.template, r.regexpType, r.options, r.regexp.String(), r.reverse, r.varsN, r.varsR)\n}\n\ntype routeTest struct {\n\ttitle           string            // title of the test\n\troute           *Route            // the route being tested\n\trequest         *http.Request     // a request to test the route\n\tvars            map[string]string // the expected vars of the match\n\tscheme          string            // the expected scheme of the built URL\n\thost            string            // the expected host of the built URL\n\tpath            string            // the expected path of the built URL\n\tquery           string            // the expected query string of the built URL\n\tpathTemplate    string            // the expected path template of the route\n\thostTemplate    string            // the expected host template of the route\n\tqueriesTemplate string            // the expected query template of the route\n\tmethods         []string          // the expected route methods\n\tpathRegexp      string            // the expected path regexp\n\tqueriesRegexp   string            // the expected query regexp\n\tshouldMatch     bool              // whether the request is expected to match the route at all\n\tshouldRedirect  bool              // whether the request should result in a redirect\n}\n\nfunc TestHost(t *testing.T) {\n\n\ttests := []routeTest{\n\t\t{\n\t\t\ttitle:       \"Host route match\",\n\t\t\troute:       new(Route).Host(\"aaa.bbb.ccc\"),\n\t\t\trequest:     newRequest(\"GET\", \"http://aaa.bbb.ccc/111/222/333\"),\n\t\t\tvars:        nil,\n\t\t\thost:        \"aaa.bbb.ccc\",\n\t\t\tpath:        \"\",\n\t\t\tshouldMatch: true,\n\t\t},\n\t\t{\n\t\t\ttitle:       \"Host route, wrong host in request URL\",\n\t\t\troute:       new(Route).Host(\"aaa.bbb.ccc\"),\n\t\t\trequest:     newRequest(\"GET\", \"http://aaa.222.ccc/111/222/333\"),\n\t\t\tvars:        nil,\n\t\t\thost:        \"aaa.bbb.ccc\",\n\t\t\tpath:        \"\",\n\t\t\tshouldMatch: false,\n\t\t},\n\t\t{\n\t\t\ttitle:       \"Host route with port, match\",\n\t\t\troute:       new(Route).Host(\"aaa.bbb.ccc:1234\"),\n\t\t\trequest:     newRequest(\"GET\", \"http://aaa.bbb.ccc:1234/111/222/333\"),\n\t\t\tvars:        nil,\n\t\t\thost:        \"aaa.bbb.ccc:1234\",\n\t\t\tpath:        \"\",\n\t\t\tshouldMatch: true,\n\t\t},\n\t\t{\n\t\t\ttitle:       \"Host route with port, wrong port in request URL\",\n\t\t\troute:       new(Route).Host(\"aaa.bbb.ccc:1234\"),\n\t\t\trequest:     newRequest(\"GET\", \"http://aaa.bbb.ccc:9999/111/222/333\"),\n\t\t\tvars:        nil,\n\t\t\thost:        \"aaa.bbb.ccc:1234\",\n\t\t\tpath:        \"\",\n\t\t\tshouldMatch: false,\n\t\t},\n\t\t{\n\t\t\ttitle:       \"Host route, match with host in request header\",\n\t\t\troute:       new(Route).Host(\"aaa.bbb.ccc\"),\n\t\t\trequest:     newRequestHost(\"GET\", \"/111/222/333\", \"aaa.bbb.ccc\"),\n\t\t\tvars:        nil,\n\t\t\thost:        \"aaa.bbb.ccc\",\n\t\t\tpath:        \"\",\n\t\t\tshouldMatch: true,\n\t\t},\n\t\t{\n\t\t\ttitle:       \"Host route, wrong host in request header\",\n\t\t\troute:       new(Route).Host(\"aaa.bbb.ccc\"),\n\t\t\trequest:     newRequestHost(\"GET\", \"/111/222/333\", \"aaa.222.ccc\"),\n\t\t\tvars:        nil,\n\t\t\thost:        \"aaa.bbb.ccc\",\n\t\t\tpath:        \"\",\n\t\t\tshouldMatch: false,\n\t\t},\n\t\t{\n\t\t\ttitle:       \"Host route with port, match with request header\",\n\t\t\troute:       new(Route).Host(\"aaa.bbb.ccc:1234\"),\n\t\t\trequest:     newRequestHost(\"GET\", \"/111/222/333\", \"aaa.bbb.ccc:1234\"),\n\t\t\tvars:        nil,\n\t\t\thost:        \"aaa.bbb.ccc:1234\",\n\t\t\tpath:        \"\",\n\t\t\tshouldMatch: true,\n\t\t},\n\t\t{\n\t\t\ttitle:       \"Host route with port, wrong host in request header\",\n\t\t\troute:       new(Route).Host(\"aaa.bbb.ccc:1234\"),\n\t\t\trequest:     newRequestHost(\"GET\", \"/111/222/333\", \"aaa.bbb.ccc:9999\"),\n\t\t\tvars:        nil,\n\t\t\thost:        \"aaa.bbb.ccc:1234\",\n\t\t\tpath:        \"\",\n\t\t\tshouldMatch: false,\n\t\t},\n\t\t{\n\t\t\ttitle:        \"Host route with pattern, match with request header\",\n\t\t\troute:        new(Route).Host(\"aaa.{v1:[a-z]{3}}.ccc:1{v2:(?:23|4)}\"),\n\t\t\trequest:      newRequestHost(\"GET\", \"/111/222/333\", \"aaa.bbb.ccc:123\"),\n\t\t\tvars:         map[string]string{\"v1\": \"bbb\", \"v2\": \"23\"},\n\t\t\thost:         \"aaa.bbb.ccc:123\",\n\t\t\tpath:         \"\",\n\t\t\thostTemplate: `aaa.{v1:[a-z]{3}}.ccc:1{v2:(?:23|4)}`,\n\t\t\tshouldMatch:  true,\n\t\t},\n\t\t{\n\t\t\ttitle:        \"Host route with pattern, match\",\n\t\t\troute:        new(Route).Host(\"aaa.{v1:[a-z]{3}}.ccc\"),\n\t\t\trequest:      newRequest(\"GET\", \"http://aaa.bbb.ccc/111/222/333\"),\n\t\t\tvars:         map[string]string{\"v1\": \"bbb\"},\n\t\t\thost:         \"aaa.bbb.ccc\",\n\t\t\tpath:         \"\",\n\t\t\thostTemplate: `aaa.{v1:[a-z]{3}}.ccc`,\n\t\t\tshouldMatch:  true,\n\t\t},\n\t\t{\n\t\t\ttitle:        \"Host route with pattern, additional capturing group, match\",\n\t\t\troute:        new(Route).Host(\"aaa.{v1:[a-z]{2}(?:b|c)}.ccc\"),\n\t\t\trequest:      newRequest(\"GET\", \"http://aaa.bbb.ccc/111/222/333\"),\n\t\t\tvars:         map[string]string{\"v1\": \"bbb\"},\n\t\t\thost:         \"aaa.bbb.ccc\",\n\t\t\tpath:         \"\",\n\t\t\thostTemplate: `aaa.{v1:[a-z]{2}(?:b|c)}.ccc`,\n\t\t\tshouldMatch:  true,\n\t\t},\n\t\t{\n\t\t\ttitle:        \"Host route with pattern, wrong host in request URL\",\n\t\t\troute:        new(Route).Host(\"aaa.{v1:[a-z]{3}}.ccc\"),\n\t\t\trequest:      newRequest(\"GET\", \"http://aaa.222.ccc/111/222/333\"),\n\t\t\tvars:         map[string]string{\"v1\": \"bbb\"},\n\t\t\thost:         \"aaa.bbb.ccc\",\n\t\t\tpath:         \"\",\n\t\t\thostTemplate: `aaa.{v1:[a-z]{3}}.ccc`,\n\t\t\tshouldMatch:  false,\n\t\t},\n\t\t{\n\t\t\ttitle:        \"Host route with multiple patterns, match\",\n\t\t\troute:        new(Route).Host(\"{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}\"),\n\t\t\trequest:      newRequest(\"GET\", \"http://aaa.bbb.ccc/111/222/333\"),\n\t\t\tvars:         map[string]string{\"v1\": \"aaa\", \"v2\": \"bbb\", \"v3\": \"ccc\"},\n\t\t\thost:         \"aaa.bbb.ccc\",\n\t\t\tpath:         \"\",\n\t\t\thostTemplate: `{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}`,\n\t\t\tshouldMatch:  true,\n\t\t},\n\t\t{\n\t\t\ttitle:        \"Host route with multiple patterns, wrong host in request URL\",\n\t\t\troute:        new(Route).Host(\"{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}\"),\n\t\t\trequest:      newRequest(\"GET\", \"http://aaa.222.ccc/111/222/333\"),\n\t\t\tvars:         map[string]string{\"v1\": \"aaa\", \"v2\": \"bbb\", \"v3\": \"ccc\"},\n\t\t\thost:         \"aaa.bbb.ccc\",\n\t\t\tpath:         \"\",\n\t\t\thostTemplate: `{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}`,\n\t\t\tshouldMatch:  false,\n\t\t},\n\t\t{\n\t\t\ttitle:        \"Host route with hyphenated name and pattern, match\",\n\t\t\troute:        new(Route).Host(\"aaa.{v-1:[a-z]{3}}.ccc\"),\n\t\t\trequest:      newRequest(\"GET\", \"http://aaa.bbb.ccc/111/222/333\"),\n\t\t\tvars:         map[string]string{\"v-1\": \"bbb\"},\n\t\t\thost:         \"aaa.bbb.ccc\",\n\t\t\tpath:         \"\",\n\t\t\thostTemplate: `aaa.{v-1:[a-z]{3}}.ccc`,\n\t\t\tshouldMatch:  true,\n\t\t},\n\t\t{\n\t\t\ttitle:        \"Host route with hyphenated name and pattern, additional capturing group, match\",\n\t\t\troute:        new(Route).Host(\"aaa.{v-1:[a-z]{2}(?:b|c)}.ccc\"),\n\t\t\trequest:      newRequest(\"GET\", \"http://aaa.bbb.ccc/111/222/333\"),\n\t\t\tvars:         map[string]string{\"v-1\": \"bbb\"},\n\t\t\thost:         \"aaa.bbb.ccc\",\n\t\t\tpath:         \"\",\n\t\t\thostTemplate: `aaa.{v-1:[a-z]{2}(?:b|c)}.ccc`,\n\t\t\tshouldMatch:  true,\n\t\t},\n\t\t{\n\t\t\ttitle:        \"Host route with multiple hyphenated names and patterns, match\",\n\t\t\troute:        new(Route).Host(\"{v-1:[a-z]{3}}.{v-2:[a-z]{3}}.{v-3:[a-z]{3}}\"),\n\t\t\trequest:      newRequest(\"GET\", \"http://aaa.bbb.ccc/111/222/333\"),\n\t\t\tvars:         map[string]string{\"v-1\": \"aaa\", \"v-2\": \"bbb\", \"v-3\": \"ccc\"},\n\t\t\thost:         \"aaa.bbb.ccc\",\n\t\t\tpath:         \"\",\n\t\t\thostTemplate: `{v-1:[a-z]{3}}.{v-2:[a-z]{3}}.{v-3:[a-z]{3}}`,\n\t\t\tshouldMatch:  true,\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.title, func(t *testing.T) {\n\t\t\ttestRoute(t, test)\n\t\t\ttestTemplate(t, test)\n\t\t})\n\t}\n}\n\nfunc TestPath(t *testing.T) {\n\ttests := []routeTest{\n\t\t{\n\t\t\ttitle:       \"Path route, match\",\n\t\t\troute:       new(Route).Path(\"/111/222/333\"),\n\t\t\trequest:     newRequest(\"GET\", \"http://localhost/111/222/333\"),\n\t\t\tvars:        nil,\n\t\t\thost:        \"\",\n\t\t\tpath:        \"/111/222/333\",\n\t\t\tshouldMatch: true,\n\t\t},\n\t\t{\n\t\t\ttitle:       \"Path route, match with trailing slash in request and path\",\n\t\t\troute:       new(Route).Path(\"/111/\"),\n\t\t\trequest:     newRequest(\"GET\", \"http://localhost/111/\"),\n\t\t\tvars:        nil,\n\t\t\thost:        \"\",\n\t\t\tpath:        \"/111/\",\n\t\t\tshouldMatch: true,\n\t\t},\n\t\t{\n\t\t\ttitle:        \"Path route, do not match with trailing slash in path\",\n\t\t\troute:        new(Route).Path(\"/111/\"),\n\t\t\trequest:      newRequest(\"GET\", \"http://localhost/111\"),\n\t\t\tvars:         nil,\n\t\t\thost:         \"\",\n\t\t\tpath:         \"/111\",\n\t\t\tpathTemplate: `/111/`,\n\t\t\tpathRegexp:   `^/111/$`,\n\t\t\tshouldMatch:  false,\n\t\t},\n\t\t{\n\t\t\ttitle:        \"Path route, do not match with trailing slash in request\",\n\t\t\troute:        new(Route).Path(\"/111\"),\n\t\t\trequest:      newRequest(\"GET\", \"http://localhost/111/\"),\n\t\t\tvars:         nil,\n\t\t\thost:         \"\",\n\t\t\tpath:         \"/111/\",\n\t\t\tpathTemplate: `/111`,\n\t\t\tshouldMatch:  false,\n\t\t},\n\t\t{\n\t\t\ttitle:        \"Path route, match root with no host\",\n\t\t\troute:        new(Route).Path(\"/\"),\n\t\t\trequest:      newRequest(\"GET\", \"/\"),\n\t\t\tvars:         nil,\n\t\t\thost:         \"\",\n\t\t\tpath:         \"/\",\n\t\t\tpathTemplate: `/`,\n\t\t\tpathRegexp:   `^/$`,\n\t\t\tshouldMatch:  true,\n\t\t},\n\t\t{\n\t\t\ttitle: \"Path route, match root with no host, App Engine format\",\n\t\t\troute: new(Route).Path(\"/\"),\n\t\t\trequest: func() *http.Request {\n\t\t\t\tr := newRequest(\"GET\", \"http://localhost/\")\n\t\t\t\tr.RequestURI = \"/\"\n\t\t\t\treturn r\n\t\t\t}(),\n\t\t\tvars:         nil,\n\t\t\thost:         \"\",\n\t\t\tpath:         \"/\",\n\t\t\tpathTemplate: `/`,\n\t\t\tshouldMatch:  true,\n\t\t},\n\t\t{\n\t\t\ttitle:       \"Path route, wrong path in request in request URL\",\n\t\t\troute:       new(Route).Path(\"/111/222/333\"),\n\t\t\trequest:     newRequest(\"GET\", \"http://localhost/1/2/3\"),\n\t\t\tvars:        nil,\n\t\t\thost:        \"\",\n\t\t\tpath:        \"/111/222/333\",\n\t\t\tshouldMatch: false,\n\t\t},\n\t\t{\n\t\t\ttitle:        \"Path route with pattern, match\",\n\t\t\troute:        new(Route).Path(\"/111/{v1:[0-9]{3}}/333\"),\n\t\t\trequest:      newRequest(\"GET\", \"http://localhost/111/222/333\"),\n\t\t\tvars:         map[string]string{\"v1\": \"222\"},\n\t\t\thost:         \"\",\n\t\t\tpath:         \"/111/222/333\",\n\t\t\tpathTemplate: `/111/{v1:[0-9]{3}}/333`,\n\t\t\tshouldMatch:  true,\n\t\t},\n\t\t{\n\t\t\ttitle:        \"Path route with pattern, URL in request does not match\",\n\t\t\troute:        new(Route).Path(\"/111/{v1:[0-9]{3}}/333\"),\n\t\t\trequest:      newRequest(\"GET\", \"http://localhost/111/aaa/333\"),\n\t\t\tvars:         map[string]string{\"v1\": \"222\"},\n\t\t\thost:         \"\",\n\t\t\tpath:         \"/111/222/333\",\n\t\t\tpathTemplate: `/111/{v1:[0-9]{3}}/333`,\n\t\t\tpathRegexp:   `^/111/(?P<v0>[0-9]{3})/333$`,\n\t\t\tshouldMatch:  false,\n\t\t},\n\t\t{\n\t\t\ttitle:        \"Path route with multiple patterns, match\",\n\t\t\troute:        new(Route).Path(\"/{v1:[0-9]{3}}/{v2:[0-9]{3}}/{v3:[0-9]{3}}\"),\n\t\t\trequest:      newRequest(\"GET\", \"http://localhost/111/222/333\"),\n\t\t\tvars:         map[string]string{\"v1\": \"111\", \"v2\": \"222\", \"v3\": \"333\"},\n\t\t\thost:         \"\",\n\t\t\tpath:         \"/111/222/333\",\n\t\t\tpathTemplate: `/{v1:[0-9]{3}}/{v2:[0-9]{3}}/{v3:[0-9]{3}}`,\n\t\t\tpathRegexp:   `^/(?P<v0>[0-9]{3})/(?P<v1>[0-9]{3})/(?P<v2>[0-9]{3})$`,\n\t\t\tshouldMatch:  true,\n\t\t},\n\t\t{\n\t\t\ttitle:        \"Path route with multiple patterns, URL in request does not match\",\n\t\t\troute:        new(Route).Path(\"/{v1:[0-9]{3}}/{v2:[0-9]{3}}/{v3:[0-9]{3}}\"),\n\t\t\trequest:      newRequest(\"GET\", \"http://localhost/111/aaa/333\"),\n\t\t\tvars:         map[string]string{\"v1\": \"111\", \"v2\": \"222\", \"v3\": \"333\"},\n\t\t\thost:         \"\",\n\t\t\tpath:         \"/111/222/333\",\n\t\t\tpathTemplate: `/{v1:[0-9]{3}}/{v2:[0-9]{3}}/{v3:[0-9]{3}}`,\n\t\t\tpathRegexp:   `^/(?P<v0>[0-9]{3})/(?P<v1>[0-9]{3})/(?P<v2>[0-9]{3})$`,\n\t\t\tshouldMatch:  false,\n\t\t},\n\t\t{\n\t\t\ttitle:        \"Path route with multiple patterns with pipe, match\",\n\t\t\troute:        new(Route).Path(\"/{category:a|(?:b/c)}/{product}/{id:[0-9]+}\"),\n\t\t\trequest:      newRequest(\"GET\", \"http://localhost/a/product_name/1\"),\n\t\t\tvars:         map[string]string{\"category\": \"a\", \"product\": \"product_name\", \"id\": \"1\"},\n\t\t\thost:         \"\",\n\t\t\tpath:         \"/a/product_name/1\",\n\t\t\tpathTemplate: `/{category:a|(?:b/c)}/{product}/{id:[0-9]+}`,\n\t\t\tpathRegexp:   `^/(?P<v0>a|(?:b/c))/(?P<v1>[^/]+)/(?P<v2>[0-9]+)$`,\n\t\t\tshouldMatch:  true,\n\t\t},\n\t\t{\n\t\t\ttitle:        \"Path route with hyphenated name and pattern, match\",\n\t\t\troute:        new(Route).Path(\"/111/{v-1:[0-9]{3}}/333\"),\n\t\t\trequest:      newRequest(\"GET\", \"http://localhost/111/222/333\"),\n\t\t\tvars:         map[string]string{\"v-1\": \"222\"},\n\t\t\thost:         \"\",\n\t\t\tpath:         \"/111/222/333\",\n\t\t\tpathTemplate: `/111/{v-1:[0-9]{3}}/333`,\n\t\t\tpathRegexp:   `^/111/(?P<v0>[0-9]{3})/333$`,\n\t\t\tshouldMatch:  true,\n\t\t},\n\t\t{\n\t\t\ttitle:        \"Path route with multiple hyphenated names and patterns, match\",\n\t\t\troute:        new(Route).Path(\"/{v-1:[0-9]{3}}/{v-2:[0-9]{3}}/{v-3:[0-9]{3}}\"),\n\t\t\trequest:      newRequest(\"GET\", \"http://localhost/111/222/333\"),\n\t\t\tvars:         map[string]string{\"v-1\": \"111\", \"v-2\": \"222\", \"v-3\": \"333\"},\n\t\t\thost:         \"\",\n\t\t\tpath:         \"/111/222/333\",\n\t\t\tpathTemplate: `/{v-1:[0-9]{3}}/{v-2:[0-9]{3}}/{v-3:[0-9]{3}}`,\n\t\t\tpathRegexp:   `^/(?P<v0>[0-9]{3})/(?P<v1>[0-9]{3})/(?P<v2>[0-9]{3})$`,\n\t\t\tshouldMatch:  true,\n\t\t},\n\t\t{\n\t\t\ttitle:        \"Path route with multiple hyphenated names and patterns with pipe, match\",\n\t\t\troute:        new(Route).Path(\"/{product-category:a|(?:b/c)}/{product-name}/{product-id:[0-9]+}\"),\n\t\t\trequest:      newRequest(\"GET\", \"http://localhost/a/product_name/1\"),\n\t\t\tvars:         map[string]string{\"product-category\": \"a\", \"product-name\": \"product_name\", \"product-id\": \"1\"},\n\t\t\thost:         \"\",\n\t\t\tpath:         \"/a/product_name/1\",\n\t\t\tpathTemplate: `/{product-category:a|(?:b/c)}/{product-name}/{product-id:[0-9]+}`,\n\t\t\tpathRegexp:   `^/(?P<v0>a|(?:b/c))/(?P<v1>[^/]+)/(?P<v2>[0-9]+)$`,\n\t\t\tshouldMatch:  true,\n\t\t},\n\t\t{\n\t\t\ttitle:        \"Path route with multiple hyphenated names and patterns with pipe and case insensitive, match\",\n\t\t\troute:        new(Route).Path(\"/{type:(?i:daily|mini|variety)}-{date:\\\\d{4,4}-\\\\d{2,2}-\\\\d{2,2}}\"),\n\t\t\trequest:      newRequest(\"GET\", \"http://localhost/daily-2016-01-01\"),\n\t\t\tvars:         map[string]string{\"type\": \"daily\", \"date\": \"2016-01-01\"},\n\t\t\thost:         \"\",\n\t\t\tpath:         \"/daily-2016-01-01\",\n\t\t\tpathTemplate: `/{type:(?i:daily|mini|variety)}-{date:\\d{4,4}-\\d{2,2}-\\d{2,2}}`,\n\t\t\tpathRegexp:   `^/(?P<v0>(?i:daily|mini|variety))-(?P<v1>\\d{4,4}-\\d{2,2}-\\d{2,2})$`,\n\t\t\tshouldMatch:  true,\n\t\t},\n\t\t{\n\t\t\ttitle:        \"Path route with empty match right after other match\",\n\t\t\troute:        new(Route).Path(`/{v1:[0-9]*}{v2:[a-z]*}/{v3:[0-9]*}`),\n\t\t\trequest:      newRequest(\"GET\", \"http://localhost/111/222\"),\n\t\t\tvars:         map[string]string{\"v1\": \"111\", \"v2\": \"\", \"v3\": \"222\"},\n\t\t\thost:         \"\",\n\t\t\tpath:         \"/111/222\",\n\t\t\tpathTemplate: `/{v1:[0-9]*}{v2:[a-z]*}/{v3:[0-9]*}`,\n\t\t\tpathRegexp:   `^/(?P<v0>[0-9]*)(?P<v1>[a-z]*)/(?P<v2>[0-9]*)$`,\n\t\t\tshouldMatch:  true,\n\t\t},\n\t\t{\n\t\t\ttitle:        \"Path route with single pattern with pipe, match\",\n\t\t\troute:        new(Route).Path(\"/{category:a|b/c}\"),\n\t\t\trequest:      newRequest(\"GET\", \"http://localhost/a\"),\n\t\t\tvars:         map[string]string{\"category\": \"a\"},\n\t\t\thost:         \"\",\n\t\t\tpath:         \"/a\",\n\t\t\tpathTemplate: `/{category:a|b/c}`,\n\t\t\tshouldMatch:  true,\n\t\t},\n\t\t{\n\t\t\ttitle:        \"Path route with single pattern with pipe, match\",\n\t\t\troute:        new(Route).Path(\"/{category:a|b/c}\"),\n\t\t\trequest:      newRequest(\"GET\", \"http://localhost/b/c\"),\n\t\t\tvars:         map[string]string{\"category\": \"b/c\"},\n\t\t\thost:         \"\",\n\t\t\tpath:         \"/b/c\",\n\t\t\tpathTemplate: `/{category:a|b/c}`,\n\t\t\tshouldMatch:  true,\n\t\t},\n\t\t{\n\t\t\ttitle:        \"Path route with multiple patterns with pipe, match\",\n\t\t\troute:        new(Route).Path(\"/{category:a|b/c}/{product}/{id:[0-9]+}\"),\n\t\t\trequest:      newRequest(\"GET\", \"http://localhost/a/product_name/1\"),\n\t\t\tvars:         map[string]string{\"category\": \"a\", \"product\": \"product_name\", \"id\": \"1\"},\n\t\t\thost:         \"\",\n\t\t\tpath:         \"/a/product_name/1\",\n\t\t\tpathTemplate: `/{category:a|b/c}/{product}/{id:[0-9]+}`,\n\t\t\tshouldMatch:  true,\n\t\t},\n\t\t{\n\t\t\ttitle:        \"Path route with multiple patterns with pipe, match\",\n\t\t\troute:        new(Route).Path(\"/{category:a|b/c}/{product}/{id:[0-9]+}\"),\n\t\t\trequest:      newRequest(\"GET\", \"http://localhost/b/c/product_name/1\"),\n\t\t\tvars:         map[string]string{\"category\": \"b/c\", \"product\": \"product_name\", \"id\": \"1\"},\n\t\t\thost:         \"\",\n\t\t\tpath:         \"/b/c/product_name/1\",\n\t\t\tpathTemplate: `/{category:a|b/c}/{product}/{id:[0-9]+}`,\n\t\t\tshouldMatch:  true,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.title, func(t *testing.T) {\n\t\t\ttestRoute(t, test)\n\t\t\ttestTemplate(t, test)\n\t\t\ttestUseEscapedRoute(t, test)\n\t\t\ttestRegexp(t, test)\n\t\t})\n\t}\n}\n\nfunc TestPathPrefix(t *testing.T) {\n\ttests := []routeTest{\n\t\t{\n\t\t\ttitle:       \"PathPrefix route, match\",\n\t\t\troute:       new(Route).PathPrefix(\"/111\"),\n\t\t\trequest:     newRequest(\"GET\", \"http://localhost/111/222/333\"),\n\t\t\tvars:        nil,\n\t\t\thost:        \"\",\n\t\t\tpath:        \"/111\",\n\t\t\tshouldMatch: true,\n\t\t},\n\t\t{\n\t\t\ttitle:       \"PathPrefix route, match substring\",\n\t\t\troute:       new(Route).PathPrefix(\"/1\"),\n\t\t\trequest:     newRequest(\"GET\", \"http://localhost/111/222/333\"),\n\t\t\tvars:        nil,\n\t\t\thost:        \"\",\n\t\t\tpath:        \"/1\",\n\t\t\tshouldMatch: true,\n\t\t},\n\t\t{\n\t\t\ttitle:       \"PathPrefix route, URL prefix in request does not match\",\n\t\t\troute:       new(Route).PathPrefix(\"/111\"),\n\t\t\trequest:     newRequest(\"GET\", \"http://localhost/1/2/3\"),\n\t\t\tvars:        nil,\n\t\t\thost:        \"\",\n\t\t\tpath:        \"/111\",\n\t\t\tshouldMatch: false,\n\t\t},\n\t\t{\n\t\t\ttitle:        \"PathPrefix route with pattern, match\",\n\t\t\troute:        new(Route).PathPrefix(\"/111/{v1:[0-9]{3}}\"),\n\t\t\trequest:      newRequest(\"GET\", \"http://localhost/111/222/333\"),\n\t\t\tvars:         map[string]string{\"v1\": \"222\"},\n\t\t\thost:         \"\",\n\t\t\tpath:         \"/111/222\",\n\t\t\tpathTemplate: `/111/{v1:[0-9]{3}}`,\n\t\t\tshouldMatch:  true,\n\t\t},\n\t\t{\n\t\t\ttitle:        \"PathPrefix route with pattern, URL prefix in request does not match\",\n\t\t\troute:        new(Route).PathPrefix(\"/111/{v1:[0-9]{3}}\"),\n\t\t\trequest:      newRequest(\"GET\", \"http://localhost/111/aaa/333\"),\n\t\t\tvars:         map[string]string{\"v1\": \"222\"},\n\t\t\thost:         \"\",\n\t\t\tpath:         \"/111/222\",\n\t\t\tpathTemplate: `/111/{v1:[0-9]{3}}`,\n\t\t\tshouldMatch:  false,\n\t\t},\n\t\t{\n\t\t\ttitle:        \"PathPrefix route with multiple patterns, match\",\n\t\t\troute:        new(Route).PathPrefix(\"/{v1:[0-9]{3}}/{v2:[0-9]{3}}\"),\n\t\t\trequest:      newRequest(\"GET\", \"http://localhost/111/222/333\"),\n\t\t\tvars:         map[string]string{\"v1\": \"111\", \"v2\": \"222\"},\n\t\t\thost:         \"\",\n\t\t\tpath:         \"/111/222\",\n\t\t\tpathTemplate: `/{v1:[0-9]{3}}/{v2:[0-9]{3}}`,\n\t\t\tshouldMatch:  true,\n\t\t},\n\t\t{\n\t\t\ttitle:        \"PathPrefix route with multiple patterns, URL prefix in request does not match\",\n\t\t\troute:        new(Route).PathPrefix(\"/{v1:[0-9]{3}}/{v2:[0-9]{3}}\"),\n\t\t\trequest:      newRequest(\"GET\", \"http://localhost/111/aaa/333\"),\n\t\t\tvars:         map[string]string{\"v1\": \"111\", \"v2\": \"222\"},\n\t\t\thost:         \"\",\n\t\t\tpath:         \"/111/222\",\n\t\t\tpathTemplate: `/{v1:[0-9]{3}}/{v2:[0-9]{3}}`,\n\t\t\tshouldMatch:  false,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.title, func(t *testing.T) {\n\t\t\ttestRoute(t, test)\n\t\t\ttestTemplate(t, test)\n\t\t\ttestUseEscapedRoute(t, test)\n\t\t})\n\t}\n}\n\nfunc TestSchemeHostPath(t *testing.T) {\n\ttests := []routeTest{\n\t\t{\n\t\t\ttitle:        \"Host and Path route, match\",\n\t\t\troute:        new(Route).Host(\"aaa.bbb.ccc\").Path(\"/111/222/333\"),\n\t\t\trequest:      newRequest(\"GET\", \"http://aaa.bbb.ccc/111/222/333\"),\n\t\t\tvars:         nil,\n\t\t\tscheme:       \"http\",\n\t\t\thost:         \"aaa.bbb.ccc\",\n\t\t\tpath:         \"/111/222/333\",\n\t\t\tpathTemplate: `/111/222/333`,\n\t\t\thostTemplate: `aaa.bbb.ccc`,\n\t\t\tshouldMatch:  true,\n\t\t},\n\t\t{\n\t\t\ttitle:        \"Scheme, Host, and Path route, match\",\n\t\t\troute:        new(Route).Schemes(\"https\").Host(\"aaa.bbb.ccc\").Path(\"/111/222/333\"),\n\t\t\trequest:      newRequest(\"GET\", \"https://aaa.bbb.ccc/111/222/333\"),\n\t\t\tvars:         nil,\n\t\t\tscheme:       \"https\",\n\t\t\thost:         \"aaa.bbb.ccc\",\n\t\t\tpath:         \"/111/222/333\",\n\t\t\tpathTemplate: `/111/222/333`,\n\t\t\thostTemplate: `aaa.bbb.ccc`,\n\t\t\tshouldMatch:  true,\n\t\t},\n\t\t{\n\t\t\ttitle:        \"Host and Path route, wrong host in request URL\",\n\t\t\troute:        new(Route).Host(\"aaa.bbb.ccc\").Path(\"/111/222/333\"),\n\t\t\trequest:      newRequest(\"GET\", \"http://aaa.222.ccc/111/222/333\"),\n\t\t\tvars:         nil,\n\t\t\tscheme:       \"http\",\n\t\t\thost:         \"aaa.bbb.ccc\",\n\t\t\tpath:         \"/111/222/333\",\n\t\t\tpathTemplate: `/111/222/333`,\n\t\t\thostTemplate: `aaa.bbb.ccc`,\n\t\t\tshouldMatch:  false,\n\t\t},\n\t\t{\n\t\t\ttitle:        \"Host and Path route with pattern, match\",\n\t\t\troute:        new(Route).Host(\"aaa.{v1:[a-z]{3}}.ccc\").Path(\"/111/{v2:[0-9]{3}}/333\"),\n\t\t\trequest:      newRequest(\"GET\", \"http://aaa.bbb.ccc/111/222/333\"),\n\t\t\tvars:         map[string]string{\"v1\": \"bbb\", \"v2\": \"222\"},\n\t\t\tscheme:       \"http\",\n\t\t\thost:         \"aaa.bbb.ccc\",\n\t\t\tpath:         \"/111/222/333\",\n\t\t\tpathTemplate: `/111/{v2:[0-9]{3}}/333`,\n\t\t\thostTemplate: `aaa.{v1:[a-z]{3}}.ccc`,\n\t\t\tshouldMatch:  true,\n\t\t},\n\t\t{\n\t\t\ttitle:        \"Scheme, Host, and Path route with host and path patterns, match\",\n\t\t\troute:        new(Route).Schemes(\"ftp\", \"ssss\").Host(\"aaa.{v1:[a-z]{3}}.ccc\").Path(\"/111/{v2:[0-9]{3}}/333\"),\n\t\t\trequest:      newRequest(\"GET\", \"ssss://aaa.bbb.ccc/111/222/333\"),\n\t\t\tvars:         map[string]string{\"v1\": \"bbb\", \"v2\": \"222\"},\n\t\t\tscheme:       \"ftp\",\n\t\t\thost:         \"aaa.bbb.ccc\",\n\t\t\tpath:         \"/111/222/333\",\n\t\t\tpathTemplate: `/111/{v2:[0-9]{3}}/333`,\n\t\t\thostTemplate: `aaa.{v1:[a-z]{3}}.ccc`,\n\t\t\tshouldMatch:  true,\n\t\t},\n\t\t{\n\t\t\ttitle:        \"Host and Path route with pattern, URL in request does not match\",\n\t\t\troute:        new(Route).Host(\"aaa.{v1:[a-z]{3}}.ccc\").Path(\"/111/{v2:[0-9]{3}}/333\"),\n\t\t\trequest:      newRequest(\"GET\", \"http://aaa.222.ccc/111/222/333\"),\n\t\t\tvars:         map[string]string{\"v1\": \"bbb\", \"v2\": \"222\"},\n\t\t\tscheme:       \"http\",\n\t\t\thost:         \"aaa.bbb.ccc\",\n\t\t\tpath:         \"/111/222/333\",\n\t\t\tpathTemplate: `/111/{v2:[0-9]{3}}/333`,\n\t\t\thostTemplate: `aaa.{v1:[a-z]{3}}.ccc`,\n\t\t\tshouldMatch:  false,\n\t\t},\n\t\t{\n\t\t\ttitle:        \"Host and Path route with multiple patterns, match\",\n\t\t\troute:        new(Route).Host(\"{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}\").Path(\"/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}\"),\n\t\t\trequest:      newRequest(\"GET\", \"http://aaa.bbb.ccc/111/222/333\"),\n\t\t\tvars:         map[string]string{\"v1\": \"aaa\", \"v2\": \"bbb\", \"v3\": \"ccc\", \"v4\": \"111\", \"v5\": \"222\", \"v6\": \"333\"},\n\t\t\tscheme:       \"http\",\n\t\t\thost:         \"aaa.bbb.ccc\",\n\t\t\tpath:         \"/111/222/333\",\n\t\t\tpathTemplate: `/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}`,\n\t\t\thostTemplate: `{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}`,\n\t\t\tshouldMatch:  true,\n\t\t},\n\t\t{\n\t\t\ttitle:        \"Host and Path route with multiple patterns, URL in request does not match\",\n\t\t\troute:        new(Route).Host(\"{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}\").Path(\"/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}\"),\n\t\t\trequest:      newRequest(\"GET\", \"http://aaa.222.ccc/111/222/333\"),\n\t\t\tvars:         map[string]string{\"v1\": \"aaa\", \"v2\": \"bbb\", \"v3\": \"ccc\", \"v4\": \"111\", \"v5\": \"222\", \"v6\": \"333\"},\n\t\t\tscheme:       \"http\",\n\t\t\thost:         \"aaa.bbb.ccc\",\n\t\t\tpath:         \"/111/222/333\",\n\t\t\tpathTemplate: `/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}`,\n\t\t\thostTemplate: `{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}`,\n\t\t\tshouldMatch:  false,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.title, func(t *testing.T) {\n\t\t\ttestRoute(t, test)\n\t\t\ttestTemplate(t, test)\n\t\t\ttestUseEscapedRoute(t, test)\n\t\t})\n\t}\n}\n\nfunc TestHeaders(t *testing.T) {\n\t// newRequestHeaders creates a new request with a method, url, and headers\n\tnewRequestHeaders := func(method, url string, headers map[string]string) *http.Request {\n\t\treq, err := http.NewRequest(method, url, nil)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tfor k, v := range headers {\n\t\t\treq.Header.Add(k, v)\n\t\t}\n\t\treturn req\n\t}\n\n\ttests := []routeTest{\n\t\t{\n\t\t\ttitle:       \"Headers route, match\",\n\t\t\troute:       new(Route).Headers(\"foo\", \"bar\", \"baz\", \"ding\"),\n\t\t\trequest:     newRequestHeaders(\"GET\", \"http://localhost\", map[string]string{\"foo\": \"bar\", \"baz\": \"ding\"}),\n\t\t\tvars:        nil,\n\t\t\thost:        \"\",\n\t\t\tpath:        \"\",\n\t\t\tshouldMatch: true,\n\t\t},\n\t\t{\n\t\t\ttitle:       \"Headers route, bad header values\",\n\t\t\troute:       new(Route).Headers(\"foo\", \"bar\", \"baz\", \"ding\"),\n\t\t\trequest:     newRequestHeaders(\"GET\", \"http://localhost\", map[string]string{\"foo\": \"bar\", \"baz\": \"dong\"}),\n\t\t\tvars:        nil,\n\t\t\thost:        \"\",\n\t\t\tpath:        \"\",\n\t\t\tshouldMatch: false,\n\t\t},\n\t\t{\n\t\t\ttitle:       \"Headers route, regex header values to match\",\n\t\t\troute:       new(Route).HeadersRegexp(\"foo\", \"ba[zr]\"),\n\t\t\trequest:     newRequestHeaders(\"GET\", \"http://localhost\", map[string]string{\"foo\": \"baw\"}),\n\t\t\tvars:        nil,\n\t\t\thost:        \"\",\n\t\t\tpath:        \"\",\n\t\t\tshouldMatch: false,\n\t\t},\n\t\t{\n\t\t\ttitle:       \"Headers route, regex header values to match\",\n\t\t\troute:       new(Route).HeadersRegexp(\"foo\", \"ba[zr]\"),\n\t\t\trequest:     newRequestHeaders(\"GET\", \"http://localhost\", map[string]string{\"foo\": \"baz\"}),\n\t\t\tvars:        nil,\n\t\t\thost:        \"\",\n\t\t\tpath:        \"\",\n\t\t\tshouldMatch: true,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.title, func(t *testing.T) {\n\t\t\ttestRoute(t, test)\n\t\t\ttestTemplate(t, test)\n\t\t})\n\t}\n}\n\nfunc TestMethods(t *testing.T) {\n\ttests := []routeTest{\n\t\t{\n\t\t\ttitle:       \"Methods route, match GET\",\n\t\t\troute:       new(Route).Methods(\"GET\", \"POST\"),\n\t\t\trequest:     newRequest(\"GET\", \"http://localhost\"),\n\t\t\tvars:        nil,\n\t\t\thost:        \"\",\n\t\t\tpath:        \"\",\n\t\t\tmethods:     []string{\"GET\", \"POST\"},\n\t\t\tshouldMatch: true,\n\t\t},\n\t\t{\n\t\t\ttitle:       \"Methods route, match POST\",\n\t\t\troute:       new(Route).Methods(\"GET\", \"POST\"),\n\t\t\trequest:     newRequest(\"POST\", \"http://localhost\"),\n\t\t\tvars:        nil,\n\t\t\thost:        \"\",\n\t\t\tpath:        \"\",\n\t\t\tmethods:     []string{\"GET\", \"POST\"},\n\t\t\tshouldMatch: true,\n\t\t},\n\t\t{\n\t\t\ttitle:       \"Methods route, bad method\",\n\t\t\troute:       new(Route).Methods(\"GET\", \"POST\"),\n\t\t\trequest:     newRequest(\"PUT\", \"http://localhost\"),\n\t\t\tvars:        nil,\n\t\t\thost:        \"\",\n\t\t\tpath:        \"\",\n\t\t\tmethods:     []string{\"GET\", \"POST\"},\n\t\t\tshouldMatch: false,\n\t\t},\n\t\t{\n\t\t\ttitle:       \"Route without methods\",\n\t\t\troute:       new(Route),\n\t\t\trequest:     newRequest(\"PUT\", \"http://localhost\"),\n\t\t\tvars:        nil,\n\t\t\thost:        \"\",\n\t\t\tpath:        \"\",\n\t\t\tmethods:     []string{},\n\t\t\tshouldMatch: true,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.title, func(t *testing.T) {\n\t\t\ttestRoute(t, test)\n\t\t\ttestTemplate(t, test)\n\t\t\ttestMethods(t, test)\n\t\t})\n\t}\n}\n\nfunc TestQueries(t *testing.T) {\n\ttests := []routeTest{\n\t\t{\n\t\t\ttitle:           \"Queries route, match\",\n\t\t\troute:           new(Route).Queries(\"foo\", \"bar\", \"baz\", \"ding\"),\n\t\t\trequest:         newRequest(\"GET\", \"http://localhost?foo=bar&baz=ding\"),\n\t\t\tvars:            nil,\n\t\t\thost:            \"\",\n\t\t\tpath:            \"\",\n\t\t\tquery:           \"foo=bar&baz=ding\",\n\t\t\tqueriesTemplate: \"foo=bar,baz=ding\",\n\t\t\tqueriesRegexp:   \"^foo=bar$,^baz=ding$\",\n\t\t\tshouldMatch:     true,\n\t\t},\n\t\t{\n\t\t\ttitle:           \"Queries route, match with a query string\",\n\t\t\troute:           new(Route).Host(\"www.example.com\").Path(\"/api\").Queries(\"foo\", \"bar\", \"baz\", \"ding\"),\n\t\t\trequest:         newRequest(\"GET\", \"http://www.example.com/api?foo=bar&baz=ding\"),\n\t\t\tvars:            nil,\n\t\t\thost:            \"\",\n\t\t\tpath:            \"\",\n\t\t\tquery:           \"foo=bar&baz=ding\",\n\t\t\tpathTemplate:    `/api`,\n\t\t\thostTemplate:    `www.example.com`,\n\t\t\tqueriesTemplate: \"foo=bar,baz=ding\",\n\t\t\tqueriesRegexp:   \"^foo=bar$,^baz=ding$\",\n\t\t\tshouldMatch:     true,\n\t\t},\n\t\t{\n\t\t\ttitle:           \"Queries route, match with a query string out of order\",\n\t\t\troute:           new(Route).Host(\"www.example.com\").Path(\"/api\").Queries(\"foo\", \"bar\", \"baz\", \"ding\"),\n\t\t\trequest:         newRequest(\"GET\", \"http://www.example.com/api?baz=ding&foo=bar\"),\n\t\t\tvars:            nil,\n\t\t\thost:            \"\",\n\t\t\tpath:            \"\",\n\t\t\tquery:           \"foo=bar&baz=ding\",\n\t\t\tpathTemplate:    `/api`,\n\t\t\thostTemplate:    `www.example.com`,\n\t\t\tqueriesTemplate: \"foo=bar,baz=ding\",\n\t\t\tqueriesRegexp:   \"^foo=bar$,^baz=ding$\",\n\t\t\tshouldMatch:     true,\n\t\t},\n\t\t{\n\t\t\ttitle:           \"Queries route, bad query\",\n\t\t\troute:           new(Route).Queries(\"foo\", \"bar\", \"baz\", \"ding\"),\n\t\t\trequest:         newRequest(\"GET\", \"http://localhost?foo=bar&baz=dong\"),\n\t\t\tvars:            nil,\n\t\t\thost:            \"\",\n\t\t\tpath:            \"\",\n\t\t\tqueriesTemplate: \"foo=bar,baz=ding\",\n\t\t\tqueriesRegexp:   \"^foo=bar$,^baz=ding$\",\n\t\t\tshouldMatch:     false,\n\t\t},\n\t\t{\n\t\t\ttitle:           \"Queries route with pattern, match\",\n\t\t\troute:           new(Route).Queries(\"foo\", \"{v1}\"),\n\t\t\trequest:         newRequest(\"GET\", \"http://localhost?foo=bar\"),\n\t\t\tvars:            map[string]string{\"v1\": \"bar\"},\n\t\t\thost:            \"\",\n\t\t\tpath:            \"\",\n\t\t\tquery:           \"foo=bar\",\n\t\t\tqueriesTemplate: \"foo={v1}\",\n\t\t\tqueriesRegexp:   \"^foo=(?P<v0>.*)$\",\n\t\t\tshouldMatch:     true,\n\t\t},\n\t\t{\n\t\t\ttitle:           \"Queries route with multiple patterns, match\",\n\t\t\troute:           new(Route).Queries(\"foo\", \"{v1}\", \"baz\", \"{v2}\"),\n\t\t\trequest:         newRequest(\"GET\", \"http://localhost?foo=bar&baz=ding\"),\n\t\t\tvars:            map[string]string{\"v1\": \"bar\", \"v2\": \"ding\"},\n\t\t\thost:            \"\",\n\t\t\tpath:            \"\",\n\t\t\tquery:           \"foo=bar&baz=ding\",\n\t\t\tqueriesTemplate: \"foo={v1},baz={v2}\",\n\t\t\tqueriesRegexp:   \"^foo=(?P<v0>.*)$,^baz=(?P<v0>.*)$\",\n\t\t\tshouldMatch:     true,\n\t\t},\n\t\t{\n\t\t\ttitle:           \"Queries route with regexp pattern, match\",\n\t\t\troute:           new(Route).Queries(\"foo\", \"{v1:[0-9]+}\"),\n\t\t\trequest:         newRequest(\"GET\", \"http://localhost?foo=10\"),\n\t\t\tvars:            map[string]string{\"v1\": \"10\"},\n\t\t\thost:            \"\",\n\t\t\tpath:            \"\",\n\t\t\tquery:           \"foo=10\",\n\t\t\tqueriesTemplate: \"foo={v1:[0-9]+}\",\n\t\t\tqueriesRegexp:   \"^foo=(?P<v0>[0-9]+)$\",\n\t\t\tshouldMatch:     true,\n\t\t},\n\t\t{\n\t\t\ttitle:           \"Queries route with regexp pattern, regexp does not match\",\n\t\t\troute:           new(Route).Queries(\"foo\", \"{v1:[0-9]+}\"),\n\t\t\trequest:         newRequest(\"GET\", \"http://localhost?foo=a\"),\n\t\t\tvars:            nil,\n\t\t\thost:            \"\",\n\t\t\tpath:            \"\",\n\t\t\tqueriesTemplate: \"foo={v1:[0-9]+}\",\n\t\t\tqueriesRegexp:   \"^foo=(?P<v0>[0-9]+)$\",\n\t\t\tshouldMatch:     false,\n\t\t},\n\t\t{\n\t\t\ttitle:           \"Queries route with regexp pattern with quantifier, match\",\n\t\t\troute:           new(Route).Queries(\"foo\", \"{v1:[0-9]{1}}\"),\n\t\t\trequest:         newRequest(\"GET\", \"http://localhost?foo=1\"),\n\t\t\tvars:            map[string]string{\"v1\": \"1\"},\n\t\t\thost:            \"\",\n\t\t\tpath:            \"\",\n\t\t\tquery:           \"foo=1\",\n\t\t\tqueriesTemplate: \"foo={v1:[0-9]{1}}\",\n\t\t\tqueriesRegexp:   \"^foo=(?P<v0>[0-9]{1})$\",\n\t\t\tshouldMatch:     true,\n\t\t},\n\t\t{\n\t\t\ttitle:           \"Queries route with regexp pattern with quantifier, additional variable in query string, match\",\n\t\t\troute:           new(Route).Queries(\"foo\", \"{v1:[0-9]{1}}\"),\n\t\t\trequest:         newRequest(\"GET\", \"http://localhost?bar=2&foo=1\"),\n\t\t\tvars:            map[string]string{\"v1\": \"1\"},\n\t\t\thost:            \"\",\n\t\t\tpath:            \"\",\n\t\t\tquery:           \"foo=1\",\n\t\t\tqueriesTemplate: \"foo={v1:[0-9]{1}}\",\n\t\t\tqueriesRegexp:   \"^foo=(?P<v0>[0-9]{1})$\",\n\t\t\tshouldMatch:     true,\n\t\t},\n\t\t{\n\t\t\ttitle:           \"Queries route with regexp pattern with quantifier, regexp does not match\",\n\t\t\troute:           new(Route).Queries(\"foo\", \"{v1:[0-9]{1}}\"),\n\t\t\trequest:         newRequest(\"GET\", \"http://localhost?foo=12\"),\n\t\t\tvars:            nil,\n\t\t\thost:            \"\",\n\t\t\tpath:            \"\",\n\t\t\tqueriesTemplate: \"foo={v1:[0-9]{1}}\",\n\t\t\tqueriesRegexp:   \"^foo=(?P<v0>[0-9]{1})$\",\n\t\t\tshouldMatch:     false,\n\t\t},\n\t\t{\n\t\t\ttitle:           \"Queries route with regexp pattern with quantifier, additional capturing group\",\n\t\t\troute:           new(Route).Queries(\"foo\", \"{v1:[0-9]{1}(?:a|b)}\"),\n\t\t\trequest:         newRequest(\"GET\", \"http://localhost?foo=1a\"),\n\t\t\tvars:            map[string]string{\"v1\": \"1a\"},\n\t\t\thost:            \"\",\n\t\t\tpath:            \"\",\n\t\t\tquery:           \"foo=1a\",\n\t\t\tqueriesTemplate: \"foo={v1:[0-9]{1}(?:a|b)}\",\n\t\t\tqueriesRegexp:   \"^foo=(?P<v0>[0-9]{1}(?:a|b))$\",\n\t\t\tshouldMatch:     true,\n\t\t},\n\t\t{\n\t\t\ttitle:           \"Queries route with regexp pattern with quantifier, additional variable in query string, regexp does not match\",\n\t\t\troute:           new(Route).Queries(\"foo\", \"{v1:[0-9]{1}}\"),\n\t\t\trequest:         newRequest(\"GET\", \"http://localhost?foo=12\"),\n\t\t\tvars:            nil,\n\t\t\thost:            \"\",\n\t\t\tpath:            \"\",\n\t\t\tqueriesTemplate: \"foo={v1:[0-9]{1}}\",\n\t\t\tqueriesRegexp:   \"^foo=(?P<v0>[0-9]{1})$\",\n\t\t\tshouldMatch:     false,\n\t\t},\n\t\t{\n\t\t\ttitle:           \"Queries route with hyphenated name, match\",\n\t\t\troute:           new(Route).Queries(\"foo\", \"{v-1}\"),\n\t\t\trequest:         newRequest(\"GET\", \"http://localhost?foo=bar\"),\n\t\t\tvars:            map[string]string{\"v-1\": \"bar\"},\n\t\t\thost:            \"\",\n\t\t\tpath:            \"\",\n\t\t\tquery:           \"foo=bar\",\n\t\t\tqueriesTemplate: \"foo={v-1}\",\n\t\t\tqueriesRegexp:   \"^foo=(?P<v0>.*)$\",\n\t\t\tshouldMatch:     true,\n\t\t},\n\t\t{\n\t\t\ttitle:           \"Queries route with multiple hyphenated names, match\",\n\t\t\troute:           new(Route).Queries(\"foo\", \"{v-1}\", \"baz\", \"{v-2}\"),\n\t\t\trequest:         newRequest(\"GET\", \"http://localhost?foo=bar&baz=ding\"),\n\t\t\tvars:            map[string]string{\"v-1\": \"bar\", \"v-2\": \"ding\"},\n\t\t\thost:            \"\",\n\t\t\tpath:            \"\",\n\t\t\tquery:           \"foo=bar&baz=ding\",\n\t\t\tqueriesTemplate: \"foo={v-1},baz={v-2}\",\n\t\t\tqueriesRegexp:   \"^foo=(?P<v0>.*)$,^baz=(?P<v0>.*)$\",\n\t\t\tshouldMatch:     true,\n\t\t},\n\t\t{\n\t\t\ttitle:           \"Queries route with hyphenate name and pattern, match\",\n\t\t\troute:           new(Route).Queries(\"foo\", \"{v-1:[0-9]+}\"),\n\t\t\trequest:         newRequest(\"GET\", \"http://localhost?foo=10\"),\n\t\t\tvars:            map[string]string{\"v-1\": \"10\"},\n\t\t\thost:            \"\",\n\t\t\tpath:            \"\",\n\t\t\tquery:           \"foo=10\",\n\t\t\tqueriesTemplate: \"foo={v-1:[0-9]+}\",\n\t\t\tqueriesRegexp:   \"^foo=(?P<v0>[0-9]+)$\",\n\t\t\tshouldMatch:     true,\n\t\t},\n\t\t{\n\t\t\ttitle:           \"Queries route with hyphenated name and pattern with quantifier, additional capturing group\",\n\t\t\troute:           new(Route).Queries(\"foo\", \"{v-1:[0-9]{1}(?:a|b)}\"),\n\t\t\trequest:         newRequest(\"GET\", \"http://localhost?foo=1a\"),\n\t\t\tvars:            map[string]string{\"v-1\": \"1a\"},\n\t\t\thost:            \"\",\n\t\t\tpath:            \"\",\n\t\t\tquery:           \"foo=1a\",\n\t\t\tqueriesTemplate: \"foo={v-1:[0-9]{1}(?:a|b)}\",\n\t\t\tqueriesRegexp:   \"^foo=(?P<v0>[0-9]{1}(?:a|b))$\",\n\t\t\tshouldMatch:     true,\n\t\t},\n\t\t{\n\t\t\ttitle:           \"Queries route with empty value, should match\",\n\t\t\troute:           new(Route).Queries(\"foo\", \"\"),\n\t\t\trequest:         newRequest(\"GET\", \"http://localhost?foo=bar\"),\n\t\t\tvars:            nil,\n\t\t\thost:            \"\",\n\t\t\tpath:            \"\",\n\t\t\tquery:           \"foo=\",\n\t\t\tqueriesTemplate: \"foo=\",\n\t\t\tqueriesRegexp:   \"^foo=.*$\",\n\t\t\tshouldMatch:     true,\n\t\t},\n\t\t{\n\t\t\ttitle:           \"Queries route with empty value and no parameter in request, should not match\",\n\t\t\troute:           new(Route).Queries(\"foo\", \"\"),\n\t\t\trequest:         newRequest(\"GET\", \"http://localhost\"),\n\t\t\tvars:            nil,\n\t\t\thost:            \"\",\n\t\t\tpath:            \"\",\n\t\t\tqueriesTemplate: \"foo=\",\n\t\t\tqueriesRegexp:   \"^foo=.*$\",\n\t\t\tshouldMatch:     false,\n\t\t},\n\t\t{\n\t\t\ttitle:           \"Queries route with empty value and empty parameter in request, should match\",\n\t\t\troute:           new(Route).Queries(\"foo\", \"\"),\n\t\t\trequest:         newRequest(\"GET\", \"http://localhost?foo=\"),\n\t\t\tvars:            nil,\n\t\t\thost:            \"\",\n\t\t\tpath:            \"\",\n\t\t\tquery:           \"foo=\",\n\t\t\tqueriesTemplate: \"foo=\",\n\t\t\tqueriesRegexp:   \"^foo=.*$\",\n\t\t\tshouldMatch:     true,\n\t\t},\n\t\t{\n\t\t\ttitle:           \"Queries route with overlapping value, should not match\",\n\t\t\troute:           new(Route).Queries(\"foo\", \"bar\"),\n\t\t\trequest:         newRequest(\"GET\", \"http://localhost?foo=barfoo\"),\n\t\t\tvars:            nil,\n\t\t\thost:            \"\",\n\t\t\tpath:            \"\",\n\t\t\tqueriesTemplate: \"foo=bar\",\n\t\t\tqueriesRegexp:   \"^foo=bar$\",\n\t\t\tshouldMatch:     false,\n\t\t},\n\t\t{\n\t\t\ttitle:           \"Queries route with no parameter in request, should not match\",\n\t\t\troute:           new(Route).Queries(\"foo\", \"{bar}\"),\n\t\t\trequest:         newRequest(\"GET\", \"http://localhost\"),\n\t\t\tvars:            nil,\n\t\t\thost:            \"\",\n\t\t\tpath:            \"\",\n\t\t\tqueriesTemplate: \"foo={bar}\",\n\t\t\tqueriesRegexp:   \"^foo=(?P<v0>.*)$\",\n\t\t\tshouldMatch:     false,\n\t\t},\n\t\t{\n\t\t\ttitle:           \"Queries route with empty parameter in request, should match\",\n\t\t\troute:           new(Route).Queries(\"foo\", \"{bar}\"),\n\t\t\trequest:         newRequest(\"GET\", \"http://localhost?foo=\"),\n\t\t\tvars:            map[string]string{\"foo\": \"\"},\n\t\t\thost:            \"\",\n\t\t\tpath:            \"\",\n\t\t\tquery:           \"foo=\",\n\t\t\tqueriesTemplate: \"foo={bar}\",\n\t\t\tqueriesRegexp:   \"^foo=(?P<v0>.*)$\",\n\t\t\tshouldMatch:     true,\n\t\t},\n\t\t{\n\t\t\ttitle:           \"Queries route, bad submatch\",\n\t\t\troute:           new(Route).Queries(\"foo\", \"bar\", \"baz\", \"ding\"),\n\t\t\trequest:         newRequest(\"GET\", \"http://localhost?fffoo=bar&baz=dingggg\"),\n\t\t\tvars:            nil,\n\t\t\thost:            \"\",\n\t\t\tpath:            \"\",\n\t\t\tqueriesTemplate: \"foo=bar,baz=ding\",\n\t\t\tqueriesRegexp:   \"^foo=bar$,^baz=ding$\",\n\t\t\tshouldMatch:     false,\n\t\t},\n\t\t{\n\t\t\ttitle:           \"Queries route with pattern, match, escaped value\",\n\t\t\troute:           new(Route).Queries(\"foo\", \"{v1}\"),\n\t\t\trequest:         newRequest(\"GET\", \"http://localhost?foo=%25bar%26%20%2F%3D%3F\"),\n\t\t\tvars:            map[string]string{\"v1\": \"%bar& /=?\"},\n\t\t\thost:            \"\",\n\t\t\tpath:            \"\",\n\t\t\tquery:           \"foo=%25bar%26+%2F%3D%3F\",\n\t\t\tqueriesTemplate: \"foo={v1}\",\n\t\t\tqueriesRegexp:   \"^foo=(?P<v0>.*)$\",\n\t\t\tshouldMatch:     true,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.title, func(t *testing.T) {\n\t\t\ttestTemplate(t, test)\n\t\t\ttestQueriesTemplates(t, test)\n\t\t\ttestUseEscapedRoute(t, test)\n\t\t\ttestQueriesRegexp(t, test)\n\t\t})\n\t}\n}\n\nfunc TestSchemes(t *testing.T) {\n\ttests := []routeTest{\n\t\t// Schemes\n\t\t{\n\t\t\ttitle:       \"Schemes route, default scheme, match http, build http\",\n\t\t\troute:       new(Route).Host(\"localhost\"),\n\t\t\trequest:     newRequest(\"GET\", \"http://localhost\"),\n\t\t\tscheme:      \"http\",\n\t\t\thost:        \"localhost\",\n\t\t\tshouldMatch: true,\n\t\t},\n\t\t{\n\t\t\ttitle:       \"Schemes route, match https, build https\",\n\t\t\troute:       new(Route).Schemes(\"https\", \"ftp\").Host(\"localhost\"),\n\t\t\trequest:     newRequest(\"GET\", \"https://localhost\"),\n\t\t\tscheme:      \"https\",\n\t\t\thost:        \"localhost\",\n\t\t\tshouldMatch: true,\n\t\t},\n\t\t{\n\t\t\ttitle:       \"Schemes route, match ftp, build https\",\n\t\t\troute:       new(Route).Schemes(\"https\", \"ftp\").Host(\"localhost\"),\n\t\t\trequest:     newRequest(\"GET\", \"ftp://localhost\"),\n\t\t\tscheme:      \"https\",\n\t\t\thost:        \"localhost\",\n\t\t\tshouldMatch: true,\n\t\t},\n\t\t{\n\t\t\ttitle:       \"Schemes route, match ftp, build ftp\",\n\t\t\troute:       new(Route).Schemes(\"ftp\", \"https\").Host(\"localhost\"),\n\t\t\trequest:     newRequest(\"GET\", \"ftp://localhost\"),\n\t\t\tscheme:      \"ftp\",\n\t\t\thost:        \"localhost\",\n\t\t\tshouldMatch: true,\n\t\t},\n\t\t{\n\t\t\ttitle:       \"Schemes route, bad scheme\",\n\t\t\troute:       new(Route).Schemes(\"https\", \"ftp\").Host(\"localhost\"),\n\t\t\trequest:     newRequest(\"GET\", \"http://localhost\"),\n\t\t\tscheme:      \"https\",\n\t\t\thost:        \"localhost\",\n\t\t\tshouldMatch: false,\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.title, func(t *testing.T) {\n\t\t\ttestRoute(t, test)\n\t\t\ttestTemplate(t, test)\n\t\t})\n\t}\n}\n\nfunc TestMatcherFunc(t *testing.T) {\n\tm := func(r *http.Request, m *RouteMatch) bool {\n\t\treturn r.URL.Host == \"aaa.bbb.ccc\"\n\t}\n\n\ttests := []routeTest{\n\t\t{\n\t\t\ttitle:       \"MatchFunc route, match\",\n\t\t\troute:       new(Route).MatcherFunc(m),\n\t\t\trequest:     newRequest(\"GET\", \"http://aaa.bbb.ccc\"),\n\t\t\tvars:        nil,\n\t\t\thost:        \"\",\n\t\t\tpath:        \"\",\n\t\t\tshouldMatch: true,\n\t\t},\n\t\t{\n\t\t\ttitle:       \"MatchFunc route, non-match\",\n\t\t\troute:       new(Route).MatcherFunc(m),\n\t\t\trequest:     newRequest(\"GET\", \"http://aaa.222.ccc\"),\n\t\t\tvars:        nil,\n\t\t\thost:        \"\",\n\t\t\tpath:        \"\",\n\t\t\tshouldMatch: false,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.title, func(t *testing.T) {\n\t\t\ttestRoute(t, test)\n\t\t\ttestTemplate(t, test)\n\t\t})\n\t}\n}\n\nfunc TestBuildVarsFunc(t *testing.T) {\n\ttests := []routeTest{\n\t\t{\n\t\t\ttitle: \"BuildVarsFunc set on route\",\n\t\t\troute: new(Route).Path(`/111/{v1:\\d}{v2:.*}`).BuildVarsFunc(func(vars map[string]string) map[string]string {\n\t\t\t\tvars[\"v1\"] = \"3\"\n\t\t\t\tvars[\"v2\"] = \"a\"\n\t\t\t\treturn vars\n\t\t\t}),\n\t\t\trequest:      newRequest(\"GET\", \"http://localhost/111/2\"),\n\t\t\tpath:         \"/111/3a\",\n\t\t\tpathTemplate: `/111/{v1:\\d}{v2:.*}`,\n\t\t\tshouldMatch:  true,\n\t\t},\n\t\t{\n\t\t\ttitle: \"BuildVarsFunc set on route and parent route\",\n\t\t\troute: new(Route).PathPrefix(`/{v1:\\d}`).BuildVarsFunc(func(vars map[string]string) map[string]string {\n\t\t\t\tvars[\"v1\"] = \"2\"\n\t\t\t\treturn vars\n\t\t\t}).Subrouter().Path(`/{v2:\\w}`).BuildVarsFunc(func(vars map[string]string) map[string]string {\n\t\t\t\tvars[\"v2\"] = \"b\"\n\t\t\t\treturn vars\n\t\t\t}),\n\t\t\trequest:      newRequest(\"GET\", \"http://localhost/1/a\"),\n\t\t\tpath:         \"/2/b\",\n\t\t\tpathTemplate: `/{v1:\\d}/{v2:\\w}`,\n\t\t\tshouldMatch:  true,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.title, func(t *testing.T) {\n\t\t\ttestRoute(t, test)\n\t\t\ttestTemplate(t, test)\n\t\t})\n\t}\n}\n\nfunc TestSubRouter(t *testing.T) {\n\tsubrouter1 := new(Route).Host(\"{v1:[a-z]+}.google.com\").Subrouter()\n\tsubrouter2 := new(Route).PathPrefix(\"/foo/{v1}\").Subrouter()\n\tsubrouter3 := new(Route).PathPrefix(\"/foo\").Subrouter()\n\tsubrouter4 := new(Route).PathPrefix(\"/foo/bar\").Subrouter()\n\tsubrouter5 := new(Route).PathPrefix(\"/{category}\").Subrouter()\n\ttests := []routeTest{\n\t\t{\n\t\t\troute:        subrouter1.Path(\"/{v2:[a-z]+}\"),\n\t\t\trequest:      newRequest(\"GET\", \"http://aaa.google.com/bbb\"),\n\t\t\tvars:         map[string]string{\"v1\": \"aaa\", \"v2\": \"bbb\"},\n\t\t\thost:         \"aaa.google.com\",\n\t\t\tpath:         \"/bbb\",\n\t\t\tpathTemplate: `/{v2:[a-z]+}`,\n\t\t\thostTemplate: `{v1:[a-z]+}.google.com`,\n\t\t\tshouldMatch:  true,\n\t\t},\n\t\t{\n\t\t\troute:        subrouter1.Path(\"/{v2:[a-z]+}\"),\n\t\t\trequest:      newRequest(\"GET\", \"http://111.google.com/111\"),\n\t\t\tvars:         map[string]string{\"v1\": \"aaa\", \"v2\": \"bbb\"},\n\t\t\thost:         \"aaa.google.com\",\n\t\t\tpath:         \"/bbb\",\n\t\t\tpathTemplate: `/{v2:[a-z]+}`,\n\t\t\thostTemplate: `{v1:[a-z]+}.google.com`,\n\t\t\tshouldMatch:  false,\n\t\t},\n\t\t{\n\t\t\troute:        subrouter2.Path(\"/baz/{v2}\"),\n\t\t\trequest:      newRequest(\"GET\", \"http://localhost/foo/bar/baz/ding\"),\n\t\t\tvars:         map[string]string{\"v1\": \"bar\", \"v2\": \"ding\"},\n\t\t\thost:         \"\",\n\t\t\tpath:         \"/foo/bar/baz/ding\",\n\t\t\tpathTemplate: `/foo/{v1}/baz/{v2}`,\n\t\t\tshouldMatch:  true,\n\t\t},\n\t\t{\n\t\t\troute:        subrouter2.Path(\"/baz/{v2}\"),\n\t\t\trequest:      newRequest(\"GET\", \"http://localhost/foo/bar\"),\n\t\t\tvars:         map[string]string{\"v1\": \"bar\", \"v2\": \"ding\"},\n\t\t\thost:         \"\",\n\t\t\tpath:         \"/foo/bar/baz/ding\",\n\t\t\tpathTemplate: `/foo/{v1}/baz/{v2}`,\n\t\t\tshouldMatch:  false,\n\t\t},\n\t\t{\n\t\t\troute:        subrouter3.Path(\"/\"),\n\t\t\trequest:      newRequest(\"GET\", \"http://localhost/foo/\"),\n\t\t\tvars:         nil,\n\t\t\thost:         \"\",\n\t\t\tpath:         \"/foo/\",\n\t\t\tpathTemplate: `/foo/`,\n\t\t\tshouldMatch:  true,\n\t\t},\n\t\t{\n\t\t\troute:        subrouter3.Path(\"\"),\n\t\t\trequest:      newRequest(\"GET\", \"http://localhost/foo\"),\n\t\t\tvars:         nil,\n\t\t\thost:         \"\",\n\t\t\tpath:         \"/foo\",\n\t\t\tpathTemplate: `/foo`,\n\t\t\tshouldMatch:  true,\n\t\t},\n\n\t\t{\n\t\t\troute:        subrouter4.Path(\"/\"),\n\t\t\trequest:      newRequest(\"GET\", \"http://localhost/foo/bar/\"),\n\t\t\tvars:         nil,\n\t\t\thost:         \"\",\n\t\t\tpath:         \"/foo/bar/\",\n\t\t\tpathTemplate: `/foo/bar/`,\n\t\t\tshouldMatch:  true,\n\t\t},\n\t\t{\n\t\t\troute:        subrouter4.Path(\"\"),\n\t\t\trequest:      newRequest(\"GET\", \"http://localhost/foo/bar\"),\n\t\t\tvars:         nil,\n\t\t\thost:         \"\",\n\t\t\tpath:         \"/foo/bar\",\n\t\t\tpathTemplate: `/foo/bar`,\n\t\t\tshouldMatch:  true,\n\t\t},\n\t\t{\n\t\t\troute:        subrouter5.Path(\"/\"),\n\t\t\trequest:      newRequest(\"GET\", \"http://localhost/baz/\"),\n\t\t\tvars:         map[string]string{\"category\": \"baz\"},\n\t\t\thost:         \"\",\n\t\t\tpath:         \"/baz/\",\n\t\t\tpathTemplate: `/{category}/`,\n\t\t\tshouldMatch:  true,\n\t\t},\n\t\t{\n\t\t\troute:        subrouter5.Path(\"\"),\n\t\t\trequest:      newRequest(\"GET\", \"http://localhost/baz\"),\n\t\t\tvars:         map[string]string{\"category\": \"baz\"},\n\t\t\thost:         \"\",\n\t\t\tpath:         \"/baz\",\n\t\t\tpathTemplate: `/{category}`,\n\t\t\tshouldMatch:  true,\n\t\t},\n\t\t{\n\t\t\ttitle:        \"Mismatch method specified on parent route\",\n\t\t\troute:        new(Route).Methods(\"POST\").PathPrefix(\"/foo\").Subrouter().Path(\"/\"),\n\t\t\trequest:      newRequest(\"GET\", \"http://localhost/foo/\"),\n\t\t\tvars:         nil,\n\t\t\thost:         \"\",\n\t\t\tpath:         \"/foo/\",\n\t\t\tpathTemplate: `/foo/`,\n\t\t\tshouldMatch:  false,\n\t\t},\n\t\t{\n\t\t\ttitle:        \"Match method specified on parent route\",\n\t\t\troute:        new(Route).Methods(\"POST\").PathPrefix(\"/foo\").Subrouter().Path(\"/\"),\n\t\t\trequest:      newRequest(\"POST\", \"http://localhost/foo/\"),\n\t\t\tvars:         nil,\n\t\t\thost:         \"\",\n\t\t\tpath:         \"/foo/\",\n\t\t\tpathTemplate: `/foo/`,\n\t\t\tshouldMatch:  true,\n\t\t},\n\t\t{\n\t\t\ttitle:        \"Mismatch scheme specified on parent route\",\n\t\t\troute:        new(Route).Schemes(\"https\").Subrouter().PathPrefix(\"/\"),\n\t\t\trequest:      newRequest(\"GET\", \"http://localhost/\"),\n\t\t\tvars:         nil,\n\t\t\thost:         \"\",\n\t\t\tpath:         \"/\",\n\t\t\tpathTemplate: `/`,\n\t\t\tshouldMatch:  false,\n\t\t},\n\t\t{\n\t\t\ttitle:        \"Match scheme specified on parent route\",\n\t\t\troute:        new(Route).Schemes(\"http\").Subrouter().PathPrefix(\"/\"),\n\t\t\trequest:      newRequest(\"GET\", \"http://localhost/\"),\n\t\t\tvars:         nil,\n\t\t\thost:         \"\",\n\t\t\tpath:         \"/\",\n\t\t\tpathTemplate: `/`,\n\t\t\tshouldMatch:  true,\n\t\t},\n\t\t{\n\t\t\ttitle:        \"No match header specified on parent route\",\n\t\t\troute:        new(Route).Headers(\"X-Forwarded-Proto\", \"https\").Subrouter().PathPrefix(\"/\"),\n\t\t\trequest:      newRequest(\"GET\", \"http://localhost/\"),\n\t\t\tvars:         nil,\n\t\t\thost:         \"\",\n\t\t\tpath:         \"/\",\n\t\t\tpathTemplate: `/`,\n\t\t\tshouldMatch:  false,\n\t\t},\n\t\t{\n\t\t\ttitle:        \"Header mismatch value specified on parent route\",\n\t\t\troute:        new(Route).Headers(\"X-Forwarded-Proto\", \"https\").Subrouter().PathPrefix(\"/\"),\n\t\t\trequest:      newRequestWithHeaders(\"GET\", \"http://localhost/\", \"X-Forwarded-Proto\", \"http\"),\n\t\t\tvars:         nil,\n\t\t\thost:         \"\",\n\t\t\tpath:         \"/\",\n\t\t\tpathTemplate: `/`,\n\t\t\tshouldMatch:  false,\n\t\t},\n\t\t{\n\t\t\ttitle:        \"Header match value specified on parent route\",\n\t\t\troute:        new(Route).Headers(\"X-Forwarded-Proto\", \"https\").Subrouter().PathPrefix(\"/\"),\n\t\t\trequest:      newRequestWithHeaders(\"GET\", \"http://localhost/\", \"X-Forwarded-Proto\", \"https\"),\n\t\t\tvars:         nil,\n\t\t\thost:         \"\",\n\t\t\tpath:         \"/\",\n\t\t\tpathTemplate: `/`,\n\t\t\tshouldMatch:  true,\n\t\t},\n\t\t{\n\t\t\ttitle:        \"Query specified on parent route not present\",\n\t\t\troute:        new(Route).Headers(\"key\", \"foobar\").Subrouter().PathPrefix(\"/\"),\n\t\t\trequest:      newRequest(\"GET\", \"http://localhost/\"),\n\t\t\tvars:         nil,\n\t\t\thost:         \"\",\n\t\t\tpath:         \"/\",\n\t\t\tpathTemplate: `/`,\n\t\t\tshouldMatch:  false,\n\t\t},\n\t\t{\n\t\t\ttitle:        \"Query mismatch value specified on parent route\",\n\t\t\troute:        new(Route).Queries(\"key\", \"foobar\").Subrouter().PathPrefix(\"/\"),\n\t\t\trequest:      newRequest(\"GET\", \"http://localhost/?key=notfoobar\"),\n\t\t\tvars:         nil,\n\t\t\thost:         \"\",\n\t\t\tpath:         \"/\",\n\t\t\tpathTemplate: `/`,\n\t\t\tshouldMatch:  false,\n\t\t},\n\t\t{\n\t\t\ttitle:        \"Query match value specified on subroute\",\n\t\t\troute:        new(Route).Queries(\"key\", \"foobar\").Subrouter().PathPrefix(\"/\"),\n\t\t\trequest:      newRequest(\"GET\", \"http://localhost/?key=foobar\"),\n\t\t\tvars:         nil,\n\t\t\thost:         \"\",\n\t\t\tpath:         \"/\",\n\t\t\tpathTemplate: `/`,\n\t\t\tshouldMatch:  true,\n\t\t},\n\t\t{\n\t\t\ttitle:        \"Build with scheme on parent router\",\n\t\t\troute:        new(Route).Schemes(\"ftp\").Host(\"google.com\").Subrouter().Path(\"/\"),\n\t\t\trequest:      newRequest(\"GET\", \"ftp://google.com/\"),\n\t\t\tscheme:       \"ftp\",\n\t\t\thost:         \"google.com\",\n\t\t\tpath:         \"/\",\n\t\t\tpathTemplate: `/`,\n\t\t\thostTemplate: `google.com`,\n\t\t\tshouldMatch:  true,\n\t\t},\n\t\t{\n\t\t\ttitle:        \"Prefer scheme on child route when building URLs\",\n\t\t\troute:        new(Route).Schemes(\"https\", \"ftp\").Host(\"google.com\").Subrouter().Schemes(\"ftp\").Path(\"/\"),\n\t\t\trequest:      newRequest(\"GET\", \"ftp://google.com/\"),\n\t\t\tscheme:       \"ftp\",\n\t\t\thost:         \"google.com\",\n\t\t\tpath:         \"/\",\n\t\t\tpathTemplate: `/`,\n\t\t\thostTemplate: `google.com`,\n\t\t\tshouldMatch:  true,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.title, func(t *testing.T) {\n\t\t\ttestRoute(t, test)\n\t\t\ttestTemplate(t, test)\n\t\t\ttestUseEscapedRoute(t, test)\n\t\t})\n\t}\n}\n\nfunc TestNamedRoutes(t *testing.T) {\n\tr1 := NewRouter()\n\tr1.NewRoute().Name(\"a\")\n\tr1.NewRoute().Name(\"b\")\n\tr1.NewRoute().Name(\"c\")\n\n\tr2 := r1.NewRoute().Subrouter()\n\tr2.NewRoute().Name(\"d\")\n\tr2.NewRoute().Name(\"e\")\n\tr2.NewRoute().Name(\"f\")\n\n\tr3 := r2.NewRoute().Subrouter()\n\tr3.NewRoute().Name(\"g\")\n\tr3.NewRoute().Name(\"h\")\n\tr3.NewRoute().Name(\"i\")\n\tr3.Name(\"j\")\n\n\tif r1.namedRoutes == nil || len(r1.namedRoutes) != 10 {\n\t\tt.Errorf(\"Expected 10 named routes, got %v\", r1.namedRoutes)\n\t} else if r1.Get(\"j\") == nil {\n\t\tt.Errorf(\"Subroute name not registered\")\n\t}\n}\n\nfunc TestNameMultipleCalls(t *testing.T) {\n\tr1 := NewRouter()\n\trt := r1.NewRoute().Name(\"foo\").Name(\"bar\")\n\terr := rt.GetError()\n\tif err == nil {\n\t\tt.Errorf(\"Expected an error\")\n\t}\n}\n\nfunc TestStrictSlash(t *testing.T) {\n\tr := NewRouter()\n\tr.StrictSlash(true)\n\n\ttests := []routeTest{\n\t\t{\n\t\t\ttitle:          \"Redirect path without slash\",\n\t\t\troute:          r.NewRoute().Path(\"/111/\"),\n\t\t\trequest:        newRequest(\"GET\", \"http://localhost/111\"),\n\t\t\tvars:           nil,\n\t\t\thost:           \"\",\n\t\t\tpath:           \"/111/\",\n\t\t\tshouldMatch:    true,\n\t\t\tshouldRedirect: true,\n\t\t},\n\t\t{\n\t\t\ttitle:          \"Do not redirect path with slash\",\n\t\t\troute:          r.NewRoute().Path(\"/111/\"),\n\t\t\trequest:        newRequest(\"GET\", \"http://localhost/111/\"),\n\t\t\tvars:           nil,\n\t\t\thost:           \"\",\n\t\t\tpath:           \"/111/\",\n\t\t\tshouldMatch:    true,\n\t\t\tshouldRedirect: false,\n\t\t},\n\t\t{\n\t\t\ttitle:          \"Redirect path with slash\",\n\t\t\troute:          r.NewRoute().Path(\"/111\"),\n\t\t\trequest:        newRequest(\"GET\", \"http://localhost/111/\"),\n\t\t\tvars:           nil,\n\t\t\thost:           \"\",\n\t\t\tpath:           \"/111\",\n\t\t\tshouldMatch:    true,\n\t\t\tshouldRedirect: true,\n\t\t},\n\t\t{\n\t\t\ttitle:          \"Do not redirect path without slash\",\n\t\t\troute:          r.NewRoute().Path(\"/111\"),\n\t\t\trequest:        newRequest(\"GET\", \"http://localhost/111\"),\n\t\t\tvars:           nil,\n\t\t\thost:           \"\",\n\t\t\tpath:           \"/111\",\n\t\t\tshouldMatch:    true,\n\t\t\tshouldRedirect: false,\n\t\t},\n\t\t{\n\t\t\ttitle:          \"Propagate StrictSlash to subrouters\",\n\t\t\troute:          r.NewRoute().PathPrefix(\"/static/\").Subrouter().Path(\"/images/\"),\n\t\t\trequest:        newRequest(\"GET\", \"http://localhost/static/images\"),\n\t\t\tvars:           nil,\n\t\t\thost:           \"\",\n\t\t\tpath:           \"/static/images/\",\n\t\t\tshouldMatch:    true,\n\t\t\tshouldRedirect: true,\n\t\t},\n\t\t{\n\t\t\ttitle:          \"Ignore StrictSlash for path prefix\",\n\t\t\troute:          r.NewRoute().PathPrefix(\"/static/\"),\n\t\t\trequest:        newRequest(\"GET\", \"http://localhost/static/logo.png\"),\n\t\t\tvars:           nil,\n\t\t\thost:           \"\",\n\t\t\tpath:           \"/static/\",\n\t\t\tshouldMatch:    true,\n\t\t\tshouldRedirect: false,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.title, func(t *testing.T) {\n\t\t\ttestRoute(t, test)\n\t\t\ttestTemplate(t, test)\n\t\t\ttestUseEscapedRoute(t, test)\n\t\t})\n\t}\n}\n\nfunc TestUseEncodedPath(t *testing.T) {\n\tr := NewRouter()\n\tr.UseEncodedPath()\n\n\ttests := []routeTest{\n\t\t{\n\t\t\ttitle:        \"Router with useEncodedPath, URL with encoded slash does match\",\n\t\t\troute:        r.NewRoute().Path(\"/v1/{v1}/v2\"),\n\t\t\trequest:      newRequest(\"GET\", \"http://localhost/v1/1%2F2/v2\"),\n\t\t\tvars:         map[string]string{\"v1\": \"1%2F2\"},\n\t\t\thost:         \"\",\n\t\t\tpath:         \"/v1/1%2F2/v2\",\n\t\t\tpathTemplate: `/v1/{v1}/v2`,\n\t\t\tshouldMatch:  true,\n\t\t},\n\t\t{\n\t\t\ttitle:        \"Router with useEncodedPath, URL with encoded slash doesn't match\",\n\t\t\troute:        r.NewRoute().Path(\"/v1/1/2/v2\"),\n\t\t\trequest:      newRequest(\"GET\", \"http://localhost/v1/1%2F2/v2\"),\n\t\t\tvars:         map[string]string{\"v1\": \"1%2F2\"},\n\t\t\thost:         \"\",\n\t\t\tpath:         \"/v1/1%2F2/v2\",\n\t\t\tpathTemplate: `/v1/1/2/v2`,\n\t\t\tshouldMatch:  false,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.title, func(t *testing.T) {\n\t\t\ttestRoute(t, test)\n\t\t\ttestTemplate(t, test)\n\t\t})\n\t}\n}\n\nfunc TestWalkSingleDepth(t *testing.T) {\n\tr0 := NewRouter()\n\tr1 := NewRouter()\n\tr2 := NewRouter()\n\n\tr0.Path(\"/g\")\n\tr0.Path(\"/o\")\n\tr0.Path(\"/d\").Handler(r1)\n\tr0.Path(\"/r\").Handler(r2)\n\tr0.Path(\"/a\")\n\n\tr1.Path(\"/z\")\n\tr1.Path(\"/i\")\n\tr1.Path(\"/l\")\n\tr1.Path(\"/l\")\n\n\tr2.Path(\"/i\")\n\tr2.Path(\"/l\")\n\tr2.Path(\"/l\")\n\n\tpaths := []string{\"g\", \"o\", \"r\", \"i\", \"l\", \"l\", \"a\"}\n\tdepths := []int{0, 0, 0, 1, 1, 1, 0}\n\ti := 0\n\terr := r0.Walk(func(route *Route, router *Router, ancestors []*Route) error {\n\t\tmatcher := route.matchers[0].(*routeRegexp)\n\t\tif matcher.template == \"/d\" {\n\t\t\treturn SkipRouter\n\t\t}\n\t\tif len(ancestors) != depths[i] {\n\t\t\tt.Errorf(`Expected depth of %d at i = %d; got \"%d\"`, depths[i], i, len(ancestors))\n\t\t}\n\t\tif matcher.template != \"/\"+paths[i] {\n\t\t\tt.Errorf(`Expected \"/%s\" at i = %d; got \"%s\"`, paths[i], i, matcher.template)\n\t\t}\n\t\ti++\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tif i != len(paths) {\n\t\tt.Errorf(\"Expected %d routes, found %d\", len(paths), i)\n\t}\n}\n\nfunc TestWalkNested(t *testing.T) {\n\trouter := NewRouter()\n\n\trouteSubrouter := func(r *Route) (*Route, *Router) {\n\t\treturn r, r.Subrouter()\n\t}\n\n\tgRoute, g := routeSubrouter(router.Path(\"/g\"))\n\toRoute, o := routeSubrouter(g.PathPrefix(\"/o\"))\n\trRoute, r := routeSubrouter(o.PathPrefix(\"/r\"))\n\tiRoute, i := routeSubrouter(r.PathPrefix(\"/i\"))\n\tl1Route, l1 := routeSubrouter(i.PathPrefix(\"/l\"))\n\tl2Route, l2 := routeSubrouter(l1.PathPrefix(\"/l\"))\n\tl2.Path(\"/a\")\n\n\ttestCases := []struct {\n\t\tpath      string\n\t\tancestors []*Route\n\t}{\n\t\t{\"/g\", []*Route{}},\n\t\t{\"/g/o\", []*Route{gRoute}},\n\t\t{\"/g/o/r\", []*Route{gRoute, oRoute}},\n\t\t{\"/g/o/r/i\", []*Route{gRoute, oRoute, rRoute}},\n\t\t{\"/g/o/r/i/l\", []*Route{gRoute, oRoute, rRoute, iRoute}},\n\t\t{\"/g/o/r/i/l/l\", []*Route{gRoute, oRoute, rRoute, iRoute, l1Route}},\n\t\t{\"/g/o/r/i/l/l/a\", []*Route{gRoute, oRoute, rRoute, iRoute, l1Route, l2Route}},\n\t}\n\n\tidx := 0\n\terr := router.Walk(func(route *Route, router *Router, ancestors []*Route) error {\n\t\tpath := testCases[idx].path\n\t\ttpl := route.regexp.path.template\n\t\tif tpl != path {\n\t\t\tt.Errorf(`Expected %s got %s`, path, tpl)\n\t\t}\n\t\tcurrWantAncestors := testCases[idx].ancestors\n\t\tif !reflect.DeepEqual(currWantAncestors, ancestors) {\n\t\t\tt.Errorf(`Expected %+v got %+v`, currWantAncestors, ancestors)\n\t\t}\n\t\tidx++\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tif idx != len(testCases) {\n\t\tt.Errorf(\"Expected %d routes, found %d\", len(testCases), idx)\n\t}\n}\n\nfunc TestWalkSubrouters(t *testing.T) {\n\trouter := NewRouter()\n\n\tg := router.Path(\"/g\").Subrouter()\n\to := g.PathPrefix(\"/o\").Subrouter()\n\to.Methods(\"GET\")\n\to.Methods(\"PUT\")\n\n\t// all 4 routes should be matched\n\tpaths := []string{\"/g\", \"/g/o\", \"/g/o\", \"/g/o\"}\n\tidx := 0\n\terr := router.Walk(func(route *Route, router *Router, ancestors []*Route) error {\n\t\tpath := paths[idx]\n\t\ttpl, _ := route.GetPathTemplate()\n\t\tif tpl != path {\n\t\t\tt.Errorf(`Expected %s got %s`, path, tpl)\n\t\t}\n\t\tidx++\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tif idx != len(paths) {\n\t\tt.Errorf(\"Expected %d routes, found %d\", len(paths), idx)\n\t}\n}\n\nfunc TestWalkErrorRoute(t *testing.T) {\n\trouter := NewRouter()\n\trouter.Path(\"/g\")\n\texpectedError := errors.New(\"error\")\n\terr := router.Walk(func(route *Route, router *Router, ancestors []*Route) error {\n\t\treturn expectedError\n\t})\n\tif err != expectedError {\n\t\tt.Errorf(\"Expected %v routes, found %v\", expectedError, err)\n\t}\n}\n\nfunc TestWalkErrorMatcher(t *testing.T) {\n\trouter := NewRouter()\n\texpectedError := router.Path(\"/g\").Subrouter().Path(\"\").GetError()\n\terr := router.Walk(func(route *Route, router *Router, ancestors []*Route) error {\n\t\treturn route.GetError()\n\t})\n\tif err != expectedError {\n\t\tt.Errorf(\"Expected %v routes, found %v\", expectedError, err)\n\t}\n}\n\nfunc TestWalkErrorHandler(t *testing.T) {\n\thandler := NewRouter()\n\texpectedError := handler.Path(\"/path\").Subrouter().Path(\"\").GetError()\n\trouter := NewRouter()\n\trouter.Path(\"/g\").Handler(handler)\n\terr := router.Walk(func(route *Route, router *Router, ancestors []*Route) error {\n\t\treturn route.GetError()\n\t})\n\tif err != expectedError {\n\t\tt.Errorf(\"Expected %v routes, found %v\", expectedError, err)\n\t}\n}\n\nfunc TestSubrouterErrorHandling(t *testing.T) {\n\tsuperRouterCalled := false\n\tsubRouterCalled := false\n\n\trouter := NewRouter()\n\trouter.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tsuperRouterCalled = true\n\t})\n\tsubRouter := router.PathPrefix(\"/bign8\").Subrouter()\n\tsubRouter.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tsubRouterCalled = true\n\t})\n\n\treq, _ := http.NewRequest(\"GET\", \"http://localhost/bign8/was/here\", nil)\n\trouter.ServeHTTP(NewRecorder(), req)\n\n\tif superRouterCalled {\n\t\tt.Error(\"Super router 404 handler called when sub-router 404 handler is available.\")\n\t}\n\tif !subRouterCalled {\n\t\tt.Error(\"Sub-router 404 handler was not called.\")\n\t}\n}\n\n// See: https://github.com/gorilla/mux/issues/200\nfunc TestPanicOnCapturingGroups(t *testing.T) {\n\tdefer func() {\n\t\tif recover() == nil {\n\t\t\tt.Errorf(\"(Test that capturing groups now fail fast) Expected panic, however test completed successfully.\\n\")\n\t\t}\n\t}()\n\tNewRouter().NewRoute().Path(\"/{type:(promo|special)}/{promoId}.json\")\n}\n\nfunc TestRouterInContext(t *testing.T) {\n\trouter := NewRouter()\n\trouter.HandleFunc(\"/r1\", func(w http.ResponseWriter, r *http.Request) {\n\t\tcontextRouter := CurrentRouter(r)\n\t\tif contextRouter == nil {\n\t\t\tt.Fatal(\"Router not found in context\")\n\t\t\treturn\n\t\t}\n\n\t\troute := contextRouter.Get(\"r2\")\n\t\tif route == nil {\n\t\t\tt.Fatal(\"Route with name not found\")\n\t\t\treturn\n\t\t}\n\n\t\turl, err := route.URL()\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Error while getting url for r2: \", err)\n\t\t\treturn\n\t\t}\n\n\t\t_, err = w.Write([]byte(url.String()))\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Failed writing HTTP response: %v\", err)\n\t\t}\n\t}).Name(\"r1\")\n\n\tnoRouterMsg := []byte(\"no-router\")\n\thaveRouterMsg := []byte(\"have-router\")\n\trouter.HandleFunc(\"/r2\", func(w http.ResponseWriter, r *http.Request) {\n\t\tvar msg []byte\n\n\t\tcontextRouter := CurrentRouter(r)\n\t\tif contextRouter == nil {\n\t\t\tmsg = noRouterMsg\n\t\t} else {\n\t\t\tmsg = haveRouterMsg\n\t\t}\n\n\t\t_, err := w.Write(msg)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Failed writing HTTP response: %v\", err)\n\t\t}\n\t}).Name(\"r2\")\n\n\tt.Run(\"router in request context get route by name\", func(t *testing.T) {\n\t\trw := NewRecorder()\n\t\treq := newRequest(\"GET\", \"/r1\")\n\n\t\trouter.ServeHTTP(rw, req)\n\t\tif !bytes.Equal(rw.Body.Bytes(), []byte(\"/r2\")) {\n\t\t\tt.Fatalf(\"Expected output to be '/r1' but got '%s'\", rw.Body.String())\n\t\t}\n\t})\n\n\tt.Run(\"omit router from request context\", func(t *testing.T) {\n\t\trw := NewRecorder()\n\t\treq := newRequest(\"GET\", \"/r2\")\n\n\t\trouter.OmitRouterFromContext(true)\n\t\trouter.ServeHTTP(rw, req)\n\t\tif !bytes.Equal(rw.Body.Bytes(), noRouterMsg) {\n\t\t\tt.Fatal(\"Router not omitted from context\")\n\t\t}\n\t})\n}\n\n// ----------------------------------------------------------------------------\n// Helpers\n// ----------------------------------------------------------------------------\n\nfunc getRouteTemplate(route *Route) string {\n\thost, err := route.GetHostTemplate()\n\tif err != nil {\n\t\thost = \"none\"\n\t}\n\tpath, err := route.GetPathTemplate()\n\tif err != nil {\n\t\tpath = \"none\"\n\t}\n\treturn fmt.Sprintf(\"Host: %v, Path: %v\", host, path)\n}\n\nfunc testRoute(t *testing.T, test routeTest) {\n\trequest := test.request\n\troute := test.route\n\tvars := test.vars\n\tshouldMatch := test.shouldMatch\n\tquery := test.query\n\tshouldRedirect := test.shouldRedirect\n\turi := url.URL{\n\t\tScheme: test.scheme,\n\t\tHost:   test.host,\n\t\tPath:   test.path,\n\t}\n\tif uri.Scheme == \"\" {\n\t\turi.Scheme = \"http\"\n\t}\n\n\tvar match RouteMatch\n\tok := route.Match(request, &match)\n\tif ok != shouldMatch {\n\t\tmsg := \"Should match\"\n\t\tif !shouldMatch {\n\t\t\tmsg = \"Should not match\"\n\t\t}\n\t\tt.Errorf(\"(%v) %v:\\nRoute: %#v\\nRequest: %#v\\nVars: %v\\n\", test.title, msg, route, request, vars)\n\t\treturn\n\t}\n\tif shouldMatch {\n\t\tif vars != nil && !stringMapEqual(vars, match.Vars) {\n\t\t\tt.Errorf(\"(%v) Vars not equal: expected %v, got %v\", test.title, vars, match.Vars)\n\t\t\treturn\n\t\t}\n\t\tif test.scheme != \"\" {\n\t\t\tu, err := route.URL(mapToPairs(match.Vars)...)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"(%v) URL error: %v -- %v\", test.title, err, getRouteTemplate(route))\n\t\t\t}\n\t\t\tif uri.Scheme != u.Scheme {\n\t\t\t\tt.Errorf(\"(%v) URLScheme not equal: expected %v, got %v\", test.title, uri.Scheme, u.Scheme)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tif test.host != \"\" {\n\t\t\tu, err := test.route.URLHost(mapToPairs(match.Vars)...)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"(%v) URLHost error: %v -- %v\", test.title, err, getRouteTemplate(route))\n\t\t\t}\n\t\t\tif uri.Scheme != u.Scheme {\n\t\t\t\tt.Errorf(\"(%v) URLHost scheme not equal: expected %v, got %v -- %v\", test.title, uri.Scheme, u.Scheme, getRouteTemplate(route))\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif uri.Host != u.Host {\n\t\t\t\tt.Errorf(\"(%v) URLHost host not equal: expected %v, got %v -- %v\", test.title, uri.Host, u.Host, getRouteTemplate(route))\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tif test.path != \"\" {\n\t\t\tu, err := route.URLPath(mapToPairs(match.Vars)...)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"(%v) URLPath error: %v -- %v\", test.title, err, getRouteTemplate(route))\n\t\t\t}\n\t\t\tif uri.Path != u.Path {\n\t\t\t\tt.Errorf(\"(%v) URLPath not equal: expected %v, got %v -- %v\", test.title, uri.Path, u.Path, getRouteTemplate(route))\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tif test.host != \"\" && test.path != \"\" {\n\t\t\tu, err := route.URL(mapToPairs(match.Vars)...)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"(%v) URL error: %v -- %v\", test.title, err, getRouteTemplate(route))\n\t\t\t}\n\t\t\tif expected, got := uri.String(), u.String(); expected != got {\n\t\t\t\tt.Errorf(\"(%v) URL not equal: expected %v, got %v -- %v\", test.title, expected, got, getRouteTemplate(route))\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tif query != \"\" {\n\t\t\tu, err := route.URL(mapToPairs(match.Vars)...)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"(%v) erred while creating url: %v\", test.title, err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif query != u.RawQuery {\n\t\t\t\tt.Errorf(\"(%v) URL query not equal: expected %v, got %v\", test.title, query, u.RawQuery)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tif shouldRedirect && match.Handler == nil {\n\t\t\tt.Errorf(\"(%v) Did not redirect\", test.title)\n\t\t\treturn\n\t\t}\n\t\tif !shouldRedirect && match.Handler != nil {\n\t\t\tt.Errorf(\"(%v) Unexpected redirect\", test.title)\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc testUseEscapedRoute(t *testing.T, test routeTest) {\n\ttest.route.useEncodedPath = true\n\ttestRoute(t, test)\n}\n\nfunc testTemplate(t *testing.T, test routeTest) {\n\troute := test.route\n\tpathTemplate := test.pathTemplate\n\tif len(pathTemplate) == 0 {\n\t\tpathTemplate = test.path\n\t}\n\thostTemplate := test.hostTemplate\n\tif len(hostTemplate) == 0 {\n\t\thostTemplate = test.host\n\t}\n\n\troutePathTemplate, pathErr := route.GetPathTemplate()\n\tif pathErr == nil && routePathTemplate != pathTemplate {\n\t\tt.Errorf(\"(%v) GetPathTemplate not equal: expected %v, got %v\", test.title, pathTemplate, routePathTemplate)\n\t}\n\n\trouteHostTemplate, hostErr := route.GetHostTemplate()\n\tif hostErr == nil && routeHostTemplate != hostTemplate {\n\t\tt.Errorf(\"(%v) GetHostTemplate not equal: expected %v, got %v\", test.title, hostTemplate, routeHostTemplate)\n\t}\n}\n\nfunc testMethods(t *testing.T, test routeTest) {\n\troute := test.route\n\tmethods, _ := route.GetMethods()\n\tif strings.Join(methods, \",\") != strings.Join(test.methods, \",\") {\n\t\tt.Errorf(\"(%v) GetMethods not equal: expected %v, got %v\", test.title, test.methods, methods)\n\t}\n}\n\nfunc testRegexp(t *testing.T, test routeTest) {\n\troute := test.route\n\troutePathRegexp, regexpErr := route.GetPathRegexp()\n\tif test.pathRegexp != \"\" && regexpErr == nil && routePathRegexp != test.pathRegexp {\n\t\tt.Errorf(\"(%v) GetPathRegexp not equal: expected %v, got %v\", test.title, test.pathRegexp, routePathRegexp)\n\t}\n}\n\nfunc testQueriesRegexp(t *testing.T, test routeTest) {\n\troute := test.route\n\tqueries, queriesErr := route.GetQueriesRegexp()\n\tgotQueries := strings.Join(queries, \",\")\n\tif test.queriesRegexp != \"\" && queriesErr == nil && gotQueries != test.queriesRegexp {\n\t\tt.Errorf(\"(%v) GetQueriesRegexp not equal: expected %v, got %v\", test.title, test.queriesRegexp, gotQueries)\n\t}\n}\n\nfunc testQueriesTemplates(t *testing.T, test routeTest) {\n\troute := test.route\n\tqueries, queriesErr := route.GetQueriesTemplates()\n\tgotQueries := strings.Join(queries, \",\")\n\tif test.queriesTemplate != \"\" && queriesErr == nil && gotQueries != test.queriesTemplate {\n\t\tt.Errorf(\"(%v) GetQueriesTemplates not equal: expected %v, got %v\", test.title, test.queriesTemplate, gotQueries)\n\t}\n}\n\ntype TestA301ResponseWriter struct {\n\thh     http.Header\n\tstatus int\n}\n\nfunc (ho *TestA301ResponseWriter) Header() http.Header {\n\treturn ho.hh\n}\n\nfunc (ho *TestA301ResponseWriter) Write(b []byte) (int, error) {\n\treturn 0, nil\n}\n\nfunc (ho *TestA301ResponseWriter) WriteHeader(code int) {\n\tho.status = code\n}\n\nfunc Test301Redirect(t *testing.T) {\n\tm := make(http.Header)\n\n\tfunc1 := func(w http.ResponseWriter, r *http.Request) {}\n\tfunc2 := func(w http.ResponseWriter, r *http.Request) {}\n\n\tr := NewRouter()\n\tr.HandleFunc(\"/api/\", func2).Name(\"func2\")\n\tr.HandleFunc(\"/\", func1).Name(\"func1\")\n\n\treq, _ := http.NewRequest(\"GET\", \"http://localhost//api/?abc=def\", nil)\n\n\tres := TestA301ResponseWriter{\n\t\thh:     m,\n\t\tstatus: 0,\n\t}\n\tr.ServeHTTP(&res, req)\n\n\tif \"http://localhost/api/?abc=def\" != res.hh[\"Location\"][0] {\n\t\tt.Errorf(\"Should have complete URL with query string\")\n\t}\n}\n\nfunc TestSkipClean(t *testing.T) {\n\tfunc1 := func(w http.ResponseWriter, r *http.Request) {}\n\tfunc2 := func(w http.ResponseWriter, r *http.Request) {}\n\n\tr := NewRouter()\n\tr.SkipClean(true)\n\tr.HandleFunc(\"/api/\", func2).Name(\"func2\")\n\tr.HandleFunc(\"/\", func1).Name(\"func1\")\n\n\treq, _ := http.NewRequest(\"GET\", \"http://localhost//api/?abc=def\", nil)\n\tres := NewRecorder()\n\tr.ServeHTTP(res, req)\n\n\tif len(res.HeaderMap[\"Location\"]) != 0 {\n\t\tt.Errorf(\"Shouldn't redirect since skip clean is disabled\")\n\t}\n}\n\n// https://plus.google.com/101022900381697718949/posts/eWy6DjFJ6uW\nfunc TestSubrouterHeader(t *testing.T) {\n\texpected := \"func1 response\"\n\tfunc1 := func(w http.ResponseWriter, r *http.Request) {\n\t\tfmt.Fprint(w, expected)\n\t}\n\tfunc2 := func(http.ResponseWriter, *http.Request) {}\n\n\tr := NewRouter()\n\ts := r.Headers(\"SomeSpecialHeader\", \"\").Subrouter()\n\ts.HandleFunc(\"/\", func1).Name(\"func1\")\n\tr.HandleFunc(\"/\", func2).Name(\"func2\")\n\n\treq, _ := http.NewRequest(\"GET\", \"http://localhost/\", nil)\n\treq.Header.Add(\"SomeSpecialHeader\", \"foo\")\n\tmatch := new(RouteMatch)\n\tmatched := r.Match(req, match)\n\tif !matched {\n\t\tt.Errorf(\"Should match request\")\n\t}\n\tif match.Route.GetName() != \"func1\" {\n\t\tt.Errorf(\"Expecting func1 handler, got %s\", match.Route.GetName())\n\t}\n\tresp := NewRecorder()\n\tmatch.Handler.ServeHTTP(resp, req)\n\tif resp.Body.String() != expected {\n\t\tt.Errorf(\"Expecting %q\", expected)\n\t}\n}\n\nfunc TestNoMatchMethodErrorHandler(t *testing.T) {\n\tfunc1 := func(w http.ResponseWriter, r *http.Request) {}\n\n\tr := NewRouter()\n\tr.HandleFunc(\"/\", func1).Methods(\"GET\", \"POST\")\n\n\treq, _ := http.NewRequest(\"PUT\", \"http://localhost/\", nil)\n\tmatch := new(RouteMatch)\n\tmatched := r.Match(req, match)\n\n\tif matched {\n\t\tt.Error(\"Should not have matched route for methods\")\n\t}\n\n\tif match.MatchErr != ErrMethodMismatch {\n\t\tt.Error(\"Should get ErrMethodMismatch error\")\n\t}\n\n\tresp := NewRecorder()\n\tr.ServeHTTP(resp, req)\n\tif resp.Code != http.StatusMethodNotAllowed {\n\t\tt.Errorf(\"Expecting code %v\", 405)\n\t}\n\n\t// Add matching route\n\tr.HandleFunc(\"/\", func1).Methods(\"PUT\")\n\n\tmatch = new(RouteMatch)\n\tmatched = r.Match(req, match)\n\n\tif !matched {\n\t\tt.Error(\"Should have matched route for methods\")\n\t}\n\n\tif match.MatchErr != nil {\n\t\tt.Error(\"Should not have any matching error. Found:\", match.MatchErr)\n\t}\n}\n\nfunc TestMultipleDefinitionOfSamePathWithDifferentMethods(t *testing.T) {\n\temptyHandler := func(w http.ResponseWriter, r *http.Request) {}\n\n\tr := NewRouter()\n\tr.HandleFunc(\"/api\", emptyHandler).Methods(\"POST\")\n\tr.HandleFunc(\"/api\", emptyHandler).Queries(\"time\", \"{time:[0-9]+}\").Methods(\"GET\")\n\n\tt.Run(\"Post Method should be matched properly\", func(t *testing.T) {\n\t\treq, _ := http.NewRequest(\"POST\", \"http://localhost/api\", nil)\n\t\tmatch := new(RouteMatch)\n\t\tmatched := r.Match(req, match)\n\t\tif !matched {\n\t\t\tt.Error(\"Should have matched route for methods\")\n\t\t}\n\t\tif match.MatchErr != nil {\n\t\t\tt.Error(\"Should not have any matching error. Found:\", match.MatchErr)\n\t\t}\n\t})\n\n\tt.Run(\"Get Method with invalid query value should not match\", func(t *testing.T) {\n\t\treq, _ := http.NewRequest(\"GET\", \"http://localhost/api?time=-4\", nil)\n\t\tmatch := new(RouteMatch)\n\t\tmatched := r.Match(req, match)\n\t\tif matched {\n\t\t\tt.Error(\"Should not have matched route for methods\")\n\t\t}\n\t\tif match.MatchErr != ErrNotFound {\n\t\t\tt.Error(\"Should have ErrNotFound error. Found:\", match.MatchErr)\n\t\t}\n\t})\n\n\tt.Run(\"A mismach method of a valid path should return ErrMethodMismatch\", func(t *testing.T) {\n\t\tr := NewRouter()\n\t\tr.HandleFunc(\"/api2\", emptyHandler).Methods(\"POST\")\n\t\treq, _ := http.NewRequest(\"GET\", \"http://localhost/api2\", nil)\n\t\tmatch := new(RouteMatch)\n\t\tmatched := r.Match(req, match)\n\t\tif matched {\n\t\t\tt.Error(\"Should not have matched route for methods\")\n\t\t}\n\t\tif match.MatchErr != ErrMethodMismatch {\n\t\t\tt.Error(\"Should have ErrMethodMismatch error. Found:\", match.MatchErr)\n\t\t}\n\t})\n\n}\n\nfunc TestMultipleDefinitionOfSamePathWithDifferentQueries(t *testing.T) {\n\temptyHandler := func(w http.ResponseWriter, r *http.Request) {}\n\n\tr := NewRouter()\n\tr.HandleFunc(\"/api\", emptyHandler).Queries(\"foo\", \"{foo:[0-9]+}\").Methods(http.MethodGet)\n\tr.HandleFunc(\"/api\", emptyHandler).Queries(\"bar\", \"{bar:[0-9]+}\").Methods(http.MethodGet)\n\n\treq := newRequest(http.MethodGet, \"/api?bar=4\")\n\tmatch := new(RouteMatch)\n\tmatched := r.Match(req, match)\n\tif !matched {\n\t\tt.Error(\"Should have matched route for methods\")\n\t}\n\tif match.MatchErr != nil {\n\t\tt.Error(\"Should have no error. Found:\", match.MatchErr)\n\t}\n}\n\nfunc TestErrMatchNotFound(t *testing.T) {\n\temptyHandler := func(w http.ResponseWriter, r *http.Request) {}\n\n\tr := NewRouter()\n\tr.HandleFunc(\"/\", emptyHandler)\n\ts := r.PathPrefix(\"/sub/\").Subrouter()\n\ts.HandleFunc(\"/\", emptyHandler)\n\n\t// Regular 404 not found\n\treq, _ := http.NewRequest(\"GET\", \"/sub/whatever\", nil)\n\tmatch := new(RouteMatch)\n\tmatched := r.Match(req, match)\n\n\tif matched {\n\t\tt.Errorf(\"Subrouter should not have matched that, got %v\", match.Route)\n\t}\n\t// Even without a custom handler, MatchErr is set to ErrNotFound\n\tif match.MatchErr != ErrNotFound {\n\t\tt.Errorf(\"Expected ErrNotFound MatchErr, but was %v\", match.MatchErr)\n\t}\n\n\t// Now lets add a 404 handler to subrouter\n\ts.NotFoundHandler = http.NotFoundHandler()\n\treq, _ = http.NewRequest(\"GET\", \"/sub/whatever\", nil)\n\n\t// Test the subrouter first\n\tmatch = new(RouteMatch)\n\tmatched = s.Match(req, match)\n\t// Now we should get a match\n\tif !matched {\n\t\tt.Errorf(\"Subrouter should have matched %s\", req.RequestURI)\n\t}\n\t// But MatchErr should be set to ErrNotFound anyway\n\tif match.MatchErr != ErrNotFound {\n\t\tt.Errorf(\"Expected ErrNotFound MatchErr, but was %v\", match.MatchErr)\n\t}\n\n\t// Now test the parent (MatchErr should propagate)\n\tmatch = new(RouteMatch)\n\tmatched = r.Match(req, match)\n\n\t// Now we should get a match\n\tif !matched {\n\t\tt.Errorf(\"Router should have matched %s via subrouter\", req.RequestURI)\n\t}\n\t// But MatchErr should be set to ErrNotFound anyway\n\tif match.MatchErr != ErrNotFound {\n\t\tt.Errorf(\"Expected ErrNotFound MatchErr, but was %v\", match.MatchErr)\n\t}\n}\n\n// methodsSubrouterTest models the data necessary for testing handler\n// matching for subrouters created after HTTP methods matcher registration.\ntype methodsSubrouterTest struct {\n\ttitle    string\n\twantCode int\n\trouter   *Router\n\t// method is the input into the request and expected response\n\tmethod string\n\t// input request path\n\tpath string\n\t// redirectTo is the expected location path for strict-slash matches\n\tredirectTo string\n}\n\n// methodHandler writes the method string in response.\nfunc methodHandler(method string) http.HandlerFunc {\n\treturn func(w http.ResponseWriter, r *http.Request) {\n\t\t_, err := w.Write([]byte(method))\n\t\tif err != nil {\n\t\t\tlog.Printf(\"Failed writing HTTP response: %v\", err)\n\t\t}\n\t}\n}\n\n// TestMethodsSubrouterCatchall matches handlers for subrouters where a\n// catchall handler is set for a mis-matching method.\nfunc TestMethodsSubrouterCatchall(t *testing.T) {\n\tt.Parallel()\n\n\trouter := NewRouter()\n\trouter.Methods(\"PATCH\").Subrouter().PathPrefix(\"/\").HandlerFunc(methodHandler(\"PUT\"))\n\trouter.Methods(\"GET\").Subrouter().HandleFunc(\"/foo\", methodHandler(\"GET\"))\n\trouter.Methods(\"POST\").Subrouter().HandleFunc(\"/foo\", methodHandler(\"POST\"))\n\trouter.Methods(\"DELETE\").Subrouter().HandleFunc(\"/foo\", methodHandler(\"DELETE\"))\n\n\ttests := []methodsSubrouterTest{\n\t\t{\n\t\t\ttitle:    \"match GET handler\",\n\t\t\trouter:   router,\n\t\t\tpath:     \"http://localhost/foo\",\n\t\t\tmethod:   \"GET\",\n\t\t\twantCode: http.StatusOK,\n\t\t},\n\t\t{\n\t\t\ttitle:    \"match POST handler\",\n\t\t\trouter:   router,\n\t\t\tmethod:   \"POST\",\n\t\t\tpath:     \"http://localhost/foo\",\n\t\t\twantCode: http.StatusOK,\n\t\t},\n\t\t{\n\t\t\ttitle:    \"match DELETE handler\",\n\t\t\trouter:   router,\n\t\t\tmethod:   \"DELETE\",\n\t\t\tpath:     \"http://localhost/foo\",\n\t\t\twantCode: http.StatusOK,\n\t\t},\n\t\t{\n\t\t\ttitle:    \"disallow PUT method\",\n\t\t\trouter:   router,\n\t\t\tmethod:   \"PUT\",\n\t\t\tpath:     \"http://localhost/foo\",\n\t\t\twantCode: http.StatusMethodNotAllowed,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.title, func(t *testing.T) {\n\t\t\ttestMethodsSubrouter(t, test)\n\t\t})\n\t}\n}\n\n// TestMethodsSubrouterStrictSlash matches handlers on subrouters with\n// strict-slash matchers.\nfunc TestMethodsSubrouterStrictSlash(t *testing.T) {\n\tt.Parallel()\n\n\trouter := NewRouter()\n\tsub := router.PathPrefix(\"/\").Subrouter()\n\tsub.StrictSlash(true).Path(\"/foo\").Methods(\"GET\").Subrouter().HandleFunc(\"\", methodHandler(\"GET\"))\n\tsub.StrictSlash(true).Path(\"/foo/\").Methods(\"PUT\").Subrouter().HandleFunc(\"/\", methodHandler(\"PUT\"))\n\tsub.StrictSlash(true).Path(\"/foo/\").Methods(\"POST\").Subrouter().HandleFunc(\"/\", methodHandler(\"POST\"))\n\n\ttests := []methodsSubrouterTest{\n\t\t{\n\t\t\ttitle:    \"match POST handler\",\n\t\t\trouter:   router,\n\t\t\tmethod:   \"POST\",\n\t\t\tpath:     \"http://localhost/foo/\",\n\t\t\twantCode: http.StatusOK,\n\t\t},\n\t\t{\n\t\t\ttitle:    \"match GET handler\",\n\t\t\trouter:   router,\n\t\t\tmethod:   \"GET\",\n\t\t\tpath:     \"http://localhost/foo\",\n\t\t\twantCode: http.StatusOK,\n\t\t},\n\t\t{\n\t\t\ttitle:      \"match POST handler, redirect strict-slash\",\n\t\t\trouter:     router,\n\t\t\tmethod:     \"POST\",\n\t\t\tpath:       \"http://localhost/foo\",\n\t\t\tredirectTo: \"http://localhost/foo/\",\n\t\t\twantCode:   http.StatusMovedPermanently,\n\t\t},\n\t\t{\n\t\t\ttitle:      \"match GET handler, redirect strict-slash\",\n\t\t\trouter:     router,\n\t\t\tmethod:     \"GET\",\n\t\t\tpath:       \"http://localhost/foo/\",\n\t\t\tredirectTo: \"http://localhost/foo\",\n\t\t\twantCode:   http.StatusMovedPermanently,\n\t\t},\n\t\t{\n\t\t\ttitle:    \"disallow DELETE method\",\n\t\t\trouter:   router,\n\t\t\tmethod:   \"DELETE\",\n\t\t\tpath:     \"http://localhost/foo\",\n\t\t\twantCode: http.StatusMethodNotAllowed,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.title, func(t *testing.T) {\n\t\t\ttestMethodsSubrouter(t, test)\n\t\t})\n\t}\n}\n\n// TestMethodsSubrouterPathPrefix matches handlers on subrouters created\n// on a router with a path prefix matcher and method matcher.\nfunc TestMethodsSubrouterPathPrefix(t *testing.T) {\n\tt.Parallel()\n\n\trouter := NewRouter()\n\trouter.PathPrefix(\"/1\").Methods(\"POST\").Subrouter().HandleFunc(\"/2\", methodHandler(\"POST\"))\n\trouter.PathPrefix(\"/1\").Methods(\"DELETE\").Subrouter().HandleFunc(\"/2\", methodHandler(\"DELETE\"))\n\trouter.PathPrefix(\"/1\").Methods(\"PUT\").Subrouter().HandleFunc(\"/2\", methodHandler(\"PUT\"))\n\trouter.PathPrefix(\"/1\").Methods(\"POST\").Subrouter().HandleFunc(\"/2\", methodHandler(\"POST2\"))\n\n\ttests := []methodsSubrouterTest{\n\t\t{\n\t\t\ttitle:    \"match first POST handler\",\n\t\t\trouter:   router,\n\t\t\tmethod:   \"POST\",\n\t\t\tpath:     \"http://localhost/1/2\",\n\t\t\twantCode: http.StatusOK,\n\t\t},\n\t\t{\n\t\t\ttitle:    \"match DELETE handler\",\n\t\t\trouter:   router,\n\t\t\tmethod:   \"DELETE\",\n\t\t\tpath:     \"http://localhost/1/2\",\n\t\t\twantCode: http.StatusOK,\n\t\t},\n\t\t{\n\t\t\ttitle:    \"match PUT handler\",\n\t\t\trouter:   router,\n\t\t\tmethod:   \"PUT\",\n\t\t\tpath:     \"http://localhost/1/2\",\n\t\t\twantCode: http.StatusOK,\n\t\t},\n\t\t{\n\t\t\ttitle:    \"disallow PATCH method\",\n\t\t\trouter:   router,\n\t\t\tmethod:   \"PATCH\",\n\t\t\tpath:     \"http://localhost/1/2\",\n\t\t\twantCode: http.StatusMethodNotAllowed,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.title, func(t *testing.T) {\n\t\t\ttestMethodsSubrouter(t, test)\n\t\t})\n\t}\n}\n\n// TestMethodsSubrouterSubrouter matches handlers on subrouters produced\n// from method matchers registered on a root subrouter.\nfunc TestMethodsSubrouterSubrouter(t *testing.T) {\n\tt.Parallel()\n\n\trouter := NewRouter()\n\tsub := router.PathPrefix(\"/1\").Subrouter()\n\tsub.Methods(\"POST\").Subrouter().HandleFunc(\"/2\", methodHandler(\"POST\"))\n\tsub.Methods(\"GET\").Subrouter().HandleFunc(\"/2\", methodHandler(\"GET\"))\n\tsub.Methods(\"PATCH\").Subrouter().HandleFunc(\"/2\", methodHandler(\"PATCH\"))\n\tsub.HandleFunc(\"/2\", methodHandler(\"PUT\")).Subrouter().Methods(\"PUT\")\n\tsub.HandleFunc(\"/2\", methodHandler(\"POST2\")).Subrouter().Methods(\"POST\")\n\n\ttests := []methodsSubrouterTest{\n\t\t{\n\t\t\ttitle:    \"match first POST handler\",\n\t\t\trouter:   router,\n\t\t\tmethod:   \"POST\",\n\t\t\tpath:     \"http://localhost/1/2\",\n\t\t\twantCode: http.StatusOK,\n\t\t},\n\t\t{\n\t\t\ttitle:    \"match GET handler\",\n\t\t\trouter:   router,\n\t\t\tmethod:   \"GET\",\n\t\t\tpath:     \"http://localhost/1/2\",\n\t\t\twantCode: http.StatusOK,\n\t\t},\n\t\t{\n\t\t\ttitle:    \"match PATCH handler\",\n\t\t\trouter:   router,\n\t\t\tmethod:   \"PATCH\",\n\t\t\tpath:     \"http://localhost/1/2\",\n\t\t\twantCode: http.StatusOK,\n\t\t},\n\t\t{\n\t\t\ttitle:    \"match PUT handler\",\n\t\t\trouter:   router,\n\t\t\tmethod:   \"PUT\",\n\t\t\tpath:     \"http://localhost/1/2\",\n\t\t\twantCode: http.StatusOK,\n\t\t},\n\t\t{\n\t\t\ttitle:    \"disallow DELETE method\",\n\t\t\trouter:   router,\n\t\t\tmethod:   \"DELETE\",\n\t\t\tpath:     \"http://localhost/1/2\",\n\t\t\twantCode: http.StatusMethodNotAllowed,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.title, func(t *testing.T) {\n\t\t\ttestMethodsSubrouter(t, test)\n\t\t})\n\t}\n}\n\n// TestMethodsSubrouterPathVariable matches handlers on matching paths\n// with path variables in them.\nfunc TestMethodsSubrouterPathVariable(t *testing.T) {\n\tt.Parallel()\n\n\trouter := NewRouter()\n\trouter.Methods(\"GET\").Subrouter().HandleFunc(\"/foo\", methodHandler(\"GET\"))\n\trouter.Methods(\"POST\").Subrouter().HandleFunc(\"/{any}\", methodHandler(\"POST\"))\n\trouter.Methods(\"DELETE\").Subrouter().HandleFunc(\"/1/{any}\", methodHandler(\"DELETE\"))\n\trouter.Methods(\"PUT\").Subrouter().HandleFunc(\"/1/{any}\", methodHandler(\"PUT\"))\n\n\ttests := []methodsSubrouterTest{\n\t\t{\n\t\t\ttitle:    \"match GET handler\",\n\t\t\trouter:   router,\n\t\t\tmethod:   \"GET\",\n\t\t\tpath:     \"http://localhost/foo\",\n\t\t\twantCode: http.StatusOK,\n\t\t},\n\t\t{\n\t\t\ttitle:    \"match POST handler\",\n\t\t\trouter:   router,\n\t\t\tmethod:   \"POST\",\n\t\t\tpath:     \"http://localhost/foo\",\n\t\t\twantCode: http.StatusOK,\n\t\t},\n\t\t{\n\t\t\ttitle:    \"match DELETE handler\",\n\t\t\trouter:   router,\n\t\t\tmethod:   \"DELETE\",\n\t\t\tpath:     \"http://localhost/1/foo\",\n\t\t\twantCode: http.StatusOK,\n\t\t},\n\t\t{\n\t\t\ttitle:    \"match PUT handler\",\n\t\t\trouter:   router,\n\t\t\tmethod:   \"PUT\",\n\t\t\tpath:     \"http://localhost/1/foo\",\n\t\t\twantCode: http.StatusOK,\n\t\t},\n\t\t{\n\t\t\ttitle:    \"disallow PATCH method\",\n\t\t\trouter:   router,\n\t\t\tmethod:   \"PATCH\",\n\t\t\tpath:     \"http://localhost/1/foo\",\n\t\t\twantCode: http.StatusMethodNotAllowed,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.title, func(t *testing.T) {\n\t\t\ttestMethodsSubrouter(t, test)\n\t\t})\n\t}\n}\n\nfunc ExampleSetURLVars() {\n\treq, _ := http.NewRequest(\"GET\", \"/foo\", nil)\n\treq = SetURLVars(req, map[string]string{\"foo\": \"bar\"})\n\n\tfmt.Println(Vars(req)[\"foo\"])\n\n\t// Output: bar\n}\n\n// testMethodsSubrouter runs an individual methodsSubrouterTest.\nfunc testMethodsSubrouter(t *testing.T, test methodsSubrouterTest) {\n\t// Execute request\n\treq, _ := http.NewRequest(test.method, test.path, nil)\n\tresp := NewRecorder()\n\ttest.router.ServeHTTP(resp, req)\n\n\tswitch test.wantCode {\n\tcase http.StatusMethodNotAllowed:\n\t\tif resp.Code != http.StatusMethodNotAllowed {\n\t\t\tt.Errorf(`(%s) Expected \"405 Method Not Allowed\", but got %d code`, test.title, resp.Code)\n\t\t} else if matchedMethod := resp.Body.String(); matchedMethod != \"\" {\n\t\t\tt.Errorf(`(%s) Expected \"405 Method Not Allowed\", but %q handler was called`, test.title, matchedMethod)\n\t\t}\n\n\tcase http.StatusMovedPermanently:\n\t\tif gotLocation := resp.HeaderMap.Get(\"Location\"); gotLocation != test.redirectTo {\n\t\t\tt.Errorf(\"(%s) Expected %q route-match to redirect to %q, but got %q\", test.title, test.method, test.redirectTo, gotLocation)\n\t\t}\n\n\tcase http.StatusOK:\n\t\tif matchedMethod := resp.Body.String(); matchedMethod != test.method {\n\t\t\tt.Errorf(\"(%s) Expected %q handler to be called, but %q handler was called\", test.title, test.method, matchedMethod)\n\t\t}\n\n\tdefault:\n\t\texpectedCodes := []int{http.StatusMethodNotAllowed, http.StatusMovedPermanently, http.StatusOK}\n\t\tt.Errorf(\"(%s) Expected wantCode to be one of: %v, but got %d\", test.title, expectedCodes, test.wantCode)\n\t}\n}\n\nfunc TestSubrouterMatching(t *testing.T) {\n\tconst (\n\t\tnone, stdOnly, subOnly uint8 = 0, 1 << 0, 1 << 1\n\t\tboth                         = subOnly | stdOnly\n\t)\n\n\ttype request struct {\n\t\tName    string\n\t\tRequest *http.Request\n\t\tFlags   uint8\n\t}\n\n\tcases := []struct {\n\t\tName                string\n\t\tStandard, Subrouter func(*Router)\n\t\tRequests            []request\n\t}{\n\t\t{\n\t\t\t\"pathPrefix\",\n\t\t\tfunc(r *Router) {\n\t\t\t\tr.PathPrefix(\"/before\").PathPrefix(\"/after\")\n\t\t\t},\n\t\t\tfunc(r *Router) {\n\t\t\t\tr.PathPrefix(\"/before\").Subrouter().PathPrefix(\"/after\")\n\t\t\t},\n\t\t\t[]request{\n\t\t\t\t{\"no match final path prefix\", newRequest(\"GET\", \"/after\"), none},\n\t\t\t\t{\"no match parent path prefix\", newRequest(\"GET\", \"/before\"), none},\n\t\t\t\t{\"matches append\", newRequest(\"GET\", \"/before/after\"), both},\n\t\t\t\t{\"matches as prefix\", newRequest(\"GET\", \"/before/after/1234\"), both},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"path\",\n\t\t\tfunc(r *Router) {\n\t\t\t\tr.Path(\"/before\").Path(\"/after\")\n\t\t\t},\n\t\t\tfunc(r *Router) {\n\t\t\t\tr.Path(\"/before\").Subrouter().Path(\"/after\")\n\t\t\t},\n\t\t\t[]request{\n\t\t\t\t{\"no match subroute path\", newRequest(\"GET\", \"/after\"), none},\n\t\t\t\t{\"no match parent path\", newRequest(\"GET\", \"/before\"), none},\n\t\t\t\t{\"no match as prefix\", newRequest(\"GET\", \"/before/after/1234\"), none},\n\t\t\t\t{\"no match append\", newRequest(\"GET\", \"/before/after\"), none},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"host\",\n\t\t\tfunc(r *Router) {\n\t\t\t\tr.Host(\"before.com\").Host(\"after.com\")\n\t\t\t},\n\t\t\tfunc(r *Router) {\n\t\t\t\tr.Host(\"before.com\").Subrouter().Host(\"after.com\")\n\t\t\t},\n\t\t\t[]request{\n\t\t\t\t{\"no match before\", newRequestHost(\"GET\", \"/\", \"before.com\"), none},\n\t\t\t\t{\"no match other\", newRequestHost(\"GET\", \"/\", \"other.com\"), none},\n\t\t\t\t{\"matches after\", newRequestHost(\"GET\", \"/\", \"after.com\"), none},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"queries variant keys\",\n\t\t\tfunc(r *Router) {\n\t\t\t\tr.Queries(\"foo\", \"bar\").Queries(\"cricket\", \"baseball\")\n\t\t\t},\n\t\t\tfunc(r *Router) {\n\t\t\t\tr.Queries(\"foo\", \"bar\").Subrouter().Queries(\"cricket\", \"baseball\")\n\t\t\t},\n\t\t\t[]request{\n\t\t\t\t{\"matches with all\", newRequest(\"GET\", \"/?foo=bar&cricket=baseball\"), both},\n\t\t\t\t{\"matches with more\", newRequest(\"GET\", \"/?foo=bar&cricket=baseball&something=else\"), both},\n\t\t\t\t{\"no match with none\", newRequest(\"GET\", \"/\"), none},\n\t\t\t\t{\"no match with some\", newRequest(\"GET\", \"/?cricket=baseball\"), none},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"queries overlapping keys\",\n\t\t\tfunc(r *Router) {\n\t\t\t\tr.Queries(\"foo\", \"bar\").Queries(\"foo\", \"baz\")\n\t\t\t},\n\t\t\tfunc(r *Router) {\n\t\t\t\tr.Queries(\"foo\", \"bar\").Subrouter().Queries(\"foo\", \"baz\")\n\t\t\t},\n\t\t\t[]request{\n\t\t\t\t{\"no match old value\", newRequest(\"GET\", \"/?foo=bar\"), none},\n\t\t\t\t{\"no match diff value\", newRequest(\"GET\", \"/?foo=bak\"), none},\n\t\t\t\t{\"no match with none\", newRequest(\"GET\", \"/\"), none},\n\t\t\t\t{\"matches override\", newRequest(\"GET\", \"/?foo=baz\"), none},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"header variant keys\",\n\t\t\tfunc(r *Router) {\n\t\t\t\tr.Headers(\"foo\", \"bar\").Headers(\"cricket\", \"baseball\")\n\t\t\t},\n\t\t\tfunc(r *Router) {\n\t\t\t\tr.Headers(\"foo\", \"bar\").Subrouter().Headers(\"cricket\", \"baseball\")\n\t\t\t},\n\t\t\t[]request{\n\t\t\t\t{\n\t\t\t\t\t\"matches with all\",\n\t\t\t\t\tnewRequestWithHeaders(\"GET\", \"/\", \"foo\", \"bar\", \"cricket\", \"baseball\"),\n\t\t\t\t\tboth,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"matches with more\",\n\t\t\t\t\tnewRequestWithHeaders(\"GET\", \"/\", \"foo\", \"bar\", \"cricket\", \"baseball\", \"something\", \"else\"),\n\t\t\t\t\tboth,\n\t\t\t\t},\n\t\t\t\t{\"no match with none\", newRequest(\"GET\", \"/\"), none},\n\t\t\t\t{\"no match with some\", newRequestWithHeaders(\"GET\", \"/\", \"cricket\", \"baseball\"), none},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"header overlapping keys\",\n\t\t\tfunc(r *Router) {\n\t\t\t\tr.Headers(\"foo\", \"bar\").Headers(\"foo\", \"baz\")\n\t\t\t},\n\t\t\tfunc(r *Router) {\n\t\t\t\tr.Headers(\"foo\", \"bar\").Subrouter().Headers(\"foo\", \"baz\")\n\t\t\t},\n\t\t\t[]request{\n\t\t\t\t{\"no match old value\", newRequestWithHeaders(\"GET\", \"/\", \"foo\", \"bar\"), none},\n\t\t\t\t{\"no match diff value\", newRequestWithHeaders(\"GET\", \"/\", \"foo\", \"bak\"), none},\n\t\t\t\t{\"no match with none\", newRequest(\"GET\", \"/\"), none},\n\t\t\t\t{\"matches override\", newRequestWithHeaders(\"GET\", \"/\", \"foo\", \"baz\"), none},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"method\",\n\t\t\tfunc(r *Router) {\n\t\t\t\tr.Methods(\"POST\").Methods(\"GET\")\n\t\t\t},\n\t\t\tfunc(r *Router) {\n\t\t\t\tr.Methods(\"POST\").Subrouter().Methods(\"GET\")\n\t\t\t},\n\t\t\t[]request{\n\t\t\t\t{\"matches before\", newRequest(\"POST\", \"/\"), none},\n\t\t\t\t{\"no match other\", newRequest(\"HEAD\", \"/\"), none},\n\t\t\t\t{\"matches override\", newRequest(\"GET\", \"/\"), none},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"schemes\",\n\t\t\tfunc(r *Router) {\n\t\t\t\tr.Schemes(\"http\").Schemes(\"https\")\n\t\t\t},\n\t\t\tfunc(r *Router) {\n\t\t\t\tr.Schemes(\"http\").Subrouter().Schemes(\"https\")\n\t\t\t},\n\t\t\t[]request{\n\t\t\t\t{\"matches overrides\", newRequest(\"GET\", \"https://www.example.com/\"), none},\n\t\t\t\t{\"matches original\", newRequest(\"GET\", \"http://www.example.com/\"), none},\n\t\t\t\t{\"no match other\", newRequest(\"GET\", \"ftp://www.example.com/\"), none},\n\t\t\t},\n\t\t},\n\t}\n\n\t// case -> request -> router\n\tfor _, c := range cases {\n\t\tt.Run(c.Name, func(t *testing.T) {\n\t\t\tfor _, req := range c.Requests {\n\t\t\t\tt.Run(req.Name, func(t *testing.T) {\n\t\t\t\t\tfor _, v := range []struct {\n\t\t\t\t\t\tName     string\n\t\t\t\t\t\tConfig   func(*Router)\n\t\t\t\t\t\tExpected bool\n\t\t\t\t\t}{\n\t\t\t\t\t\t{\"subrouter\", c.Subrouter, (req.Flags & subOnly) != 0},\n\t\t\t\t\t\t{\"standard\", c.Standard, (req.Flags & stdOnly) != 0},\n\t\t\t\t\t} {\n\t\t\t\t\t\tr := NewRouter()\n\t\t\t\t\t\tv.Config(r)\n\t\t\t\t\t\tif r.Match(req.Request, &RouteMatch{}) != v.Expected {\n\t\t\t\t\t\t\tif v.Expected {\n\t\t\t\t\t\t\t\tt.Errorf(\"expected %v match\", v.Name)\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tt.Errorf(\"expected %v no match\", v.Name)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t}\n\t\t})\n\t}\n}\n\n// verify that copyRouteConf copies fields as expected.\nfunc Test_copyRouteConf(t *testing.T) {\n\tvar (\n\t\tm MatcherFunc = func(*http.Request, *RouteMatch) bool {\n\t\t\treturn true\n\t\t}\n\t\tb BuildVarsFunc = func(i map[string]string) map[string]string {\n\t\t\treturn i\n\t\t}\n\t\tr, _ = newRouteRegexp(\"hi\", regexpTypeHost, routeRegexpOptions{})\n\t)\n\n\ttests := []struct {\n\t\tname string\n\t\targs routeConf\n\t\twant routeConf\n\t}{\n\t\t{\n\t\t\t\"empty\",\n\t\t\trouteConf{},\n\t\t\trouteConf{},\n\t\t},\n\t\t{\n\t\t\t\"full\",\n\t\t\trouteConf{\n\t\t\t\tuseEncodedPath: true,\n\t\t\t\tstrictSlash:    true,\n\t\t\t\tskipClean:      true,\n\t\t\t\tregexp:         routeRegexpGroup{host: r, path: r, queries: []*routeRegexp{r}},\n\t\t\t\tmatchers:       []matcher{m},\n\t\t\t\tbuildScheme:    \"https\",\n\t\t\t\tbuildVarsFunc:  b,\n\t\t\t},\n\t\t\trouteConf{\n\t\t\t\tuseEncodedPath: true,\n\t\t\t\tstrictSlash:    true,\n\t\t\t\tskipClean:      true,\n\t\t\t\tregexp:         routeRegexpGroup{host: r, path: r, queries: []*routeRegexp{r}},\n\t\t\t\tmatchers:       []matcher{m},\n\t\t\t\tbuildScheme:    \"https\",\n\t\t\t\tbuildVarsFunc:  b,\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\t// special case some incomparable fields of routeConf before delegating to reflect.DeepEqual\n\t\t\tgot := copyRouteConf(tt.args)\n\n\t\t\t// funcs not comparable, just compare length of slices\n\t\t\tif len(got.matchers) != len(tt.want.matchers) {\n\t\t\t\tt.Errorf(\"matchers different lengths: %v %v\", len(got.matchers), len(tt.want.matchers))\n\t\t\t}\n\t\t\tgot.matchers, tt.want.matchers = nil, nil\n\n\t\t\t// deep equal treats nil slice differently to empty slice so check for zero len first\n\t\t\t{\n\t\t\t\tbothZero := len(got.regexp.queries) == 0 && len(tt.want.regexp.queries) == 0\n\t\t\t\tif !bothZero && !reflect.DeepEqual(got.regexp.queries, tt.want.regexp.queries) {\n\t\t\t\t\tt.Errorf(\"queries unequal: %v %v\", got.regexp.queries, tt.want.regexp.queries)\n\t\t\t\t}\n\t\t\t\tgot.regexp.queries, tt.want.regexp.queries = nil, nil\n\t\t\t}\n\n\t\t\t// funcs not comparable, just compare nullity\n\t\t\tif (got.buildVarsFunc == nil) != (tt.want.buildVarsFunc == nil) {\n\t\t\t\tt.Errorf(\"build vars funcs unequal: %v %v\", got.buildVarsFunc == nil, tt.want.buildVarsFunc == nil)\n\t\t\t}\n\t\t\tgot.buildVarsFunc, tt.want.buildVarsFunc = nil, nil\n\n\t\t\t// finish the deal\n\t\t\tif !reflect.DeepEqual(got, tt.want) {\n\t\t\t\tt.Errorf(\"route confs unequal: %v %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestMethodNotAllowed(t *testing.T) {\n\thandler := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }\n\trouter := NewRouter()\n\trouter.HandleFunc(\"/thing\", handler).Methods(http.MethodGet)\n\trouter.HandleFunc(\"/something\", handler).Methods(http.MethodGet)\n\n\tw := NewRecorder()\n\treq := newRequest(http.MethodPut, \"/thing\")\n\n\trouter.ServeHTTP(w, req)\n\n\tif w.Code != http.StatusMethodNotAllowed {\n\t\tt.Fatalf(\"Expected status code 405 (got %d)\", w.Code)\n\t}\n}\n\nfunc TestMethodNotAllowedSubrouterWithSeveralRoutes(t *testing.T) {\n\thandler := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }\n\n\trouter := NewRouter()\n\tsubrouter := router.PathPrefix(\"/v1\").Subrouter()\n\tsubrouter.HandleFunc(\"/api\", handler).Methods(http.MethodGet)\n\tsubrouter.HandleFunc(\"/api/{id}\", handler).Methods(http.MethodGet)\n\n\tw := NewRecorder()\n\treq := newRequest(http.MethodPut, \"/v1/api\")\n\trouter.ServeHTTP(w, req)\n\n\tif w.Code != http.StatusMethodNotAllowed {\n\t\tt.Errorf(\"Expected status code 405 (got %d)\", w.Code)\n\t}\n}\n\ntype customMethodNotAllowedHandler struct {\n\tmsg string\n}\n\nfunc (h customMethodNotAllowedHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n\tw.WriteHeader(http.StatusMethodNotAllowed)\n\tfmt.Fprint(w, h.msg)\n}\n\nfunc TestSubrouterCustomMethodNotAllowed(t *testing.T) {\n\thandler := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }\n\n\trouter := NewRouter()\n\trouter.HandleFunc(\"/test\", handler).Methods(http.MethodGet)\n\trouter.MethodNotAllowedHandler = customMethodNotAllowedHandler{msg: \"custom router handler\"}\n\n\tsubrouter := router.PathPrefix(\"/sub\").Subrouter()\n\tsubrouter.HandleFunc(\"/test\", handler).Methods(http.MethodGet)\n\tsubrouter.MethodNotAllowedHandler = customMethodNotAllowedHandler{msg: \"custom sub router handler\"}\n\n\ttestCases := map[string]struct {\n\t\tpath   string\n\t\texpMsg string\n\t}{\n\t\t\"router method not allowed\": {\n\t\t\tpath:   \"/test\",\n\t\t\texpMsg: \"custom router handler\",\n\t\t},\n\t\t\"subrouter method not allowed\": {\n\t\t\tpath:   \"/sub/test\",\n\t\t\texpMsg: \"custom sub router handler\",\n\t\t},\n\t}\n\n\tfor name, tc := range testCases {\n\t\tt.Run(name, func(tt *testing.T) {\n\t\t\tw := NewRecorder()\n\t\t\treq := newRequest(http.MethodPut, tc.path)\n\n\t\t\trouter.ServeHTTP(w, req)\n\n\t\t\tif w.Code != http.StatusMethodNotAllowed {\n\t\t\t\ttt.Errorf(\"Expected status code 405 (got %d)\", w.Code)\n\t\t\t}\n\n\t\t\tb, err := io.ReadAll(w.Body)\n\t\t\tif err != nil {\n\t\t\t\ttt.Errorf(\"failed to read body: %v\", err)\n\t\t\t}\n\n\t\t\tif string(b) != tc.expMsg {\n\t\t\t\ttt.Errorf(\"expected msg %q, got %q\", tc.expMsg, string(b))\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSubrouterNotFound(t *testing.T) {\n\thandler := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }\n\trouter := NewRouter()\n\trouter.Path(\"/a\").Subrouter().HandleFunc(\"/thing\", handler).Methods(http.MethodGet)\n\trouter.Path(\"/b\").Subrouter().HandleFunc(\"/something\", handler).Methods(http.MethodGet)\n\n\tw := NewRecorder()\n\treq := newRequest(http.MethodPut, \"/not-present\")\n\n\trouter.ServeHTTP(w, req)\n\n\tif w.Code != http.StatusNotFound {\n\t\tt.Fatalf(\"Expected status code 404 (got %d)\", w.Code)\n\t}\n}\n\nfunc TestContextMiddleware(t *testing.T) {\n\twithTimeout := func(h http.Handler) http.Handler {\n\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\tctx, cancel := context.WithTimeout(r.Context(), time.Minute)\n\t\t\tdefer cancel()\n\t\t\th.ServeHTTP(w, r.WithContext(ctx))\n\t\t})\n\t}\n\n\tr := NewRouter()\n\tr.Handle(\"/path/{foo}\", withTimeout(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tvars := Vars(r)\n\t\tif vars[\"foo\"] != \"bar\" {\n\t\t\tt.Fatal(\"Expected foo var to be set\")\n\t\t}\n\t})))\n\n\trec := NewRecorder()\n\treq := newRequest(\"GET\", \"/path/bar\")\n\tr.ServeHTTP(rec, req)\n}\n\nfunc TestGetVarNames(t *testing.T) {\n\tr := NewRouter()\n\n\troute := r.Host(\"{domain}\").\n\t\tPath(\"/{group}/{item_id}\").\n\t\tQueries(\"some_data1\", \"{some_data1}\").\n\t\tQueries(\"some_data2_and_3\", \"{some_data2}.{some_data3}\")\n\n\t// Order of vars in the slice is not guaranteed, so just check for existence\n\texpected := map[string]bool{\n\t\t\"domain\":     true,\n\t\t\"group\":      true,\n\t\t\"item_id\":    true,\n\t\t\"some_data1\": true,\n\t\t\"some_data2\": true,\n\t\t\"some_data3\": true,\n\t}\n\n\tvarNames, err := route.GetVarNames()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif len(varNames) != len(expected) {\n\t\tt.Fatalf(\"expected %d names, got %d\", len(expected), len(varNames))\n\t}\n\n\tfor _, varName := range varNames {\n\t\tif !expected[varName] {\n\t\t\tt.Fatalf(\"got unexpected %s\", varName)\n\t\t}\n\t}\n}\n\nfunc getPopulateContextTestCases() []struct {\n\tname                 string\n\tpath                 string\n\tomitRouteFromContext bool\n\twantVar              string\n\twantStaticRoute      bool\n\twantDynamicRoute     bool\n} {\n\treturn []struct {\n\t\tname                 string\n\t\tpath                 string\n\t\tomitRouteFromContext bool\n\t\twantVar              string\n\t\twantStaticRoute      bool\n\t\twantDynamicRoute     bool\n\t}{\n\t\t{\n\t\t\tname:            \"no populated vars\",\n\t\t\tpath:            \"/static\",\n\t\t\twantVar:         \"\",\n\t\t\twantStaticRoute: true,\n\t\t},\n\t\t{\n\t\t\tname:             \"empty var\",\n\t\t\tpath:             \"/dynamic/\",\n\t\t\twantVar:          \"\",\n\t\t\twantDynamicRoute: true,\n\t\t},\n\t\t{\n\t\t\tname:             \"populated vars\",\n\t\t\tpath:             \"/dynamic/foo\",\n\t\t\twantVar:          \"foo\",\n\t\t\twantDynamicRoute: true,\n\t\t},\n\t\t{\n\t\t\tname:                 \"omit route /static\",\n\t\t\tpath:                 \"/static\",\n\t\t\tomitRouteFromContext: true,\n\t\t\twantVar:              \"\",\n\t\t\twantStaticRoute:      false,\n\t\t},\n\t\t{\n\t\t\tname:                 \"omit route /dynamic\",\n\t\t\tpath:                 \"/dynamic/\",\n\t\t\tomitRouteFromContext: true,\n\t\t\twantVar:              \"\",\n\t\t\twantDynamicRoute:     false,\n\t\t},\n\t}\n}\n\nfunc TestPopulateContext(t *testing.T) {\n\ttestCases := getPopulateContextTestCases()\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tmatched := false\n\t\t\tr := NewRouter()\n\t\t\tr.OmitRouteFromContext(tc.omitRouteFromContext)\n\t\t\tvar static *Route\n\t\t\tvar dynamic *Route\n\t\t\tfn := func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tmatched = true\n\t\t\t\tif got := Vars(r)[\"x\"]; got != tc.wantVar {\n\t\t\t\t\tt.Fatalf(\"wantVar=%q, got=%q\", tc.wantVar, got)\n\t\t\t\t}\n\t\t\t\tswitch {\n\t\t\t\tcase tc.wantDynamicRoute:\n\t\t\t\t\tr2 := CurrentRoute(r)\n\t\t\t\t\tif r2 != dynamic || r2.GetName() != \"dynamic\" {\n\t\t\t\t\t\tt.Fatalf(\"expected dynmic route in ctx, got %v\", r2)\n\t\t\t\t\t}\n\t\t\t\tcase tc.wantStaticRoute:\n\t\t\t\t\tr2 := CurrentRoute(r)\n\t\t\t\t\tif r2 != static || r2.GetName() != \"static\" {\n\t\t\t\t\t\tt.Fatalf(\"expected static route in ctx, got %v\", r2)\n\t\t\t\t\t}\n\t\t\t\tdefault:\n\t\t\t\t\tif r2 := CurrentRoute(r); r2 != nil {\n\t\t\t\t\t\tt.Fatalf(\"expected no route in ctx, got %v\", r2)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tw.WriteHeader(http.StatusNoContent)\n\t\t\t}\n\t\t\tstatic = r.Name(\"static\").Path(\"/static\").HandlerFunc(fn)\n\t\t\tdynamic = r.Name(\"dynamic\").Path(\"/dynamic/{x:.*}\").HandlerFunc(fn)\n\t\t\treq := newRequest(http.MethodGet, \"http://localhost\"+tc.path)\n\t\t\trec := NewRecorder()\n\t\t\tr.ServeHTTP(rec, req)\n\t\t\tif !matched {\n\t\t\t\tt.Fatal(\"Expected route to match\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkPopulateContext(b *testing.B) {\n\ttestCases := getPopulateContextTestCases()\n\tfor _, tc := range testCases {\n\t\tb.Run(tc.name, func(b *testing.B) {\n\t\t\tmatched := false\n\t\t\tr := NewRouter()\n\t\t\tr.OmitRouteFromContext(tc.omitRouteFromContext)\n\t\t\tfn := func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tmatched = true\n\t\t\t\tw.WriteHeader(http.StatusNoContent)\n\t\t\t}\n\t\t\tr.Name(\"static\").Path(\"/static\").HandlerFunc(fn)\n\t\t\tr.Name(\"dynamic\").Path(\"/dynamic/{x:.*}\").HandlerFunc(fn)\n\t\t\treq := newRequest(http.MethodGet, \"http://localhost\"+tc.path)\n\t\t\trec := NewRecorder()\n\t\t\tb.ReportAllocs()\n\t\t\tb.ResetTimer()\n\t\t\tfor i := 0; i < b.N; i++ {\n\t\t\t\tr.ServeHTTP(rec, req)\n\t\t\t}\n\t\t\tif !matched {\n\t\t\t\tb.Fatal(\"Expected route to match\")\n\t\t\t}\n\t\t})\n\t}\n}\n\n// mapToPairs converts a string map to a slice of string pairs\nfunc mapToPairs(m map[string]string) []string {\n\tvar i int\n\tp := make([]string, len(m)*2)\n\tfor k, v := range m {\n\t\tp[i] = k\n\t\tp[i+1] = v\n\t\ti += 2\n\t}\n\treturn p\n}\n\n// stringMapEqual checks the equality of two string maps\nfunc stringMapEqual(m1, m2 map[string]string) bool {\n\tnil1 := m1 == nil\n\tnil2 := m2 == nil\n\tif nil1 != nil2 || len(m1) != len(m2) {\n\t\treturn false\n\t}\n\tfor k, v := range m1 {\n\t\tif v != m2[k] {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\n// stringHandler returns a handler func that writes a message 's' to the\n// http.ResponseWriter.\nfunc stringHandler(s string) http.HandlerFunc {\n\treturn func(w http.ResponseWriter, r *http.Request) {\n\t\t_, err := w.Write([]byte(s))\n\t\tif err != nil {\n\t\t\tlog.Printf(\"Failed writing HTTP response: %v\", err)\n\t\t}\n\t}\n}\n\n// newRequest is a helper function to create a new request with a method and url.\n// The request returned is a 'server' request as opposed to a 'client' one through\n// simulated write onto the wire and read off of the wire.\n// The differences between requests are detailed in the net/http package.\nfunc newRequest(method, url string) *http.Request {\n\treq, err := http.NewRequest(method, url, nil)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\t// extract the escaped original host+path from url\n\t// http://localhost/path/here?v=1#frag -> //localhost/path/here\n\topaque := \"\"\n\tif i := len(req.URL.Scheme); i > 0 {\n\t\topaque = url[i+1:]\n\t}\n\n\tif i := strings.LastIndex(opaque, \"?\"); i > -1 {\n\t\topaque = opaque[:i]\n\t}\n\tif i := strings.LastIndex(opaque, \"#\"); i > -1 {\n\t\topaque = opaque[:i]\n\t}\n\n\t// Escaped host+path workaround as detailed in https://golang.org/pkg/net/url/#URL\n\t// for < 1.5 client side workaround\n\treq.URL.Opaque = opaque\n\n\t// Simulate writing to wire\n\tvar buff bytes.Buffer\n\terr = req.Write(&buff)\n\tif err != nil {\n\t\tlog.Printf(\"Failed writing HTTP request: %v\", err)\n\t}\n\tioreader := bufio.NewReader(&buff)\n\n\t// Parse request off of 'wire'\n\treq, err = http.ReadRequest(ioreader)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn req\n}\n\n// create a new request with the provided headers\nfunc newRequestWithHeaders(method, url string, headers ...string) *http.Request {\n\treq := newRequest(method, url)\n\n\tif len(headers)%2 != 0 {\n\t\tpanic(fmt.Sprintf(\"Expected headers length divisible by 2 but got %v\", len(headers)))\n\t}\n\n\tfor i := 0; i < len(headers); i += 2 {\n\t\treq.Header.Set(headers[i], headers[i+1])\n\t}\n\n\treturn req\n}\n\n// newRequestHost a new request with a method, url, and host header\nfunc newRequestHost(method, url, host string) *http.Request {\n\treq := httptest.NewRequest(method, url, nil)\n\treq.Host = host\n\treturn req\n}\n"
  },
  {
    "path": "old_test.go",
    "content": "// Old tests ported to Go1. This is a mess. Want to drop it one day.\n\n// Copyright 2011 Gorilla Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\npackage mux\n\nimport (\n\t\"bytes\"\n\t\"net/http\"\n\t\"testing\"\n)\n\n// ----------------------------------------------------------------------------\n// ResponseRecorder\n// ----------------------------------------------------------------------------\n// Copyright 2009 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\n// ResponseRecorder is an implementation of http.ResponseWriter that\n// records its mutations for later inspection in tests.\ntype ResponseRecorder struct {\n\tCode      int           // the HTTP response code from WriteHeader\n\tHeaderMap http.Header   // the HTTP response headers\n\tBody      *bytes.Buffer // if non-nil, the bytes.Buffer to append written data to\n\tFlushed   bool\n}\n\n// NewRecorder returns an initialized ResponseRecorder.\nfunc NewRecorder() *ResponseRecorder {\n\treturn &ResponseRecorder{\n\t\tHeaderMap: make(http.Header),\n\t\tBody:      new(bytes.Buffer),\n\t}\n}\n\n// Header returns the response headers.\nfunc (rw *ResponseRecorder) Header() http.Header {\n\treturn rw.HeaderMap\n}\n\n// Write always succeeds and writes to rw.Body, if not nil.\nfunc (rw *ResponseRecorder) Write(buf []byte) (int, error) {\n\tif rw.Body != nil {\n\t\trw.Body.Write(buf)\n\t}\n\tif rw.Code == 0 {\n\t\trw.Code = http.StatusOK\n\t}\n\treturn len(buf), nil\n}\n\n// WriteHeader sets rw.Code.\nfunc (rw *ResponseRecorder) WriteHeader(code int) {\n\trw.Code = code\n}\n\n// Flush sets rw.Flushed to true.\nfunc (rw *ResponseRecorder) Flush() {\n\trw.Flushed = true\n}\n\n// ----------------------------------------------------------------------------\n\nfunc TestRouteMatchers(t *testing.T) {\n\tvar scheme, host, path, query, method string\n\tvar headers map[string]string\n\tvar resultVars map[bool]map[string]string\n\n\trouter := NewRouter()\n\trouter.NewRoute().Host(\"{var1}.google.com\").\n\t\tPath(\"/{var2:[a-z]+}/{var3:[0-9]+}\").\n\t\tQueries(\"foo\", \"bar\").\n\t\tMethods(\"GET\").\n\t\tSchemes(\"https\").\n\t\tHeaders(\"x-requested-with\", \"XMLHttpRequest\")\n\trouter.NewRoute().Host(\"www.{var4}.com\").\n\t\tPathPrefix(\"/foo/{var5:[a-z]+}/{var6:[0-9]+}\").\n\t\tQueries(\"baz\", \"ding\").\n\t\tMethods(\"POST\").\n\t\tSchemes(\"http\").\n\t\tHeaders(\"Content-Type\", \"application/json\")\n\n\treset := func() {\n\t\t// Everything match.\n\t\tscheme = \"https\"\n\t\thost = \"www.google.com\"\n\t\tpath = \"/product/42\"\n\t\tquery = \"?foo=bar\"\n\t\tmethod = \"GET\"\n\t\theaders = map[string]string{\"X-Requested-With\": \"XMLHttpRequest\"}\n\t\tresultVars = map[bool]map[string]string{\n\t\t\ttrue:  {\"var1\": \"www\", \"var2\": \"product\", \"var3\": \"42\"},\n\t\t\tfalse: {},\n\t\t}\n\t}\n\n\treset2 := func() {\n\t\t// Everything match.\n\t\tscheme = \"http\"\n\t\thost = \"www.google.com\"\n\t\tpath = \"/foo/product/42/path/that/is/ignored\"\n\t\tquery = \"?baz=ding\"\n\t\tmethod = \"POST\"\n\t\theaders = map[string]string{\"Content-Type\": \"application/json\"}\n\t\tresultVars = map[bool]map[string]string{\n\t\t\ttrue:  {\"var4\": \"google\", \"var5\": \"product\", \"var6\": \"42\"},\n\t\t\tfalse: {},\n\t\t}\n\t}\n\n\tmatch := func(shouldMatch bool) {\n\t\turl := scheme + \"://\" + host + path + query\n\t\trequest, _ := http.NewRequest(method, url, nil)\n\t\tfor key, value := range headers {\n\t\t\trequest.Header.Add(key, value)\n\t\t}\n\n\t\tvar routeMatch RouteMatch\n\t\tmatched := router.Match(request, &routeMatch)\n\t\tif matched != shouldMatch {\n\t\t\tt.Errorf(\"Expected: %v\\nGot: %v\\nRequest: %v %v\", shouldMatch, matched, request.Method, url)\n\t\t}\n\n\t\tif matched {\n\t\t\tcurrentRoute := routeMatch.Route\n\t\t\tif currentRoute == nil {\n\t\t\t\tt.Errorf(\"Expected a current route.\")\n\t\t\t}\n\t\t\tvars := routeMatch.Vars\n\t\t\texpectedVars := resultVars[shouldMatch]\n\t\t\tif len(vars) != len(expectedVars) {\n\t\t\t\tt.Errorf(\"Expected vars: %v Got: %v.\", expectedVars, vars)\n\t\t\t}\n\t\t\tfor name, value := range vars {\n\t\t\t\tif expectedVars[name] != value {\n\t\t\t\t\tt.Errorf(\"Expected vars: %v Got: %v.\", expectedVars, vars)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// 1st route --------------------------------------------------------------\n\n\t// Everything match.\n\treset()\n\tmatch(true)\n\n\t// Scheme doesn't match.\n\treset()\n\tscheme = \"http\"\n\tmatch(false)\n\n\t// Host doesn't match.\n\treset()\n\thost = \"www.mygoogle.com\"\n\tmatch(false)\n\n\t// Path doesn't match.\n\treset()\n\tpath = \"/product/notdigits\"\n\tmatch(false)\n\n\t// Query doesn't match.\n\treset()\n\tquery = \"?foo=baz\"\n\tmatch(false)\n\n\t// Method doesn't match.\n\treset()\n\tmethod = \"POST\"\n\tmatch(false)\n\n\t// Header doesn't match.\n\treset()\n\theaders = map[string]string{}\n\tmatch(false)\n\n\t// Everything match, again.\n\treset()\n\tmatch(true)\n\n\t// 2nd route --------------------------------------------------------------\n\t// Everything match.\n\treset2()\n\tmatch(true)\n\n\t// Scheme doesn't match.\n\treset2()\n\tscheme = \"https\"\n\tmatch(false)\n\n\t// Host doesn't match.\n\treset2()\n\thost = \"sub.google.com\"\n\tmatch(false)\n\n\t// Path doesn't match.\n\treset2()\n\tpath = \"/bar/product/42\"\n\tmatch(false)\n\n\t// Query doesn't match.\n\treset2()\n\tquery = \"?foo=baz\"\n\tmatch(false)\n\n\t// Method doesn't match.\n\treset2()\n\tmethod = \"GET\"\n\tmatch(false)\n\n\t// Header doesn't match.\n\treset2()\n\theaders = map[string]string{}\n\tmatch(false)\n\n\t// Everything match, again.\n\treset2()\n\tmatch(true)\n}\n\ntype headerMatcherTest struct {\n\tmatcher headerMatcher\n\theaders map[string]string\n\tresult  bool\n}\n\nvar headerMatcherTests = []headerMatcherTest{\n\t{\n\t\tmatcher: headerMatcher(map[string]string{\"x-requested-with\": \"XMLHttpRequest\"}),\n\t\theaders: map[string]string{\"X-Requested-With\": \"XMLHttpRequest\"},\n\t\tresult:  true,\n\t},\n\t{\n\t\tmatcher: headerMatcher(map[string]string{\"x-requested-with\": \"\"}),\n\t\theaders: map[string]string{\"X-Requested-With\": \"anything\"},\n\t\tresult:  true,\n\t},\n\t{\n\t\tmatcher: headerMatcher(map[string]string{\"x-requested-with\": \"XMLHttpRequest\"}),\n\t\theaders: map[string]string{},\n\t\tresult:  false,\n\t},\n}\n\ntype hostMatcherTest struct {\n\tmatcher *Route\n\turl     string\n\tvars    map[string]string\n\tresult  bool\n}\n\nvar hostMatcherTests = []hostMatcherTest{\n\t{\n\t\tmatcher: NewRouter().NewRoute().Host(\"{foo:[a-z][a-z][a-z]}.{bar:[a-z][a-z][a-z]}.{baz:[a-z][a-z][a-z]}\"),\n\t\turl:     \"http://abc.def.ghi/\",\n\t\tvars:    map[string]string{\"foo\": \"abc\", \"bar\": \"def\", \"baz\": \"ghi\"},\n\t\tresult:  true,\n\t},\n\t{\n\t\tmatcher: NewRouter().NewRoute().Host(\"{foo:[a-z][a-z][a-z]}.{bar:[a-z][a-z][a-z]}.{baz:[a-z][a-z][a-z]}:{port:.*}\"),\n\t\turl:     \"http://abc.def.ghi:65535/\",\n\t\tvars:    map[string]string{\"foo\": \"abc\", \"bar\": \"def\", \"baz\": \"ghi\", \"port\": \"65535\"},\n\t\tresult:  true,\n\t},\n\t{\n\t\tmatcher: NewRouter().NewRoute().Host(\"{foo:[a-z][a-z][a-z]}.{bar:[a-z][a-z][a-z]}.{baz:[a-z][a-z][a-z]}\"),\n\t\turl:     \"http://abc.def.ghi:65535/\",\n\t\tvars:    map[string]string{\"foo\": \"abc\", \"bar\": \"def\", \"baz\": \"ghi\"},\n\t\tresult:  true,\n\t},\n\t{\n\t\tmatcher: NewRouter().NewRoute().Host(\"{foo:[a-z][a-z][a-z]}.{bar:[a-z][a-z][a-z]}.{baz:[a-z][a-z][a-z]}\"),\n\t\turl:     \"http://a.b.c/\",\n\t\tvars:    map[string]string{\"foo\": \"abc\", \"bar\": \"def\", \"baz\": \"ghi\"},\n\t\tresult:  false,\n\t},\n}\n\ntype methodMatcherTest struct {\n\tmatcher methodMatcher\n\tmethod  string\n\tresult  bool\n}\n\nvar methodMatcherTests = []methodMatcherTest{\n\t{\n\t\tmatcher: methodMatcher([]string{\"GET\", \"POST\", \"PUT\"}),\n\t\tmethod:  \"GET\",\n\t\tresult:  true,\n\t},\n\t{\n\t\tmatcher: methodMatcher([]string{\"GET\", \"POST\", \"PUT\"}),\n\t\tmethod:  \"POST\",\n\t\tresult:  true,\n\t},\n\t{\n\t\tmatcher: methodMatcher([]string{\"GET\", \"POST\", \"PUT\"}),\n\t\tmethod:  \"PUT\",\n\t\tresult:  true,\n\t},\n\t{\n\t\tmatcher: methodMatcher([]string{\"GET\", \"POST\", \"PUT\"}),\n\t\tmethod:  \"DELETE\",\n\t\tresult:  false,\n\t},\n}\n\ntype pathMatcherTest struct {\n\tmatcher *Route\n\turl     string\n\tvars    map[string]string\n\tresult  bool\n}\n\nvar pathMatcherTests = []pathMatcherTest{\n\t{\n\t\tmatcher: NewRouter().NewRoute().Path(\"/{foo:[0-9][0-9][0-9]}/{bar:[0-9][0-9][0-9]}/{baz:[0-9][0-9][0-9]}\"),\n\t\turl:     \"http://localhost:8080/123/456/789\",\n\t\tvars:    map[string]string{\"foo\": \"123\", \"bar\": \"456\", \"baz\": \"789\"},\n\t\tresult:  true,\n\t},\n\t{\n\t\tmatcher: NewRouter().NewRoute().Path(\"/{foo:[0-9][0-9][0-9]}/{bar:[0-9][0-9][0-9]}/{baz:[0-9][0-9][0-9]}\"),\n\t\turl:     \"http://localhost:8080/1/2/3\",\n\t\tvars:    map[string]string{\"foo\": \"123\", \"bar\": \"456\", \"baz\": \"789\"},\n\t\tresult:  false,\n\t},\n}\n\ntype schemeMatcherTest struct {\n\tmatcher schemeMatcher\n\turl     string\n\tresult  bool\n}\n\nvar schemeMatcherTests = []schemeMatcherTest{\n\t{\n\t\tmatcher: schemeMatcher([]string{\"http\", \"https\"}),\n\t\turl:     \"http://localhost:8080/\",\n\t\tresult:  true,\n\t},\n\t{\n\t\tmatcher: schemeMatcher([]string{\"http\", \"https\"}),\n\t\turl:     \"https://localhost:8080/\",\n\t\tresult:  true,\n\t},\n\t{\n\t\tmatcher: schemeMatcher([]string{\"https\"}),\n\t\turl:     \"http://localhost:8080/\",\n\t\tresult:  false,\n\t},\n\t{\n\t\tmatcher: schemeMatcher([]string{\"http\"}),\n\t\turl:     \"https://localhost:8080/\",\n\t\tresult:  false,\n\t},\n}\n\ntype urlBuildingTest struct {\n\troute *Route\n\tvars  []string\n\turl   string\n}\n\nvar urlBuildingTests = []urlBuildingTest{\n\t{\n\t\troute: new(Route).Host(\"foo.domain.com\"),\n\t\tvars:  []string{},\n\t\turl:   \"http://foo.domain.com\",\n\t},\n\t{\n\t\troute: new(Route).Host(\"{subdomain}.domain.com\"),\n\t\tvars:  []string{\"subdomain\", \"bar\"},\n\t\turl:   \"http://bar.domain.com\",\n\t},\n\t{\n\t\troute: new(Route).Host(\"{subdomain}.domain.com:{port:.*}\"),\n\t\tvars:  []string{\"subdomain\", \"bar\", \"port\", \"65535\"},\n\t\turl:   \"http://bar.domain.com:65535\",\n\t},\n\t{\n\t\troute: new(Route).Host(\"foo.domain.com\").Path(\"/articles\"),\n\t\tvars:  []string{},\n\t\turl:   \"http://foo.domain.com/articles\",\n\t},\n\t{\n\t\troute: new(Route).Path(\"/articles\"),\n\t\tvars:  []string{},\n\t\turl:   \"/articles\",\n\t},\n\t{\n\t\troute: new(Route).Path(\"/articles/{category}/{id:[0-9]+}\"),\n\t\tvars:  []string{\"category\", \"technology\", \"id\", \"42\"},\n\t\turl:   \"/articles/technology/42\",\n\t},\n\t{\n\t\troute: new(Route).Host(\"{subdomain}.domain.com\").Path(\"/articles/{category}/{id:[0-9]+}\"),\n\t\tvars:  []string{\"subdomain\", \"foo\", \"category\", \"technology\", \"id\", \"42\"},\n\t\turl:   \"http://foo.domain.com/articles/technology/42\",\n\t},\n\t{\n\t\troute: new(Route).Host(\"example.com\").Schemes(\"https\", \"http\"),\n\t\tvars:  []string{},\n\t\turl:   \"https://example.com\",\n\t},\n}\n\nfunc TestHeaderMatcher(t *testing.T) {\n\tfor _, v := range headerMatcherTests {\n\t\trequest, _ := http.NewRequest(\"GET\", \"http://localhost:8080/\", nil)\n\t\tfor key, value := range v.headers {\n\t\t\trequest.Header.Add(key, value)\n\t\t}\n\t\tvar routeMatch RouteMatch\n\t\tresult := v.matcher.Match(request, &routeMatch)\n\t\tif result != v.result {\n\t\t\tif v.result {\n\t\t\t\tt.Errorf(\"%#v: should match %v.\", v.matcher, request.Header)\n\t\t\t} else {\n\t\t\t\tt.Errorf(\"%#v: should not match %v.\", v.matcher, request.Header)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestHostMatcher(t *testing.T) {\n\tfor _, v := range hostMatcherTests {\n\t\trequest, err := http.NewRequest(\"GET\", v.url, nil)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"http.NewRequest failed %#v\", err)\n\t\t\tcontinue\n\t\t}\n\t\tvar routeMatch RouteMatch\n\t\tresult := v.matcher.Match(request, &routeMatch)\n\t\tvars := routeMatch.Vars\n\t\tif result != v.result {\n\t\t\tif v.result {\n\t\t\t\tt.Errorf(\"%#v: should match %v.\", v.matcher, v.url)\n\t\t\t} else {\n\t\t\t\tt.Errorf(\"%#v: should not match %v.\", v.matcher, v.url)\n\t\t\t}\n\t\t}\n\t\tif result {\n\t\t\tif len(vars) != len(v.vars) {\n\t\t\t\tt.Errorf(\"%#v: vars length should be %v, got %v.\", v.matcher, len(v.vars), len(vars))\n\t\t\t}\n\t\t\tfor name, value := range vars {\n\t\t\t\tif v.vars[name] != value {\n\t\t\t\t\tt.Errorf(\"%#v: expected value %v for key %v, got %v.\", v.matcher, v.vars[name], name, value)\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tif len(vars) != 0 {\n\t\t\t\tt.Errorf(\"%#v: vars length should be 0, got %v.\", v.matcher, len(vars))\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestMethodMatcher(t *testing.T) {\n\tfor _, v := range methodMatcherTests {\n\t\trequest, _ := http.NewRequest(v.method, \"http://localhost:8080/\", nil)\n\t\tvar routeMatch RouteMatch\n\t\tresult := v.matcher.Match(request, &routeMatch)\n\t\tif result != v.result {\n\t\t\tif v.result {\n\t\t\t\tt.Errorf(\"%#v: should match %v.\", v.matcher, v.method)\n\t\t\t} else {\n\t\t\t\tt.Errorf(\"%#v: should not match %v.\", v.matcher, v.method)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestPathMatcher(t *testing.T) {\n\tfor _, v := range pathMatcherTests {\n\t\trequest, _ := http.NewRequest(\"GET\", v.url, nil)\n\t\tvar routeMatch RouteMatch\n\t\tresult := v.matcher.Match(request, &routeMatch)\n\t\tvars := routeMatch.Vars\n\t\tif result != v.result {\n\t\t\tif v.result {\n\t\t\t\tt.Errorf(\"%#v: should match %v.\", v.matcher, v.url)\n\t\t\t} else {\n\t\t\t\tt.Errorf(\"%#v: should not match %v.\", v.matcher, v.url)\n\t\t\t}\n\t\t}\n\t\tif result {\n\t\t\tif len(vars) != len(v.vars) {\n\t\t\t\tt.Errorf(\"%#v: vars length should be %v, got %v.\", v.matcher, len(v.vars), len(vars))\n\t\t\t}\n\t\t\tfor name, value := range vars {\n\t\t\t\tif v.vars[name] != value {\n\t\t\t\t\tt.Errorf(\"%#v: expected value %v for key %v, got %v.\", v.matcher, v.vars[name], name, value)\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tif len(vars) != 0 {\n\t\t\t\tt.Errorf(\"%#v: vars length should be 0, got %v.\", v.matcher, len(vars))\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestSchemeMatcher(t *testing.T) {\n\tfor _, v := range schemeMatcherTests {\n\t\trequest, _ := http.NewRequest(\"GET\", v.url, nil)\n\t\tvar routeMatch RouteMatch\n\t\tresult := v.matcher.Match(request, &routeMatch)\n\t\tif result != v.result {\n\t\t\tif v.result {\n\t\t\t\tt.Errorf(\"%#v: should match %v.\", v.matcher, v.url)\n\t\t\t} else {\n\t\t\t\tt.Errorf(\"%#v: should not match %v.\", v.matcher, v.url)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestUrlBuilding(t *testing.T) {\n\n\tfor _, v := range urlBuildingTests {\n\t\tu, _ := v.route.URL(v.vars...)\n\t\turl := u.String()\n\t\tif url != v.url {\n\t\t\tt.Errorf(\"expected %v, got %v\", v.url, url)\n\t\t}\n\t}\n\n\tArticleHandler := func(w http.ResponseWriter, r *http.Request) {\n\t}\n\n\trouter := NewRouter()\n\trouter.HandleFunc(\"/articles/{category}/{id:[0-9]+}\", ArticleHandler).Name(\"article\")\n\n\turl, _ := router.Get(\"article\").URL(\"category\", \"technology\", \"id\", \"42\")\n\texpected := \"/articles/technology/42\"\n\tif url.String() != expected {\n\t\tt.Errorf(\"Expected %v, got %v\", expected, url.String())\n\t}\n}\n\nfunc TestMatchedRouteName(t *testing.T) {\n\trouteName := \"stock\"\n\trouter := NewRouter()\n\troute := router.NewRoute().Path(\"/products/\").Name(routeName)\n\n\turl := \"http://www.example.com/products/\"\n\trequest, _ := http.NewRequest(\"GET\", url, nil)\n\tvar rv RouteMatch\n\tok := router.Match(request, &rv)\n\n\tif !ok || rv.Route != route {\n\t\tt.Errorf(\"Expected same route, got %+v.\", rv.Route)\n\t}\n\n\tretName := rv.Route.GetName()\n\tif retName != routeName {\n\t\tt.Errorf(\"Expected %q, got %q.\", routeName, retName)\n\t}\n}\n\nfunc TestSubRouting(t *testing.T) {\n\t// Example from docs.\n\trouter := NewRouter()\n\tsubrouter := router.NewRoute().Host(\"www.example.com\").Subrouter()\n\troute := subrouter.NewRoute().Path(\"/products/\").Name(\"products\")\n\n\turl := \"http://www.example.com/products/\"\n\trequest, _ := http.NewRequest(\"GET\", url, nil)\n\tvar rv RouteMatch\n\tok := router.Match(request, &rv)\n\n\tif !ok || rv.Route != route {\n\t\tt.Errorf(\"Expected same route, got %+v.\", rv.Route)\n\t}\n\n\tu, _ := router.Get(\"products\").URL()\n\tbuiltURL := u.String()\n\t// Yay, subroute aware of the domain when building!\n\tif builtURL != url {\n\t\tt.Errorf(\"Expected %q, got %q.\", url, builtURL)\n\t}\n}\n\nfunc TestVariableNames(t *testing.T) {\n\troute := new(Route).Host(\"{arg1}.domain.com\").Path(\"/{arg1}/{arg2:[0-9]+}\")\n\tif route.err == nil {\n\t\tt.Errorf(\"Expected error for duplicated variable names\")\n\t}\n}\n\nfunc TestRedirectSlash(t *testing.T) {\n\tvar route *Route\n\tvar routeMatch RouteMatch\n\tr := NewRouter()\n\n\tr.StrictSlash(false)\n\troute = r.NewRoute()\n\tif route.strictSlash != false {\n\t\tt.Errorf(\"Expected false redirectSlash.\")\n\t}\n\n\tr.StrictSlash(true)\n\troute = r.NewRoute()\n\tif route.strictSlash != true {\n\t\tt.Errorf(\"Expected true redirectSlash.\")\n\t}\n\n\troute = new(Route)\n\troute.strictSlash = true\n\troute.Path(\"/{arg1}/{arg2:[0-9]+}/\")\n\trequest, _ := http.NewRequest(\"GET\", \"http://localhost/foo/123\", nil)\n\trouteMatch = RouteMatch{}\n\t_ = route.Match(request, &routeMatch)\n\tvars := routeMatch.Vars\n\tif vars[\"arg1\"] != \"foo\" {\n\t\tt.Errorf(\"Expected foo.\")\n\t}\n\tif vars[\"arg2\"] != \"123\" {\n\t\tt.Errorf(\"Expected 123.\")\n\t}\n\trsp := NewRecorder()\n\trouteMatch.Handler.ServeHTTP(rsp, request)\n\tif rsp.HeaderMap.Get(\"Location\") != \"http://localhost/foo/123/\" {\n\t\tt.Errorf(\"Expected redirect header.\")\n\t}\n\n\troute = new(Route)\n\troute.strictSlash = true\n\troute.Path(\"/{arg1}/{arg2:[0-9]+}\")\n\trequest, _ = http.NewRequest(\"GET\", \"http://localhost/foo/123/\", nil)\n\trouteMatch = RouteMatch{}\n\t_ = route.Match(request, &routeMatch)\n\tvars = routeMatch.Vars\n\tif vars[\"arg1\"] != \"foo\" {\n\t\tt.Errorf(\"Expected foo.\")\n\t}\n\tif vars[\"arg2\"] != \"123\" {\n\t\tt.Errorf(\"Expected 123.\")\n\t}\n\trsp = NewRecorder()\n\trouteMatch.Handler.ServeHTTP(rsp, request)\n\tif rsp.HeaderMap.Get(\"Location\") != \"http://localhost/foo/123\" {\n\t\tt.Errorf(\"Expected redirect header.\")\n\t}\n}\n\n// Test for the new regexp library, still not available in stable Go.\nfunc TestNewRegexp(t *testing.T) {\n\tvar p *routeRegexp\n\tvar matches []string\n\n\ttests := map[string]map[string][]string{\n\t\t\"/{foo:a{2}}\": {\n\t\t\t\"/a\":    nil,\n\t\t\t\"/aa\":   {\"aa\"},\n\t\t\t\"/aaa\":  nil,\n\t\t\t\"/aaaa\": nil,\n\t\t},\n\t\t\"/{foo:a{2,}}\": {\n\t\t\t\"/a\":    nil,\n\t\t\t\"/aa\":   {\"aa\"},\n\t\t\t\"/aaa\":  {\"aaa\"},\n\t\t\t\"/aaaa\": {\"aaaa\"},\n\t\t},\n\t\t\"/{foo:a{2,3}}\": {\n\t\t\t\"/a\":    nil,\n\t\t\t\"/aa\":   {\"aa\"},\n\t\t\t\"/aaa\":  {\"aaa\"},\n\t\t\t\"/aaaa\": nil,\n\t\t},\n\t\t\"/{foo:[a-z]{3}}/{bar:[a-z]{2}}\": {\n\t\t\t\"/a\":       nil,\n\t\t\t\"/ab\":      nil,\n\t\t\t\"/abc\":     nil,\n\t\t\t\"/abcd\":    nil,\n\t\t\t\"/abc/ab\":  {\"abc\", \"ab\"},\n\t\t\t\"/abc/abc\": nil,\n\t\t\t\"/abcd/ab\": nil,\n\t\t},\n\t\t`/{foo:\\w{3,}}/{bar:\\d{2,}}`: {\n\t\t\t\"/a\":        nil,\n\t\t\t\"/ab\":       nil,\n\t\t\t\"/abc\":      nil,\n\t\t\t\"/abc/1\":    nil,\n\t\t\t\"/abc/12\":   {\"abc\", \"12\"},\n\t\t\t\"/abcd/12\":  {\"abcd\", \"12\"},\n\t\t\t\"/abcd/123\": {\"abcd\", \"123\"},\n\t\t},\n\t}\n\n\tfor pattern, paths := range tests {\n\t\tp, _ = newRouteRegexp(pattern, regexpTypePath, routeRegexpOptions{})\n\t\tfor path, result := range paths {\n\t\t\tmatches = p.regexp.FindStringSubmatch(path)\n\t\t\tif result == nil {\n\t\t\t\tif matches != nil {\n\t\t\t\t\tt.Errorf(\"%v should not match %v.\", pattern, path)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif len(matches) != len(result)+1 {\n\t\t\t\t\tt.Errorf(\"Expected %v matches, got %v.\", len(result)+1, len(matches))\n\t\t\t\t} else {\n\t\t\t\t\tfor k, v := range result {\n\t\t\t\t\t\tif matches[k+1] != v {\n\t\t\t\t\t\t\tt.Errorf(\"Expected %v, got %v.\", v, matches[k+1])\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "regexp.go",
    "content": "// Copyright 2012 The Gorilla Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\npackage mux\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n)\n\ntype routeRegexpOptions struct {\n\tstrictSlash    bool\n\tuseEncodedPath bool\n}\n\ntype regexpType int\n\nconst (\n\tregexpTypePath regexpType = iota\n\tregexpTypeHost\n\tregexpTypePrefix\n\tregexpTypeQuery\n)\n\n// newRouteRegexp parses a route template and returns a routeRegexp,\n// used to match a host, a path or a query string.\n//\n// It will extract named variables, assemble a regexp to be matched, create\n// a \"reverse\" template to build URLs and compile regexps to validate variable\n// values used in URL building.\n//\n// Previously we accepted only Python-like identifiers for variable\n// names ([a-zA-Z_][a-zA-Z0-9_]*), but currently the only restriction is that\n// name and pattern can't be empty, and names can't contain a colon.\nfunc newRouteRegexp(tpl string, typ regexpType, options routeRegexpOptions) (*routeRegexp, error) {\n\t// Check if it is well-formed.\n\tidxs, errBraces := braceIndices(tpl)\n\tif errBraces != nil {\n\t\treturn nil, errBraces\n\t}\n\t// Backup the original.\n\ttemplate := tpl\n\t// Now let's parse it.\n\tdefaultPattern := \"[^/]+\"\n\tif typ == regexpTypeQuery {\n\t\tdefaultPattern = \".*\"\n\t} else if typ == regexpTypeHost {\n\t\tdefaultPattern = \"[^.]+\"\n\t}\n\t// Only match strict slash if not matching\n\tif typ != regexpTypePath {\n\t\toptions.strictSlash = false\n\t}\n\t// Set a flag for strictSlash.\n\tendSlash := false\n\tif options.strictSlash && strings.HasSuffix(tpl, \"/\") {\n\t\ttpl = tpl[:len(tpl)-1]\n\t\tendSlash = true\n\t}\n\tvarsN := make([]string, len(idxs)/2)\n\tvarsR := make([]*regexp.Regexp, len(idxs)/2)\n\n\tvar pattern, reverse strings.Builder\n\tpattern.WriteByte('^')\n\n\tvar end, colonIdx, groupIdx int\n\tvar err error\n\tvar patt, param, name string\n\tfor i := 0; i < len(idxs); i += 2 {\n\t\t// Set all values we are interested in.\n\t\tgroupIdx = i / 2\n\n\t\traw := tpl[end:idxs[i]]\n\t\tend = idxs[i+1]\n\t\ttag := tpl[idxs[i]:end]\n\n\t\t// trim braces from tag\n\t\tparam = tag[1 : len(tag)-1]\n\n\t\tcolonIdx = strings.Index(param, \":\")\n\t\tif colonIdx == -1 {\n\t\t\tname = param\n\t\t\tpatt = defaultPattern\n\t\t} else {\n\t\t\tname = param[0:colonIdx]\n\t\t\tpatt = param[colonIdx+1:]\n\t\t}\n\n\t\t// Name or pattern can't be empty.\n\t\tif name == \"\" || patt == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"mux: missing name or pattern in %q\", tag)\n\t\t}\n\t\t// Build the regexp pattern.\n\t\tgroupName := varGroupName(groupIdx)\n\n\t\tpattern.WriteString(regexp.QuoteMeta(raw) + \"(?P<\" + groupName + \">\" + patt + \")\")\n\n\t\t// Build the reverse template.\n\t\treverse.WriteString(raw + \"%s\")\n\n\t\t// Append variable name and compiled pattern.\n\t\tvarsN[groupIdx] = name\n\t\tvarsR[groupIdx], err = RegexpCompileFunc(\"^\" + patt + \"$\")\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"mux: error compiling regex for %q: %w\", tag, err)\n\t\t}\n\t}\n\t// Add the remaining.\n\traw := tpl[end:]\n\tpattern.WriteString(regexp.QuoteMeta(raw))\n\tif options.strictSlash {\n\t\tpattern.WriteString(\"[/]?\")\n\t}\n\tif typ == regexpTypeQuery {\n\t\t// Add the default pattern if the query value is empty\n\t\tif queryVal := strings.SplitN(template, \"=\", 2)[1]; queryVal == \"\" {\n\t\t\tpattern.WriteString(defaultPattern)\n\t\t}\n\t}\n\tif typ != regexpTypePrefix {\n\t\tpattern.WriteByte('$')\n\t}\n\n\t// Compile full regexp.\n\tpatternStr := pattern.String()\n\treg, errCompile := RegexpCompileFunc(patternStr)\n\tif errCompile != nil {\n\t\treturn nil, errCompile\n\t}\n\n\t// Check for capturing groups which used to work in older versions\n\tif reg.NumSubexp() != len(idxs)/2 {\n\t\tpanic(fmt.Sprintf(\"route %s contains capture groups in its regexp. \", template) +\n\t\t\t\"Only non-capturing groups are accepted: e.g. (?:pattern) instead of (pattern)\")\n\t}\n\n\tvar wildcardHostPort bool\n\tif typ == regexpTypeHost {\n\t\tif !strings.Contains(patternStr, \":\") {\n\t\t\twildcardHostPort = true\n\t\t}\n\t}\n\treverse.WriteString(raw)\n\tif endSlash {\n\t\treverse.WriteByte('/')\n\t}\n\n\t// Done!\n\treturn &routeRegexp{\n\t\ttemplate:         template,\n\t\tregexpType:       typ,\n\t\toptions:          options,\n\t\tregexp:           reg,\n\t\treverse:          reverse.String(),\n\t\tvarsN:            varsN,\n\t\tvarsR:            varsR,\n\t\twildcardHostPort: wildcardHostPort,\n\t}, nil\n}\n\n// routeRegexp stores a regexp to match a host or path and information to\n// collect and validate route variables.\ntype routeRegexp struct {\n\t// The unmodified template.\n\ttemplate string\n\t// The type of match\n\tregexpType regexpType\n\t// Options for matching\n\toptions routeRegexpOptions\n\t// Expanded regexp.\n\tregexp *regexp.Regexp\n\t// Reverse template.\n\treverse string\n\t// Variable names.\n\tvarsN []string\n\t// Variable regexps (validators).\n\tvarsR []*regexp.Regexp\n\t// Wildcard host-port (no strict port match in hostname)\n\twildcardHostPort bool\n}\n\n// Match matches the regexp against the URL host or path.\nfunc (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool {\n\tif r.regexpType == regexpTypeHost {\n\t\thost := getHost(req)\n\t\tif r.wildcardHostPort {\n\t\t\t// Don't be strict on the port match\n\t\t\tif i := strings.Index(host, \":\"); i != -1 {\n\t\t\t\thost = host[:i]\n\t\t\t}\n\t\t}\n\t\treturn r.regexp.MatchString(host)\n\t}\n\n\tif r.regexpType == regexpTypeQuery {\n\t\treturn r.matchQueryString(req)\n\t}\n\tpath := req.URL.Path\n\tif r.options.useEncodedPath {\n\t\tpath = req.URL.EscapedPath()\n\t}\n\treturn r.regexp.MatchString(path)\n}\n\n// url builds a URL part using the given values.\nfunc (r *routeRegexp) url(values map[string]string) (string, error) {\n\turlValues := make([]interface{}, len(r.varsN))\n\tfor k, v := range r.varsN {\n\t\tvalue, ok := values[v]\n\t\tif !ok {\n\t\t\treturn \"\", fmt.Errorf(\"mux: missing route variable %q\", v)\n\t\t}\n\t\tif r.regexpType == regexpTypeQuery {\n\t\t\tvalue = url.QueryEscape(value)\n\t\t}\n\t\turlValues[k] = value\n\t}\n\trv := fmt.Sprintf(r.reverse, urlValues...)\n\tif !r.regexp.MatchString(rv) {\n\t\t// The URL is checked against the full regexp, instead of checking\n\t\t// individual variables. This is faster but to provide a good error\n\t\t// message, we check individual regexps if the URL doesn't match.\n\t\tfor k, v := range r.varsN {\n\t\t\tif !r.varsR[k].MatchString(values[v]) {\n\t\t\t\treturn \"\", fmt.Errorf(\n\t\t\t\t\t\"mux: variable %q doesn't match, expected %q\", values[v],\n\t\t\t\t\tr.varsR[k].String())\n\t\t\t}\n\t\t}\n\t}\n\treturn rv, nil\n}\n\n// getURLQuery returns a single query parameter from a request URL.\n// For a URL with foo=bar&baz=ding, we return only the relevant key\n// value pair for the routeRegexp.\nfunc (r *routeRegexp) getURLQuery(req *http.Request) string {\n\tif r.regexpType != regexpTypeQuery {\n\t\treturn \"\"\n\t}\n\ttemplateKey := strings.SplitN(r.template, \"=\", 2)[0]\n\tval, ok := findFirstQueryKey(req.URL.RawQuery, templateKey)\n\tif ok {\n\t\treturn templateKey + \"=\" + val\n\t}\n\treturn \"\"\n}\n\n// findFirstQueryKey returns the same result as (*url.URL).Query()[key][0].\n// If key was not found, empty string and false is returned.\nfunc findFirstQueryKey(rawQuery, key string) (value string, ok bool) {\n\tquery := []byte(rawQuery)\n\tfor len(query) > 0 {\n\t\tfoundKey := query\n\t\tif i := bytes.IndexAny(foundKey, \"&;\"); i >= 0 {\n\t\t\tfoundKey, query = foundKey[:i], foundKey[i+1:]\n\t\t} else {\n\t\t\tquery = query[:0]\n\t\t}\n\t\tif len(foundKey) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tvar value []byte\n\t\tif i := bytes.IndexByte(foundKey, '='); i >= 0 {\n\t\t\tfoundKey, value = foundKey[:i], foundKey[i+1:]\n\t\t}\n\t\tif len(foundKey) < len(key) {\n\t\t\t// Cannot possibly be key.\n\t\t\tcontinue\n\t\t}\n\t\tkeyString, err := url.QueryUnescape(string(foundKey))\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tif keyString != key {\n\t\t\tcontinue\n\t\t}\n\t\tvalueString, err := url.QueryUnescape(string(value))\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\treturn valueString, true\n\t}\n\treturn \"\", false\n}\n\nfunc (r *routeRegexp) matchQueryString(req *http.Request) bool {\n\treturn r.regexp.MatchString(r.getURLQuery(req))\n}\n\n// braceIndices returns the first level curly brace indices from a string.\n// It returns an error in case of unbalanced braces.\nfunc braceIndices(s string) ([]int, error) {\n\tvar level, idx int\n\tvar idxs []int\n\tfor i := 0; i < len(s); i++ {\n\t\tswitch s[i] {\n\t\tcase '{':\n\t\t\tif level++; level == 1 {\n\t\t\t\tidx = i\n\t\t\t}\n\t\tcase '}':\n\t\t\tif level--; level == 0 {\n\t\t\t\tidxs = append(idxs, idx, i+1)\n\t\t\t} else if level < 0 {\n\t\t\t\treturn nil, fmt.Errorf(\"mux: unbalanced braces in %q\", s)\n\t\t\t}\n\t\t}\n\t}\n\tif level != 0 {\n\t\treturn nil, fmt.Errorf(\"mux: unbalanced braces in %q\", s)\n\t}\n\treturn idxs, nil\n}\n\n// varGroupName builds a capturing group name for the indexed variable.\nfunc varGroupName(idx int) string {\n\treturn \"v\" + strconv.Itoa(idx)\n}\n\n// ----------------------------------------------------------------------------\n// routeRegexpGroup\n// ----------------------------------------------------------------------------\n\n// routeRegexpGroup groups the route matchers that carry variables.\ntype routeRegexpGroup struct {\n\thost    *routeRegexp\n\tpath    *routeRegexp\n\tqueries []*routeRegexp\n}\n\n// setMatch extracts the variables from the URL once a route matches.\nfunc (v routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route) {\n\t// Store host variables.\n\tif v.host != nil {\n\t\tif len(v.host.varsN) > 0 {\n\t\t\thost := getHost(req)\n\t\t\tif v.host.wildcardHostPort {\n\t\t\t\t// Don't be strict on the port match\n\t\t\t\tif i := strings.Index(host, \":\"); i != -1 {\n\t\t\t\t\thost = host[:i]\n\t\t\t\t}\n\t\t\t}\n\t\t\tmatches := v.host.regexp.FindStringSubmatchIndex(host)\n\t\t\tif len(matches) > 0 {\n\t\t\t\tm.Vars = extractVars(host, matches, v.host.varsN, m.Vars)\n\t\t\t}\n\t\t}\n\t}\n\tpath := req.URL.Path\n\tif r.useEncodedPath {\n\t\tpath = req.URL.EscapedPath()\n\t}\n\t// Store path variables.\n\tif v.path != nil {\n\t\tif len(v.path.varsN) > 0 {\n\t\t\tmatches := v.path.regexp.FindStringSubmatchIndex(path)\n\t\t\tif len(matches) > 0 {\n\t\t\t\tm.Vars = extractVars(path, matches, v.path.varsN, m.Vars)\n\t\t\t}\n\t\t}\n\t\t// Check if we should redirect.\n\t\tif v.path.options.strictSlash {\n\t\t\tp1 := strings.HasSuffix(path, \"/\")\n\t\t\tp2 := strings.HasSuffix(v.path.template, \"/\")\n\t\t\tif p1 != p2 {\n\t\t\t\tp := req.URL.Path\n\t\t\t\tif p1 {\n\t\t\t\t\tp = p[:len(p)-1]\n\t\t\t\t} else {\n\t\t\t\t\tp += \"/\"\n\t\t\t\t}\n\t\t\t\tu := replaceURLPath(req.URL, p)\n\t\t\t\tm.Handler = http.RedirectHandler(u, http.StatusMovedPermanently)\n\t\t\t}\n\t\t}\n\t}\n\t// Store query string variables.\n\tfor _, q := range v.queries {\n\t\tif len(q.varsN) > 0 {\n\t\t\tqueryURL := q.getURLQuery(req)\n\t\t\tmatches := q.regexp.FindStringSubmatchIndex(queryURL)\n\t\t\tif len(matches) > 0 {\n\t\t\t\tm.Vars = extractVars(queryURL, matches, q.varsN, m.Vars)\n\t\t\t}\n\t\t}\n\t}\n}\n\n// getHost tries its best to return the request host.\n// According to section 14.23 of RFC 2616 the Host header\n// can include the port number if the default value of 80 is not used.\nfunc getHost(r *http.Request) string {\n\tif r.URL.IsAbs() {\n\t\treturn r.URL.Host\n\t}\n\treturn r.Host\n}\n\nfunc extractVars(input string, matches []int, names []string, output map[string]string) map[string]string {\n\tfor i, name := range names {\n\t\tif output == nil {\n\t\t\toutput = make(map[string]string, len(names))\n\t\t}\n\t\toutput[name] = input[matches[2*i+2]:matches[2*i+3]]\n\t}\n\treturn output\n}\n"
  },
  {
    "path": "regexp_test.go",
    "content": "package mux\n\nimport (\n\t\"net/url\"\n\t\"reflect\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc Test_newRouteRegexp_Errors(t *testing.T) {\n\ttests := []struct {\n\t\tin, out string\n\t}{\n\t\t{\"/{}\", `mux: missing name or pattern in \"{}\"`},\n\t\t{\"/{:.}\", `mux: missing name or pattern in \"{:.}\"`},\n\t\t{\"/{a:}\", `mux: missing name or pattern in \"{a:}\"`},\n\t\t{\"/{id:abc(}\", `mux: error compiling regex for \"{id:abc(}\":`},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(\"Test case for \"+tc.in, func(t *testing.T) {\n\t\t\t_, err := newRouteRegexp(tc.in, 0, routeRegexpOptions{})\n\t\t\tif err != nil {\n\t\t\t\tif strings.HasPrefix(err.Error(), tc.out) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tt.Errorf(\"Resulting error does not contain %q as expected, error: %s\", tc.out, err)\n\t\t\t} else {\n\t\t\t\tt.Error(\"Expected error, got nil\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc Test_findFirstQueryKey(t *testing.T) {\n\ttests := []string{\n\t\t\"a=1&b=2\",\n\t\t\"a=1&a=2&a=banana\",\n\t\t\"ascii=%3Ckey%3A+0x90%3E\",\n\t\t\"a=1;b=2\",\n\t\t\"a=1&a=2;a=banana\",\n\t\t\"a==\",\n\t\t\"a=%2\",\n\t\t\"a=20&%20%3F&=%23+%25%21%3C%3E%23%22%7B%7D%7C%5C%5E%5B%5D%60%E2%98%BA%09:%2F@$%27%28%29%2A%2C%3B&a=30\",\n\t\t\"a=1& ?&=#+%!<>#\\\"{}|\\\\^[]`☺\\t:/@$'()*,;&a=5\",\n\t\t\"a=xxxxxxxxxxxxxxxx&b=YYYYYYYYYYYYYYY&c=ppppppppppppppppppp&f=ttttttttttttttttt&a=uuuuuuuuuuuuu\",\n\t}\n\tfor _, query := range tests {\n\t\tt.Run(query, func(t *testing.T) {\n\t\t\t// Check against url.ParseQuery, ignoring the error.\n\t\t\tall, _ := url.ParseQuery(query)\n\t\t\tfor key, want := range all {\n\t\t\t\tt.Run(key, func(t *testing.T) {\n\t\t\t\t\tgot, ok := findFirstQueryKey(query, key)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\tt.Error(\"Did not get expected key\", key)\n\t\t\t\t\t}\n\t\t\t\t\tif !reflect.DeepEqual(got, want[0]) {\n\t\t\t\t\t\tt.Errorf(\"findFirstQueryKey(%s,%s) = %v, want %v\", query, key, got, want[0])\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc Benchmark_findQueryKey(b *testing.B) {\n\ttests := []string{\n\t\t\"a=1&b=2\",\n\t\t\"ascii=%3Ckey%3A+0x90%3E\",\n\t\t\"a=20&%20%3F&=%23+%25%21%3C%3E%23%22%7B%7D%7C%5C%5E%5B%5D%60%E2%98%BA%09:%2F@$%27%28%29%2A%2C%3B&a=30\",\n\t\t\"a=xxxxxxxxxxxxxxxx&bbb=YYYYYYYYYYYYYYY&cccc=ppppppppppppppppppp&ddddd=ttttttttttttttttt&a=uuuuuuuuuuuuu\",\n\t\t\"a=;b=;c=;d=;e=;f=;g=;h=;i=,j=;k=\",\n\t}\n\tfor i, query := range tests {\n\t\tb.Run(strconv.Itoa(i), func(b *testing.B) {\n\t\t\t// Check against url.ParseQuery, ignoring the error.\n\t\t\tall, _ := url.ParseQuery(query)\n\t\t\tb.ReportAllocs()\n\t\t\tb.ResetTimer()\n\t\t\tfor i := 0; i < b.N; i++ {\n\t\t\t\tfor key := range all {\n\t\t\t\t\t_, _ = findFirstQueryKey(query, key)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc Benchmark_findQueryKeyGoLib(b *testing.B) {\n\ttests := []string{\n\t\t\"a=1&b=2\",\n\t\t\"ascii=%3Ckey%3A+0x90%3E\",\n\t\t\"a=20&%20%3F&=%23+%25%21%3C%3E%23%22%7B%7D%7C%5C%5E%5B%5D%60%E2%98%BA%09:%2F@$%27%28%29%2A%2C%3B&a=30\",\n\t\t\"a=xxxxxxxxxxxxxxxx&bbb=YYYYYYYYYYYYYYY&cccc=ppppppppppppppppppp&ddddd=ttttttttttttttttt&a=uuuuuuuuuuuuu\",\n\t\t\"a=;b=;c=;d=;e=;f=;g=;h=;i=,j=;k=\",\n\t}\n\tfor i, query := range tests {\n\t\tb.Run(strconv.Itoa(i), func(b *testing.B) {\n\t\t\t// Check against url.ParseQuery, ignoring the error.\n\t\t\tall, _ := url.ParseQuery(query)\n\t\t\tvar u url.URL\n\t\t\tu.RawQuery = query\n\t\t\tb.ReportAllocs()\n\t\t\tb.ResetTimer()\n\t\t\tfor i := 0; i < b.N; i++ {\n\t\t\t\tfor key := range all {\n\t\t\t\t\tv := u.Query()[key]\n\t\t\t\t\tif len(v) > 0 {\n\t\t\t\t\t\t_ = v[0]\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "route.go",
    "content": "// Copyright 2012 The Gorilla Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\npackage mux\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"regexp\"\n\t\"strings\"\n)\n\n// Route stores information to match a request and build URLs.\ntype Route struct {\n\t// Request handler for the route.\n\thandler http.Handler\n\t// If true, this route never matches: it is only used to build URLs.\n\tbuildOnly bool\n\t// The name used to build URLs.\n\tname string\n\t// Error resulted from building a route.\n\terr error\n\n\t// The meta data associated with this route\n\tmetadata map[any]any\n\n\t// \"global\" reference to all named routes\n\tnamedRoutes map[string]*Route\n\n\t// route specific middleware\n\tmiddlewares []middleware\n\n\t// config possibly passed in from `Router`\n\trouteConf\n}\n\n// SkipClean reports whether path cleaning is enabled for this route via\n// Router.SkipClean.\nfunc (r *Route) SkipClean() bool {\n\treturn r.skipClean\n}\n\n// Match matches the route against the request.\nfunc (r *Route) Match(req *http.Request, match *RouteMatch) bool {\n\tif r.buildOnly || r.err != nil {\n\t\treturn false\n\t}\n\n\tvar matchErr error\n\n\t// Match everything.\n\tfor _, m := range r.matchers {\n\t\tif matched := m.Match(req, match); !matched {\n\t\t\tif _, ok := m.(methodMatcher); ok {\n\t\t\t\tmatchErr = ErrMethodMismatch\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// Multiple routes may share the same path but use different HTTP methods. For instance:\n\t\t\t// Route 1: POST \"/users/{id}\".\n\t\t\t// Route 2: GET \"/users/{id}\", parameters: \"id\": \"[0-9]+\".\n\t\t\t//\n\t\t\t// The router must handle these cases correctly. For a GET request to \"/users/abc\" with \"id\" as \"-2\",\n\t\t\t// The router should return a \"Not Found\" error as no route fully matches this request.\n\t\t\tif rr, ok := m.(*routeRegexp); ok {\n\t\t\t\tif rr.regexpType == regexpTypeQuery {\n\t\t\t\t\tmatchErr = ErrNotFound\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Ignore ErrNotFound errors. These errors arise from match call\n\t\t\t// to Subrouters.\n\t\t\t//\n\t\t\t// This prevents subsequent matching subrouters from failing to\n\t\t\t// run middleware. If not ignored, the middleware would see a\n\t\t\t// non-nil MatchErr and be skipped, even when there was a\n\t\t\t// matching route.\n\t\t\tif match.MatchErr == ErrNotFound {\n\t\t\t\tmatch.MatchErr = nil\n\t\t\t}\n\n\t\t\tmatchErr = nil // nolint:ineffassign\n\t\t\treturn false\n\t\t}\n\t}\n\n\tif matchErr != nil {\n\t\tmatch.MatchErr = matchErr\n\t\treturn false\n\t}\n\n\tif match.MatchErr != nil && r.handler != nil {\n\t\t// We found a route which matches request method, clear MatchErr\n\t\tmatch.MatchErr = nil\n\t\t// Then override the mis-matched handler\n\t\tmatch.Handler = r.handler\n\t}\n\n\t// Yay, we have a match. Let's collect some info about it.\n\tif match.Route == nil {\n\t\tmatch.Route = r\n\t}\n\tif match.Handler == nil {\n\t\tmatch.Handler = r.GetHandlerWithMiddlewares()\n\t}\n\n\t// Set variables.\n\tr.regexp.setMatch(req, match, r)\n\treturn true\n}\n\n// ----------------------------------------------------------------------------\n// Route attributes\n// ----------------------------------------------------------------------------\n\n// GetError returns an error resulted from building the route, if any.\nfunc (r *Route) GetError() error {\n\treturn r.err\n}\n\n// BuildOnly sets the route to never match: it is only used to build URLs.\nfunc (r *Route) BuildOnly() *Route {\n\tr.buildOnly = true\n\treturn r\n}\n\n// MetaData -------------------------------------------------------------------\n\n// Metadata is used to set metadata on a route\nfunc (r *Route) Metadata(key any, value any) *Route {\n\tif r.metadata == nil {\n\t\tr.metadata = make(map[any]any)\n\t}\n\n\tr.metadata[key] = value\n\treturn r\n}\n\n// GetMetadata returns the metadata map for route\nfunc (r *Route) GetMetadata() map[any]any {\n\treturn r.metadata\n}\n\n// MetadataContains returns whether or not the key is present in the metadata map\nfunc (r *Route) MetadataContains(key any) bool {\n\t_, ok := r.metadata[key]\n\treturn ok\n}\n\n// GetMetadataValue returns the value of a specific key in the metadata map. If the key is not present in the map mux.ErrMetadataKeyNotFound is returned\nfunc (r *Route) GetMetadataValue(key any) (any, error) {\n\tvalue, ok := r.metadata[key]\n\tif !ok {\n\t\treturn nil, ErrMetadataKeyNotFound\n\t}\n\n\treturn value, nil\n}\n\n// GetMetadataValueOr returns the value of a specific key in the metadata map. If the key is not present in the metadata the fallback value is returned\nfunc (r *Route) GetMetadataValueOr(key any, fallbackValue any) any {\n\tvalue, ok := r.metadata[key]\n\tif !ok {\n\t\treturn fallbackValue\n\t}\n\n\treturn value\n}\n\n// Handler --------------------------------------------------------------------\n\n// Handler sets a handler for the route.\nfunc (r *Route) Handler(handler http.Handler) *Route {\n\tif r.err == nil {\n\t\tr.handler = handler\n\t}\n\treturn r\n}\n\n// HandlerFunc sets a handler function for the route.\nfunc (r *Route) HandlerFunc(f func(http.ResponseWriter, *http.Request)) *Route {\n\treturn r.Handler(http.HandlerFunc(f))\n}\n\n// GetHandler returns the handler for the route, if any.\nfunc (r *Route) GetHandler() http.Handler {\n\treturn r.handler\n}\n\n// GetHandlerWithMiddleware returns the route handler wrapped in the assigned middlewares.\n// If no middlewares are specified, just the handler, if any, is returned.\nfunc (r *Route) GetHandlerWithMiddlewares() http.Handler {\n\thandler := r.handler\n\n\tif handler != nil && len(r.middlewares) > 0 {\n\t\tfor i := len(r.middlewares) - 1; i >= 0; i-- {\n\t\t\thandler = r.middlewares[i].Middleware(handler)\n\t\t}\n\t}\n\n\treturn handler\n}\n\n// Name -----------------------------------------------------------------------\n\n// Name sets the name for the route, used to build URLs.\n// It is an error to call Name more than once on a route.\nfunc (r *Route) Name(name string) *Route {\n\tif r.name != \"\" {\n\t\tr.err = fmt.Errorf(\"mux: route already has name %q, can't set %q\",\n\t\t\tr.name, name)\n\t}\n\tif r.err == nil {\n\t\tr.name = name\n\t\tr.namedRoutes[name] = r\n\t}\n\treturn r\n}\n\n// GetName returns the name for the route, if any.\nfunc (r *Route) GetName() string {\n\treturn r.name\n}\n\n// ----------------------------------------------------------------------------\n// Matchers\n// ----------------------------------------------------------------------------\n\n// matcher types try to match a request.\ntype matcher interface {\n\tMatch(*http.Request, *RouteMatch) bool\n}\n\n// addMatcher adds a matcher to the route.\nfunc (r *Route) addMatcher(m matcher) *Route {\n\tif r.err == nil {\n\t\tr.matchers = append(r.matchers, m)\n\t}\n\treturn r\n}\n\n// addRegexpMatcher adds a host or path matcher and builder to a route.\nfunc (r *Route) addRegexpMatcher(tpl string, typ regexpType) error {\n\tif r.err != nil {\n\t\treturn r.err\n\t}\n\tif typ == regexpTypePath || typ == regexpTypePrefix {\n\t\tif len(tpl) > 0 && tpl[0] != '/' {\n\t\t\treturn fmt.Errorf(\"mux: path must start with a slash, got %q\", tpl)\n\t\t}\n\t\tif r.regexp.path != nil {\n\t\t\ttpl = strings.TrimRight(r.regexp.path.template, \"/\") + tpl\n\t\t}\n\t}\n\trr, err := newRouteRegexp(tpl, typ, routeRegexpOptions{\n\t\tstrictSlash:    r.strictSlash,\n\t\tuseEncodedPath: r.useEncodedPath,\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, q := range r.regexp.queries {\n\t\tif err = uniqueVars(rr.varsN, q.varsN); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif typ == regexpTypeHost {\n\t\tif r.regexp.path != nil {\n\t\t\tif err = uniqueVars(rr.varsN, r.regexp.path.varsN); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tr.regexp.host = rr\n\t} else {\n\t\tif r.regexp.host != nil {\n\t\t\tif err = uniqueVars(rr.varsN, r.regexp.host.varsN); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tif typ == regexpTypeQuery {\n\t\t\tr.regexp.queries = append(r.regexp.queries, rr)\n\t\t} else {\n\t\t\tr.regexp.path = rr\n\t\t}\n\t}\n\tr.addMatcher(rr)\n\treturn nil\n}\n\n// Headers --------------------------------------------------------------------\n\n// headerMatcher matches the request against header values.\ntype headerMatcher map[string]string\n\nfunc (m headerMatcher) Match(r *http.Request, match *RouteMatch) bool {\n\treturn matchMapWithString(m, r.Header, true)\n}\n\n// Headers adds a matcher for request header values.\n// It accepts a sequence of key/value pairs to be matched. For example:\n//\n//\tr := mux.NewRouter().NewRoute()\n//\tr.Headers(\"Content-Type\", \"application/json\",\n//\t          \"X-Requested-With\", \"XMLHttpRequest\")\n//\n// The above route will only match if both request header values match.\n// If the value is an empty string, it will match any value if the key is set.\nfunc (r *Route) Headers(pairs ...string) *Route {\n\tif r.err == nil {\n\t\tvar headers map[string]string\n\t\theaders, r.err = mapFromPairsToString(pairs...)\n\t\treturn r.addMatcher(headerMatcher(headers))\n\t}\n\treturn r\n}\n\n// headerRegexMatcher matches the request against the route given a regex for the header\ntype headerRegexMatcher map[string]*regexp.Regexp\n\nfunc (m headerRegexMatcher) Match(r *http.Request, match *RouteMatch) bool {\n\treturn matchMapWithRegex(m, r.Header, true)\n}\n\n// HeadersRegexp accepts a sequence of key/value pairs, where the value has regex\n// support. For example:\n//\n//\tr := mux.NewRouter().NewRoute()\n//\tr.HeadersRegexp(\"Content-Type\", \"application/(text|json)\",\n//\t          \"X-Requested-With\", \"XMLHttpRequest\")\n//\n// The above route will only match if both the request header matches both regular expressions.\n// If the value is an empty string, it will match any value if the key is set.\n// Use the start and end of string anchors (^ and $) to match an exact value.\nfunc (r *Route) HeadersRegexp(pairs ...string) *Route {\n\tif r.err == nil {\n\t\tvar headers map[string]*regexp.Regexp\n\t\theaders, r.err = mapFromPairsToRegex(pairs...)\n\t\treturn r.addMatcher(headerRegexMatcher(headers))\n\t}\n\treturn r\n}\n\n// Host -----------------------------------------------------------------------\n\n// Host adds a matcher for the URL host.\n// It accepts a template with zero or more URL variables enclosed by {}.\n// Variables can define an optional regexp pattern to be matched:\n//\n// - {name} matches anything until the next dot.\n//\n// - {name:pattern} matches the given regexp pattern.\n//\n// For example:\n//\n//\tr := mux.NewRouter().NewRoute()\n//\tr.Host(\"www.example.com\")\n//\tr.Host(\"{subdomain}.domain.com\")\n//\tr.Host(\"{subdomain:[a-z]+}.domain.com\")\n//\n// Variable names must be unique in a given route. They can be retrieved\n// calling mux.Vars(request).\nfunc (r *Route) Host(tpl string) *Route {\n\tr.err = r.addRegexpMatcher(tpl, regexpTypeHost)\n\treturn r\n}\n\n// MatcherFunc ----------------------------------------------------------------\n\n// MatcherFunc is the function signature used by custom matchers.\ntype MatcherFunc func(*http.Request, *RouteMatch) bool\n\n// Match returns the match for a given request.\nfunc (m MatcherFunc) Match(r *http.Request, match *RouteMatch) bool {\n\treturn m(r, match)\n}\n\n// MatcherFunc adds a custom function to be used as request matcher.\nfunc (r *Route) MatcherFunc(f MatcherFunc) *Route {\n\treturn r.addMatcher(f)\n}\n\n// Methods --------------------------------------------------------------------\n\n// methodMatcher matches the request against HTTP methods.\ntype methodMatcher []string\n\nfunc (m methodMatcher) Match(r *http.Request, match *RouteMatch) bool {\n\treturn matchInArray(m, r.Method)\n}\n\n// Methods adds a matcher for HTTP methods.\n// It accepts a sequence of one or more methods to be matched, e.g.:\n// \"GET\", \"POST\", \"PUT\".\nfunc (r *Route) Methods(methods ...string) *Route {\n\tfor k, v := range methods {\n\t\tmethods[k] = strings.ToUpper(v)\n\t}\n\treturn r.addMatcher(methodMatcher(methods))\n}\n\n// Path -----------------------------------------------------------------------\n\n// Path adds a matcher for the URL path.\n// It accepts a template with zero or more URL variables enclosed by {}. The\n// template must start with a \"/\".\n// Variables can define an optional regexp pattern to be matched:\n//\n// - {name} matches anything until the next slash.\n//\n// - {name:pattern} matches the given regexp pattern.\n//\n// For example:\n//\n//\tr := mux.NewRouter().NewRoute()\n//\tr.Path(\"/products/\").Handler(ProductsHandler)\n//\tr.Path(\"/products/{key}\").Handler(ProductsHandler)\n//\tr.Path(\"/articles/{category}/{id:[0-9]+}\").\n//\t  Handler(ArticleHandler)\n//\n// Variable names must be unique in a given route. They can be retrieved\n// calling mux.Vars(request).\nfunc (r *Route) Path(tpl string) *Route {\n\tr.err = r.addRegexpMatcher(tpl, regexpTypePath)\n\treturn r\n}\n\n// PathPrefix -----------------------------------------------------------------\n\n// PathPrefix adds a matcher for the URL path prefix. This matches if the given\n// template is a prefix of the full URL path. See Route.Path() for details on\n// the tpl argument.\n//\n// Note that it does not treat slashes specially (\"/foobar/\" will be matched by\n// the prefix \"/foo\") so you may want to use a trailing slash here.\n//\n// Also note that the setting of Router.StrictSlash() has no effect on routes\n// with a PathPrefix matcher.\nfunc (r *Route) PathPrefix(tpl string) *Route {\n\tr.err = r.addRegexpMatcher(tpl, regexpTypePrefix)\n\treturn r\n}\n\n// Query ----------------------------------------------------------------------\n\n// Queries adds a matcher for URL query values.\n// It accepts a sequence of key/value pairs. Values may define variables.\n// For example:\n//\n//\tr := mux.NewRouter().NewRoute()\n//\tr.Queries(\"foo\", \"bar\", \"id\", \"{id:[0-9]+}\")\n//\n// The above route will only match if the URL contains the defined queries\n// values, e.g.: ?foo=bar&id=42.\n//\n// If the value is an empty string, it will match any value if the key is set.\n//\n// Variables can define an optional regexp pattern to be matched:\n//\n// - {name} matches anything until the next slash.\n//\n// - {name:pattern} matches the given regexp pattern.\nfunc (r *Route) Queries(pairs ...string) *Route {\n\tlength := len(pairs)\n\tif length%2 != 0 {\n\t\tr.err = fmt.Errorf(\n\t\t\t\"mux: number of parameters must be multiple of 2, got %v\", pairs)\n\t\treturn nil\n\t}\n\tfor i := 0; i < length; i += 2 {\n\t\tif r.err = r.addRegexpMatcher(pairs[i]+\"=\"+pairs[i+1], regexpTypeQuery); r.err != nil {\n\t\t\treturn r\n\t\t}\n\t}\n\n\treturn r\n}\n\n// Schemes --------------------------------------------------------------------\n\n// schemeMatcher matches the request against URL schemes.\ntype schemeMatcher []string\n\nfunc (m schemeMatcher) Match(r *http.Request, match *RouteMatch) bool {\n\tscheme := r.URL.Scheme\n\t// https://golang.org/pkg/net/http/#Request\n\t// \"For [most] server requests, fields other than Path and RawQuery will be\n\t// empty.\"\n\t// Since we're an http muxer, the scheme is either going to be http or https\n\t// though, so we can just set it based on the tls termination state.\n\tif scheme == \"\" {\n\t\tif r.TLS == nil {\n\t\t\tscheme = \"http\"\n\t\t} else {\n\t\t\tscheme = \"https\"\n\t\t}\n\t}\n\treturn matchInArray(m, scheme)\n}\n\n// Schemes adds a matcher for URL schemes.\n// It accepts a sequence of schemes to be matched, e.g.: \"http\", \"https\".\n// If the request's URL has a scheme set, it will be matched against.\n// Generally, the URL scheme will only be set if a previous handler set it,\n// such as the ProxyHeaders handler from gorilla/handlers.\n// If unset, the scheme will be determined based on the request's TLS\n// termination state.\n// The first argument to Schemes will be used when constructing a route URL.\nfunc (r *Route) Schemes(schemes ...string) *Route {\n\tfor k, v := range schemes {\n\t\tschemes[k] = strings.ToLower(v)\n\t}\n\tif len(schemes) > 0 {\n\t\tr.buildScheme = schemes[0]\n\t}\n\treturn r.addMatcher(schemeMatcher(schemes))\n}\n\n// BuildVarsFunc --------------------------------------------------------------\n\n// BuildVarsFunc is the function signature used by custom build variable\n// functions (which can modify route variables before a route's URL is built).\ntype BuildVarsFunc func(map[string]string) map[string]string\n\n// BuildVarsFunc adds a custom function to be used to modify build variables\n// before a route's URL is built.\nfunc (r *Route) BuildVarsFunc(f BuildVarsFunc) *Route {\n\tif r.buildVarsFunc != nil {\n\t\t// compose the old and new functions\n\t\told := r.buildVarsFunc\n\t\tr.buildVarsFunc = func(m map[string]string) map[string]string {\n\t\t\treturn f(old(m))\n\t\t}\n\t} else {\n\t\tr.buildVarsFunc = f\n\t}\n\treturn r\n}\n\n// Subrouter ------------------------------------------------------------------\n\n// Subrouter creates a subrouter for the route.\n//\n// It will test the inner routes only if the parent route matched. For example:\n//\n//\tr := mux.NewRouter().NewRoute()\n//\ts := r.Host(\"www.example.com\").Subrouter()\n//\ts.HandleFunc(\"/products/\", ProductsHandler)\n//\ts.HandleFunc(\"/products/{key}\", ProductHandler)\n//\ts.HandleFunc(\"/articles/{category}/{id:[0-9]+}\"), ArticleHandler)\n//\n// Here, the routes registered in the subrouter won't be tested if the host\n// doesn't match.\nfunc (r *Route) Subrouter() *Router {\n\t// initialize a subrouter with a copy of the parent route's configuration\n\trouter := &Router{routeConf: copyRouteConf(r.routeConf), namedRoutes: r.namedRoutes}\n\tr.addMatcher(router)\n\treturn router\n}\n\n// ----------------------------------------------------------------------------\n// URL building\n// ----------------------------------------------------------------------------\n\n// URL builds a URL for the route.\n//\n// It accepts a sequence of key/value pairs for the route variables. For\n// example, given this route:\n//\n//\tr := mux.NewRouter()\n//\tr.HandleFunc(\"/articles/{category}/{id:[0-9]+}\", ArticleHandler).\n//\t  Name(\"article\")\n//\n// ...a URL for it can be built using:\n//\n//\turl, err := r.Get(\"article\").URL(\"category\", \"technology\", \"id\", \"42\")\n//\n// ...which will return an url.URL with the following path:\n//\n//\t\"/articles/technology/42\"\n//\n// This also works for host variables:\n//\n//\tr := mux.NewRouter()\n//\tr.HandleFunc(\"/articles/{category}/{id:[0-9]+}\", ArticleHandler).\n//\t  Host(\"{subdomain}.domain.com\").\n//\t  Name(\"article\")\n//\n//\t// url.String() will be \"http://news.domain.com/articles/technology/42\"\n//\turl, err := r.Get(\"article\").URL(\"subdomain\", \"news\",\n//\t                                 \"category\", \"technology\",\n//\t                                 \"id\", \"42\")\n//\n// The scheme of the resulting url will be the first argument that was passed to Schemes:\n//\n//\t// url.String() will be \"https://example.com\"\n//\tr := mux.NewRouter().NewRoute()\n//\turl, err := r.Host(\"example.com\")\n//\t             .Schemes(\"https\", \"http\").URL()\n//\n// All variables defined in the route are required, and their values must\n// conform to the corresponding patterns.\nfunc (r *Route) URL(pairs ...string) (*url.URL, error) {\n\tif r.err != nil {\n\t\treturn nil, r.err\n\t}\n\tvalues, err := r.prepareVars(pairs...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar scheme, host, path string\n\tqueries := make([]string, 0, len(r.regexp.queries))\n\tif r.regexp.host != nil {\n\t\tif host, err = r.regexp.host.url(values); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tscheme = \"http\"\n\t\tif r.buildScheme != \"\" {\n\t\t\tscheme = r.buildScheme\n\t\t}\n\t}\n\tif r.regexp.path != nil {\n\t\tif path, err = r.regexp.path.url(values); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tfor _, q := range r.regexp.queries {\n\t\tvar query string\n\t\tif query, err = q.url(values); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tqueries = append(queries, query)\n\t}\n\treturn &url.URL{\n\t\tScheme:   scheme,\n\t\tHost:     host,\n\t\tPath:     path,\n\t\tRawQuery: strings.Join(queries, \"&\"),\n\t}, nil\n}\n\n// URLHost builds the host part of the URL for a route. See Route.URL().\n//\n// The route must have a host defined.\nfunc (r *Route) URLHost(pairs ...string) (*url.URL, error) {\n\tif r.err != nil {\n\t\treturn nil, r.err\n\t}\n\tif r.regexp.host == nil {\n\t\treturn nil, errors.New(\"mux: route doesn't have a host\")\n\t}\n\tvalues, err := r.prepareVars(pairs...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\thost, err := r.regexp.host.url(values)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tu := &url.URL{\n\t\tScheme: \"http\",\n\t\tHost:   host,\n\t}\n\tif r.buildScheme != \"\" {\n\t\tu.Scheme = r.buildScheme\n\t}\n\treturn u, nil\n}\n\n// URLPath builds the path part of the URL for a route. See Route.URL().\n//\n// The route must have a path defined.\nfunc (r *Route) URLPath(pairs ...string) (*url.URL, error) {\n\tif r.err != nil {\n\t\treturn nil, r.err\n\t}\n\tif r.regexp.path == nil {\n\t\treturn nil, errors.New(\"mux: route doesn't have a path\")\n\t}\n\tvalues, err := r.prepareVars(pairs...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tpath, err := r.regexp.path.url(values)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &url.URL{\n\t\tPath: path,\n\t}, nil\n}\n\n// GetPathTemplate returns the template used to build the\n// route match.\n// This is useful for building simple REST API documentation and for instrumentation\n// against third-party services.\n// An error will be returned if the route does not define a path.\nfunc (r *Route) GetPathTemplate() (string, error) {\n\tif r.err != nil {\n\t\treturn \"\", r.err\n\t}\n\tif r.regexp.path == nil {\n\t\treturn \"\", errors.New(\"mux: route doesn't have a path\")\n\t}\n\treturn r.regexp.path.template, nil\n}\n\n// GetPathRegexp returns the expanded regular expression used to match route path.\n// This is useful for building simple REST API documentation and for instrumentation\n// against third-party services.\n// An error will be returned if the route does not define a path.\nfunc (r *Route) GetPathRegexp() (string, error) {\n\tif r.err != nil {\n\t\treturn \"\", r.err\n\t}\n\tif r.regexp.path == nil {\n\t\treturn \"\", errors.New(\"mux: route does not have a path\")\n\t}\n\treturn r.regexp.path.regexp.String(), nil\n}\n\n// GetQueriesRegexp returns the expanded regular expressions used to match the\n// route queries.\n// This is useful for building simple REST API documentation and for instrumentation\n// against third-party services.\n// An error will be returned if the route does not have queries.\nfunc (r *Route) GetQueriesRegexp() ([]string, error) {\n\tif r.err != nil {\n\t\treturn nil, r.err\n\t}\n\tif r.regexp.queries == nil {\n\t\treturn nil, errors.New(\"mux: route doesn't have queries\")\n\t}\n\tqueries := make([]string, 0, len(r.regexp.queries))\n\tfor _, query := range r.regexp.queries {\n\t\tqueries = append(queries, query.regexp.String())\n\t}\n\treturn queries, nil\n}\n\n// GetQueriesTemplates returns the templates used to build the\n// query matching.\n// This is useful for building simple REST API documentation and for instrumentation\n// against third-party services.\n// An error will be returned if the route does not define queries.\nfunc (r *Route) GetQueriesTemplates() ([]string, error) {\n\tif r.err != nil {\n\t\treturn nil, r.err\n\t}\n\tif r.regexp.queries == nil {\n\t\treturn nil, errors.New(\"mux: route doesn't have queries\")\n\t}\n\tqueries := make([]string, 0, len(r.regexp.queries))\n\tfor _, query := range r.regexp.queries {\n\t\tqueries = append(queries, query.template)\n\t}\n\treturn queries, nil\n}\n\n// GetMethods returns the methods the route matches against\n// This is useful for building simple REST API documentation and for instrumentation\n// against third-party services.\n// An error will be returned if route does not have methods.\nfunc (r *Route) GetMethods() ([]string, error) {\n\tif r.err != nil {\n\t\treturn nil, r.err\n\t}\n\tfor _, m := range r.matchers {\n\t\tif methods, ok := m.(methodMatcher); ok {\n\t\t\treturn []string(methods), nil\n\t\t}\n\t}\n\treturn nil, errors.New(\"mux: route doesn't have methods\")\n}\n\n// GetHostTemplate returns the template used to build the\n// route match.\n// This is useful for building simple REST API documentation and for instrumentation\n// against third-party services.\n// An error will be returned if the route does not define a host.\nfunc (r *Route) GetHostTemplate() (string, error) {\n\tif r.err != nil {\n\t\treturn \"\", r.err\n\t}\n\tif r.regexp.host == nil {\n\t\treturn \"\", errors.New(\"mux: route doesn't have a host\")\n\t}\n\treturn r.regexp.host.template, nil\n}\n\n// GetVarNames returns the names of all variables added by regexp matchers\n// These can be used to know which route variables should be passed into r.URL()\nfunc (r *Route) GetVarNames() ([]string, error) {\n\tif r.err != nil {\n\t\treturn nil, r.err\n\t}\n\tvar varNames []string\n\tif r.regexp.host != nil {\n\t\tvarNames = append(varNames, r.regexp.host.varsN...)\n\t}\n\tif r.regexp.path != nil {\n\t\tvarNames = append(varNames, r.regexp.path.varsN...)\n\t}\n\tfor _, regx := range r.regexp.queries {\n\t\tvarNames = append(varNames, regx.varsN...)\n\t}\n\treturn varNames, nil\n}\n\n// prepareVars converts the route variable pairs into a map. If the route has a\n// BuildVarsFunc, it is invoked.\nfunc (r *Route) prepareVars(pairs ...string) (map[string]string, error) {\n\tm, err := mapFromPairsToString(pairs...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn r.buildVars(m), nil\n}\n\nfunc (r *Route) buildVars(m map[string]string) map[string]string {\n\tif r.buildVarsFunc != nil {\n\t\tm = r.buildVarsFunc(m)\n\t}\n\treturn m\n}\n"
  },
  {
    "path": "route_test.go",
    "content": "package mux\n\nimport (\n\t\"errors\"\n\t\"net/http\"\n\t\"reflect\"\n\t\"regexp\"\n\t\"sync\"\n\t\"testing\"\n)\n\nvar testNewRouterMu sync.Mutex\nvar testHandler = http.NotFoundHandler()\n\nfunc BenchmarkNewRouter(b *testing.B) {\n\ttestNewRouterMu.Lock()\n\tdefer testNewRouterMu.Unlock()\n\n\t// Set the RegexpCompileFunc to the default regexp.Compile.\n\tRegexpCompileFunc = regexp.Compile\n\n\tb.ReportAllocs()\n\tb.ResetTimer()\n\n\tfor i := 0; i < b.N; i++ {\n\t\ttestNewRouter(b, testHandler)\n\t}\n}\n\nfunc BenchmarkNewRouterRegexpFunc(b *testing.B) {\n\ttestNewRouterMu.Lock()\n\tdefer testNewRouterMu.Unlock()\n\n\t// We preallocate the size to 8.\n\tcache := make(map[string]*regexp.Regexp, 8)\n\n\t// Override the RegexpCompileFunc to reuse compiled expressions\n\t// from the `cache` map. Real world caches should have eviction\n\t// policies or some sort of approach to limit memory use.\n\tRegexpCompileFunc = func(expr string) (*regexp.Regexp, error) {\n\t\tif regex, ok := cache[expr]; ok {\n\t\t\treturn regex, nil\n\t\t}\n\n\t\tregex, err := regexp.Compile(expr)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tcache[expr] = regex\n\t\treturn regex, nil\n\t}\n\n\tb.ReportAllocs()\n\tb.ResetTimer()\n\n\tfor i := 0; i < b.N; i++ {\n\t\ttestNewRouter(b, testHandler)\n\t}\n}\n\nfunc testNewRouter(_ testing.TB, handler http.Handler) {\n\tr := NewRouter()\n\t// A route with a route variable:\n\tr.Handle(\"/metrics/{type}\", handler)\n\tr.Queries(\"orgID\", \"{orgID:[0-9]*?}\")\n\tr.Host(\"{subdomain}.domain.com\")\n}\n\nfunc TestRouteMetadata(t *testing.T) {\n\trouter := NewRouter()\n\trw := NewRecorder()\n\n\texpectedMap := make(map[any]any)\n\texpectedMap[\"key\"] = \"value\"\n\n\trouter.HandleFunc(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\troute := CurrentRoute(r)\n\t\tmetadata := route.GetMetadata()\n\n\t\tif !reflect.DeepEqual(metadata, expectedMap) {\n\t\t\tprintln(metadata)\n\t\t\tt.Fatalf(\"Expected map does not equal the metadata map\")\n\t\t}\n\n\t}).Metadata(\"key\", \"value\")\n\n\trouter.HandleFunc(\"/single-value\", func(w http.ResponseWriter, r *http.Request) {\n\t\troute := CurrentRoute(r)\n\t\tvalue, err := route.GetMetadataValue(\"key\")\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Expected metadata value to be present, but gave error: %s\", err)\n\t\t}\n\n\t\tstringValue, ok := value.(string)\n\t\tif !ok {\n\t\t\tt.Fatalf(\"Expected metadata value to be string, but was: %s\", reflect.TypeOf(value))\n\t\t}\n\n\t\tif stringValue != \"value\" {\n\t\t\tt.Fatalf(\"Expected metadata value to be '%s', but got '%s'\", \"value\", stringValue)\n\t\t}\n\n\t\t_, err = route.GetMetadataValue(\"key2\")\n\t\tif err == nil {\n\t\t\tt.Fatalf(\"Expected metadata key not to be present and error, but error was nil\")\n\t\t}\n\n\t\tif !errors.Is(err, ErrMetadataKeyNotFound) {\n\t\t\tt.Fatalf(\"Expected error to be ErrMetadataKeyNotFound but got: %s\", err)\n\t\t}\n\n\t}).Metadata(\"key\", \"value\")\n\n\trouter.HandleFunc(\"/single-value-fallback\", func(w http.ResponseWriter, r *http.Request) {\n\t\troute := CurrentRoute(r)\n\t\tvalue := route.GetMetadataValueOr(\"key\", \"value-fallback\")\n\n\t\tstringValue, ok := value.(string)\n\t\tif !ok {\n\t\t\tt.Fatalf(\"Expected metadata value to be string, but was: %s\", reflect.TypeOf(value))\n\t\t}\n\n\t\tif stringValue != \"value\" {\n\t\t\tt.Fatalf(\"Expected metadata value to be '%s', but got '%s'\", \"value\", stringValue)\n\t\t}\n\n\t\tfallbackValue := route.GetMetadataValueOr(\"key2\", \"value2\")\n\t\tfallbackStringValue, ok := fallbackValue.(string)\n\t\tif !ok {\n\t\t\tt.Fatalf(\"Expected metadata value to be string, but was: %s\", reflect.TypeOf(value))\n\t\t}\n\n\t\tif fallbackStringValue != \"value2\" {\n\t\t\tt.Fatalf(\"Expected metadata value to be '%s', but got '%s'\", \"value2\", fallbackStringValue)\n\t\t}\n\n\t}).Metadata(\"key\", \"value\")\n\n\tt.Run(\"get metadata map\", func(t *testing.T) {\n\t\treq := newRequest(\"GET\", \"/\")\n\t\trouter.ServeHTTP(rw, req)\n\t})\n\n\tt.Run(\"get metadata value\", func(t *testing.T) {\n\t\treq := newRequest(\"GET\", \"/single-value\")\n\t\trouter.ServeHTTP(rw, req)\n\t})\n\n\tt.Run(\"get metadata value or fallback\", func(t *testing.T) {\n\t\treq := newRequest(\"GET\", \"/single-value-fallback\")\n\t\trouter.ServeHTTP(rw, req)\n\t})\n}\n"
  },
  {
    "path": "test_helpers.go",
    "content": "// Copyright 2012 The Gorilla Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\npackage mux\n\nimport \"net/http\"\n\n// SetURLVars sets the URL variables for the given request, to be accessed via\n// mux.Vars for testing route behaviour. Arguments are not modified, a shallow\n// copy is returned.\n//\n// This API should only be used for testing purposes; it provides a way to\n// inject variables into the request context. Alternatively, URL variables\n// can be set by making a route that captures the required variables,\n// starting a server and sending the request to that server.\nfunc SetURLVars(r *http.Request, val map[string]string) *http.Request {\n\treturn requestWithVars(r, val)\n}\n"
  }
]