Full Code of gorilla/mux for AI

main db9d1d0073d2 cached
27 files
249.5 KB
71.1k tokens
267 symbols
1 requests
Download .txt
Showing preview only (260K chars total). Download the full file or copy to clipboard to get everything.
Repository: gorilla/mux
Branch: main
Commit: db9d1d0073d2
Files: 27
Total size: 249.5 KB

Directory structure:
gitextract_gscv0_ce/

├── .editorconfig
├── .github/
│   └── workflows/
│       ├── issues.yml
│       ├── security.yml
│       ├── test.yml
│       └── verify.yml
├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── bench_test.go
├── doc.go
├── example_authentication_middleware_test.go
├── example_cors_method_middleware_test.go
├── example_route_test.go
├── example_route_vars_test.go
├── go.mod
├── middleware.go
├── middleware_test.go
├── mux.go
├── mux_httpserver_test.go
├── mux_test.go
├── old_test.go
├── regexp.go
├── regexp_test.go
├── route.go
├── route_test.go
└── test_helpers.go

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
; https://editorconfig.org/

root = true

[*]
insert_final_newline = true
charset = utf-8
trim_trailing_whitespace = true
indent_style = space
indent_size = 2

[{Makefile,go.mod,go.sum,*.go,.gitmodules}]
indent_style = tab
indent_size = 4

[*.md]
indent_size = 4
trim_trailing_whitespace = false

eclint_indent_style = unset

================================================
FILE: .github/workflows/issues.yml
================================================
# Add all the issues created to the project.
name: Add issue or pull request to Project

on:
  issues:
    types:
      - opened
  pull_request_target:
    types:
      - opened
      - reopened

jobs:
  add-to-project:
    runs-on: ubuntu-latest
    steps:
      - name: Add issue to project
        uses: actions/add-to-project@v0.5.0
        with:
          project-url: https://github.com/orgs/gorilla/projects/4
          github-token: ${{ secrets.ADD_TO_PROJECT_TOKEN }}


================================================
FILE: .github/workflows/security.yml
================================================
name: Security
on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main
permissions:
  contents: read
jobs:
  scan:
    strategy:
      matrix:
        go: ['1.20','1.21']
      fail-fast: true
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Code
        uses: actions/checkout@v3

      - name: Setup Go ${{ matrix.go }}
        uses: actions/setup-go@v4
        with:
          go-version: ${{ matrix.go }}
          cache: false

      - name: Run GoSec
        uses: securego/gosec@master
        with:
          args: -exclude-dir examples ./...

      - name: Run GoVulnCheck
        uses: golang/govulncheck-action@v1
        with:
          go-version-input: ${{ matrix.go }}
          go-package: ./...


================================================
FILE: .github/workflows/test.yml
================================================
name: Test
on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main
permissions:
  contents: read
jobs:
  unit:
    strategy:
      matrix:
        go: ['1.20','1.21']
        os: [ubuntu-latest, macos-latest, windows-latest]
      fail-fast: true
    runs-on: ${{ matrix.os }}
    steps:
      - name: Checkout Code
        uses: actions/checkout@v3

      - name: Setup Go ${{ matrix.go }}
        uses: actions/setup-go@v4
        with:
          go-version: ${{ matrix.go }}
          cache: false

      - name: Run Tests
        run: go test -race -cover -coverprofile=coverage -covermode=atomic -v ./...

      - name: Upload coverage to Codecov
        uses: codecov/codecov-action@v3
        with:
          files: ./coverage


================================================
FILE: .github/workflows/verify.yml
================================================
name: Verify
on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main
permissions:
  contents: read
jobs:
  lint:
    strategy:
      matrix:
        go: ['1.20','1.21']
      fail-fast: true
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Code
        uses: actions/checkout@v3

      - name: Setup Go ${{ matrix.go }}
        uses: actions/setup-go@v4
        with:
          go-version: ${{ matrix.go }}
          cache: false

      - name: Run GolangCI-Lint
        uses: golangci/golangci-lint-action@v3
        with:
          version: v1.53
          args: --timeout=5m


================================================
FILE: .gitignore
================================================
coverage.coverprofile


================================================
FILE: LICENSE
================================================
Copyright (c) 2023 The Gorilla Authors. All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

	 * Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
	 * Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
	 * Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


================================================
FILE: Makefile
================================================
GO_LINT=$(shell which golangci-lint 2> /dev/null || echo '')
GO_LINT_URI=github.com/golangci/golangci-lint/cmd/golangci-lint@latest

GO_SEC=$(shell which gosec 2> /dev/null || echo '')
GO_SEC_URI=github.com/securego/gosec/v2/cmd/gosec@latest

GO_VULNCHECK=$(shell which govulncheck 2> /dev/null || echo '')
GO_VULNCHECK_URI=golang.org/x/vuln/cmd/govulncheck@latest

.PHONY: golangci-lint
golangci-lint:
	$(if $(GO_LINT), ,go install $(GO_LINT_URI))
	@echo "##### Running golangci-lint"
	golangci-lint run -v
	
.PHONY: gosec
gosec:
	$(if $(GO_SEC), ,go install $(GO_SEC_URI))
	@echo "##### Running gosec"
	gosec ./...

.PHONY: govulncheck
govulncheck:
	$(if $(GO_VULNCHECK), ,go install $(GO_VULNCHECK_URI))
	@echo "##### Running govulncheck"
	govulncheck ./...

.PHONY: verify
verify: golangci-lint gosec govulncheck

.PHONY: test
test:
	@echo "##### Running tests"
	go test -race -cover -coverprofile=coverage.coverprofile -covermode=atomic -v ./...

================================================
FILE: README.md
================================================
# gorilla/mux

![testing](https://github.com/gorilla/mux/actions/workflows/test.yml/badge.svg)
[![codecov](https://codecov.io/github/gorilla/mux/branch/main/graph/badge.svg)](https://codecov.io/github/gorilla/mux)
[![godoc](https://godoc.org/github.com/gorilla/mux?status.svg)](https://godoc.org/github.com/gorilla/mux)
[![sourcegraph](https://sourcegraph.com/github.com/gorilla/mux/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/mux?badge)


![Gorilla Logo](https://github.com/gorilla/.github/assets/53367916/d92caabf-98e0-473e-bfbf-ab554ba435e5)

Package `gorilla/mux` implements a request router and dispatcher for matching incoming requests to
their respective handler.

The name mux stands for "HTTP request multiplexer". Like the standard `http.ServeMux`, `mux.Router` matches incoming requests against a list of registered routes and calls a handler for the route that matches the URL or other conditions. The main features are:

* It implements the `http.Handler` interface so it is compatible with the standard `http.ServeMux`.
* Requests can be matched based on URL host, path, path prefix, schemes, header and query values, HTTP methods or using custom matchers.
* URL hosts, paths and query values can have variables with an optional regular expression.
* Registered URLs can be built, or "reversed", which helps maintaining references to resources.
* Routes can be used as subrouters: nested routes are only tested if the parent route matches. This is useful to define groups of routes that share common conditions like a host, a path prefix or other repeated attributes. As a bonus, this optimizes request matching.

---

* [Install](#install)
* [Examples](#examples)
* [Matching Routes](#matching-routes)
* [Static Files](#static-files)
* [Serving Single Page Applications](#serving-single-page-applications) (e.g. React, Vue, Ember.js, etc.)
* [Registered URLs](#registered-urls)
* [Walking Routes](#walking-routes)
* [Graceful Shutdown](#graceful-shutdown)
* [Middleware](#middleware)
* [Handling CORS Requests](#handling-cors-requests)
* [Testing Handlers](#testing-handlers)
* [Full Example](#full-example)

---

## Install

With a [correctly configured](https://golang.org/doc/install#testing) Go toolchain:

```sh
go get -u github.com/gorilla/mux
```

## Examples

Let's start registering a couple of URL paths and handlers:

```go
func main() {
    r := mux.NewRouter()
    r.HandleFunc("/", HomeHandler)
    r.HandleFunc("/products", ProductsHandler)
    r.HandleFunc("/articles", ArticlesHandler)
    http.Handle("/", r)
}
```

Here we register three routes mapping URL paths to handlers. This is equivalent to how `http.HandleFunc()` works: if an incoming request URL matches one of the paths, the corresponding handler is called passing (`http.ResponseWriter`, `*http.Request`) as parameters.

Paths can have variables. They are defined using the format `{name}` or `{name:pattern}`. If a regular expression pattern is not defined, the matched variable will be anything until the next slash. For example:

```go
r := mux.NewRouter()
r.HandleFunc("/products/{key}", ProductHandler)
r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler)
r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
```

The names are used to create a map of route variables which can be retrieved calling `mux.Vars()`:

```go
func ArticlesCategoryHandler(w http.ResponseWriter, r *http.Request) {
    vars := mux.Vars(r)
    w.WriteHeader(http.StatusOK)
    fmt.Fprintf(w, "Category: %v\n", vars["category"])
}
```

And this is all you need to know about the basic usage. More advanced options are explained below.

### Matching Routes

Routes can also be restricted to a domain or subdomain. Just define a host pattern to be matched. They can also have variables:

```go
r := mux.NewRouter()
// Only matches if domain is "www.example.com".
r.Host("www.example.com")
// Matches a dynamic subdomain.
r.Host("{subdomain:[a-z]+}.example.com")
```

There are several other matchers that can be added. To match path prefixes:

```go
r.PathPrefix("/products/")
```

...or HTTP methods:

```go
r.Methods("GET", "POST")
```

...or URL schemes:

```go
r.Schemes("https")
```

...or header values:

```go
r.Headers("X-Requested-With", "XMLHttpRequest")
```

...or query values:

```go
r.Queries("key", "value")
```

...or to use a custom matcher function:

```go
r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool {
    return r.ProtoMajor == 0
})
```

...and finally, it is possible to combine several matchers in a single route:

```go
r.HandleFunc("/products", ProductsHandler).
  Host("www.example.com").
  Methods("GET").
  Schemes("http")
```

Routes are tested in the order they were added to the router. If two routes match, the first one wins:

```go
r := mux.NewRouter()
r.HandleFunc("/specific", specificHandler)
r.PathPrefix("/").Handler(catchAllHandler)
```

Setting the same matching conditions again and again can be boring, so we have a way to group several routes that share the same requirements. We call it "subrouting".

For example, let's say we have several URLs that should only match when the host is `www.example.com`. Create a route for that host and get a "subrouter" from it:

```go
r := mux.NewRouter()
s := r.Host("www.example.com").Subrouter()
```

Then register routes in the subrouter:

```go
s.HandleFunc("/products/", ProductsHandler)
s.HandleFunc("/products/{key}", ProductHandler)
s.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
```

The three URL paths we registered above will only be tested if the domain is `www.example.com`, because the subrouter is tested first. This is not only convenient, but also optimizes request matching. You can create subrouters combining any attribute matchers accepted by a route.

Subrouters can be used to create domain or path "namespaces": you define subrouters in a central place and then parts of the app can register its paths relatively to a given subrouter.

There's one more thing about subroutes. When a subrouter has a path prefix, the inner routes use it as base for their paths:

```go
r := mux.NewRouter()
s := r.PathPrefix("/products").Subrouter()
// "/products/"
s.HandleFunc("/", ProductsHandler)
// "/products/{key}/"
s.HandleFunc("/{key}/", ProductHandler)
// "/products/{key}/details"
s.HandleFunc("/{key}/details", ProductDetailsHandler)
```


### Static Files

Note that the path provided to `PathPrefix()` represents a "wildcard": calling
`PathPrefix("/static/").Handler(...)` means that the handler will be passed any
request that matches "/static/\*". This makes it easy to serve static files with mux:

```go
func main() {
    var dir string

    flag.StringVar(&dir, "dir", ".", "the directory to serve files from. Defaults to the current dir")
    flag.Parse()
    r := mux.NewRouter()

    // This will serve files under http://localhost:8000/static/<filename>
    r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(dir))))

    srv := &http.Server{
        Handler:      r,
        Addr:         "127.0.0.1:8000",
        // Good practice: enforce timeouts for servers you create!
        WriteTimeout: 15 * time.Second,
        ReadTimeout:  15 * time.Second,
    }

    log.Fatal(srv.ListenAndServe())
}
```

### Serving Single Page Applications

Most of the time it makes sense to serve your SPA on a separate web server from your API,
but sometimes it's desirable to serve them both from one place. It's possible to write a simple
handler for serving your SPA (for use with React Router's [BrowserRouter](https://reacttraining.com/react-router/web/api/BrowserRouter) for example), and leverage
mux's powerful routing for your API endpoints.

```go
package main

import (
	"encoding/json"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"time"

	"github.com/gorilla/mux"
)

// spaHandler implements the http.Handler interface, so we can use it
// to respond to HTTP requests. The path to the static directory and
// path to the index file within that static directory are used to
// serve the SPA in the given static directory.
type spaHandler struct {
	staticPath string
	indexPath  string
}

// ServeHTTP inspects the URL path to locate a file within the static dir
// on the SPA handler. If a file is found, it will be served. If not, the
// file located at the index path on the SPA handler will be served. This
// is suitable behavior for serving an SPA (single page application).
func (h spaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// Join internally call path.Clean to prevent directory traversal
	path := filepath.Join(h.staticPath, r.URL.Path)

	// check whether a file exists or is a directory at the given path
	fi, err := os.Stat(path)
	if os.IsNotExist(err) || fi.IsDir() {
		// file does not exist or path is a directory, serve index.html
		http.ServeFile(w, r, filepath.Join(h.staticPath, h.indexPath))
		return
	}

	if err != nil {
		// if we got an error (that wasn't that the file doesn't exist) stating the
		// file, return a 500 internal server error and stop
		http.Error(w, err.Error(), http.StatusInternalServerError)
        return
	}

	// otherwise, use http.FileServer to serve the static file
	http.FileServer(http.Dir(h.staticPath)).ServeHTTP(w, r)
}

func main() {
	router := mux.NewRouter()

	router.HandleFunc("/api/health", func(w http.ResponseWriter, r *http.Request) {
		// an example API handler
		json.NewEncoder(w).Encode(map[string]bool{"ok": true})
	})

	spa := spaHandler{staticPath: "build", indexPath: "index.html"}
	router.PathPrefix("/").Handler(spa)

	srv := &http.Server{
		Handler: router,
		Addr:    "127.0.0.1:8000",
		// Good practice: enforce timeouts for servers you create!
		WriteTimeout: 15 * time.Second,
		ReadTimeout:  15 * time.Second,
	}

	log.Fatal(srv.ListenAndServe())
}
```

### Registered URLs

Now let's see how to build registered URLs.

Routes can be named. All routes that define a name can have their URLs built, or "reversed". We define a name calling `Name()` on a route. For example:

```go
r := mux.NewRouter()
r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
  Name("article")
```

To build a URL, get the route and call the `URL()` method, passing a sequence of key/value pairs for the route variables. For the previous route, we would do:

```go
url, err := r.Get("article").URL("category", "technology", "id", "42")
```

...and the result will be a `url.URL` with the following path:

```
"/articles/technology/42"
```

This also works for host and query value variables:

```go
r := mux.NewRouter()
r.Host("{subdomain}.example.com").
  Path("/articles/{category}/{id:[0-9]+}").
  Queries("filter", "{filter}").
  HandlerFunc(ArticleHandler).
  Name("article")

// url.String() will be "http://news.example.com/articles/technology/42?filter=gorilla"
url, err := r.Get("article").URL("subdomain", "news",
                                 "category", "technology",
                                 "id", "42",
                                 "filter", "gorilla")
```

All variables defined in the route are required, and their values must conform to the corresponding patterns. These requirements guarantee that a generated URL will always match a registered route -- the only exception is for explicitly defined "build-only" routes which never match.

Regex support also exists for matching Headers within a route. For example, we could do:

```go
r.HeadersRegexp("Content-Type", "application/(text|json)")
```

...and the route will match both requests with a Content-Type of `application/json` as well as `application/text`

There's also a way to build only the URL host or path for a route: use the methods `URLHost()` or `URLPath()` instead. For the previous route, we would do:

```go
// "http://news.example.com/"
host, err := r.Get("article").URLHost("subdomain", "news")

// "/articles/technology/42"
path, err := r.Get("article").URLPath("category", "technology", "id", "42")
```

And if you use subrouters, host and path defined separately can be built as well:

```go
r := mux.NewRouter()
s := r.Host("{subdomain}.example.com").Subrouter()
s.Path("/articles/{category}/{id:[0-9]+}").
  HandlerFunc(ArticleHandler).
  Name("article")

// "http://news.example.com/articles/technology/42"
url, err := r.Get("article").URL("subdomain", "news",
                                 "category", "technology",
                                 "id", "42")
```

To find all the required variables for a given route when calling `URL()`, the method `GetVarNames()` is available:
```go
r := mux.NewRouter()
r.Host("{domain}").
    Path("/{group}/{item_id}").
    Queries("some_data1", "{some_data1}").
    Queries("some_data2", "{some_data2}").
    Name("article")

// Will print [domain group item_id some_data1 some_data2] <nil>
fmt.Println(r.Get("article").GetVarNames())

```
### Walking Routes

The `Walk` function on `mux.Router` can be used to visit all of the routes that are registered on a router. For example,
the following prints all of the registered routes:

```go
package main

import (
	"fmt"
	"net/http"
	"strings"

	"github.com/gorilla/mux"
)

func handler(w http.ResponseWriter, r *http.Request) {
	return
}

func main() {
	r := mux.NewRouter()
	r.HandleFunc("/", handler)
	r.HandleFunc("/products", handler).Methods("POST")
	r.HandleFunc("/articles", handler).Methods("GET")
	r.HandleFunc("/articles/{id}", handler).Methods("GET", "PUT")
	r.HandleFunc("/authors", handler).Queries("surname", "{surname}")
	err := r.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
		pathTemplate, err := route.GetPathTemplate()
		if err == nil {
			fmt.Println("ROUTE:", pathTemplate)
		}
		pathRegexp, err := route.GetPathRegexp()
		if err == nil {
			fmt.Println("Path regexp:", pathRegexp)
		}
		queriesTemplates, err := route.GetQueriesTemplates()
		if err == nil {
			fmt.Println("Queries templates:", strings.Join(queriesTemplates, ","))
		}
		queriesRegexps, err := route.GetQueriesRegexp()
		if err == nil {
			fmt.Println("Queries regexps:", strings.Join(queriesRegexps, ","))
		}
		methods, err := route.GetMethods()
		if err == nil {
			fmt.Println("Methods:", strings.Join(methods, ","))
		}
		fmt.Println()
		return nil
	})

	if err != nil {
		fmt.Println(err)
	}

	http.Handle("/", r)
}
```

### Graceful Shutdown

Go 1.8 introduced the ability to [gracefully shutdown](https://golang.org/doc/go1.8#http_shutdown) a `*http.Server`. Here's how to do that alongside `mux`:

```go
package main

import (
    "context"
    "flag"
    "log"
    "net/http"
    "os"
    "os/signal"
    "time"

    "github.com/gorilla/mux"
)

func main() {
    var wait time.Duration
    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")
    flag.Parse()

    r := mux.NewRouter()
    // Add your routes as needed

    srv := &http.Server{
        Addr:         "0.0.0.0:8080",
        // Good practice to set timeouts to avoid Slowloris attacks.
        WriteTimeout: time.Second * 15,
        ReadTimeout:  time.Second * 15,
        IdleTimeout:  time.Second * 60,
        Handler: r, // Pass our instance of gorilla/mux in.
    }

    // Run our server in a goroutine so that it doesn't block.
    go func() {
        if err := srv.ListenAndServe(); err != nil {
            log.Println(err)
        }
    }()

    c := make(chan os.Signal, 1)
    // We'll accept graceful shutdowns when quit via SIGINT (Ctrl+C)
    // SIGKILL, SIGQUIT or SIGTERM (Ctrl+/) will not be caught.
    signal.Notify(c, os.Interrupt)

    // Block until we receive our signal.
    <-c

    // Create a deadline to wait for.
    ctx, cancel := context.WithTimeout(context.Background(), wait)
    defer cancel()
    // Doesn't block if no connections, but will otherwise wait
    // until the timeout deadline.
    srv.Shutdown(ctx)
    // Optionally, you could run srv.Shutdown in a goroutine and block on
    // <-ctx.Done() if your application should wait for other services
    // to finalize based on context cancellation.
    log.Println("shutting down")
    os.Exit(0)
}
```

### Middleware

Mux supports the addition of middlewares to a [Router](https://godoc.org/github.com/gorilla/mux#Router), which are executed in the order they are added if a match is found, including its subrouters.
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.

Mux middlewares are defined using the de facto standard type:

```go
type MiddlewareFunc func(http.Handler) http.Handler
```

Typically, the returned handler is a closure which does something with the http.ResponseWriter and http.Request passed to it, and then calls the handler passed as parameter to the MiddlewareFunc. This takes advantage of closures being able access variables from the context where they are created, while retaining the signature enforced by the receivers.

A very basic middleware which logs the URI of the request being handled could be written as:

```go
func loggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // Do stuff here
        log.Println(r.RequestURI)
        // Call the next handler, which can be another middleware in the chain, or the final handler.
        next.ServeHTTP(w, r)
    })
}
```

Middlewares can be added to a router using `Router.Use()`:

```go
r := mux.NewRouter()
r.HandleFunc("/", handler)
r.Use(loggingMiddleware)
```

A more complex authentication middleware, which maps session token to users, could be written as:

```go
// Define our struct
type authenticationMiddleware struct {
	tokenUsers map[string]string
}

// Initialize it somewhere
func (amw *authenticationMiddleware) Populate() {
	amw.tokenUsers["00000000"] = "user0"
	amw.tokenUsers["aaaaaaaa"] = "userA"
	amw.tokenUsers["05f717e5"] = "randomUser"
	amw.tokenUsers["deadbeef"] = "user0"
}

// Middleware function, which will be called for each request
func (amw *authenticationMiddleware) Middleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        token := r.Header.Get("X-Session-Token")

        if user, found := amw.tokenUsers[token]; found {
        	// We found the token in our map
        	log.Printf("Authenticated user %s\n", user)
        	// Pass down the request to the next middleware (or final handler)
        	next.ServeHTTP(w, r)
        } else {
        	// Write an error and stop the handler chain
        	http.Error(w, "Forbidden", http.StatusForbidden)
        }
    })
}
```

```go
r := mux.NewRouter()
r.HandleFunc("/", handler)

amw := authenticationMiddleware{tokenUsers: make(map[string]string)}
amw.Populate()

r.Use(amw.Middleware)
```

Note: The handler chain will be stopped if your middleware doesn't call `next.ServeHTTP()` with the corresponding parameters. This can be used to abort a request if the middleware writer wants to. Middlewares _should_ write to `ResponseWriter` if they _are_ going to terminate the request, and they _should not_ write to `ResponseWriter` if they _are not_ going to terminate it.

### Handling CORS Requests

[CORSMethodMiddleware](https://godoc.org/github.com/gorilla/mux#CORSMethodMiddleware) intends to make it easier to strictly set the `Access-Control-Allow-Methods` response header.

* You will still need to use your own CORS handler to set the other CORS headers such as `Access-Control-Allow-Origin`
* The middleware will set the `Access-Control-Allow-Methods` header to all the method matchers (e.g. `r.Methods(http.MethodGet, http.MethodPut, http.MethodOptions)` -> `Access-Control-Allow-Methods: GET,PUT,OPTIONS`) on a route
* If you do not specify any methods, then:
> _Important_: there must be an `OPTIONS` method matcher for the middleware to set the headers.

Here is an example of using `CORSMethodMiddleware` along with a custom `OPTIONS` handler to set all the required CORS headers:

```go
package main

import (
	"net/http"
	"github.com/gorilla/mux"
)

func main() {
    r := mux.NewRouter()

    // IMPORTANT: you must specify an OPTIONS method matcher for the middleware to set CORS headers
    r.HandleFunc("/foo", fooHandler).Methods(http.MethodGet, http.MethodPut, http.MethodPatch, http.MethodOptions)
    r.Use(mux.CORSMethodMiddleware(r))
    
    http.ListenAndServe(":8080", r)
}

func fooHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Access-Control-Allow-Origin", "*")
    if r.Method == http.MethodOptions {
        return
    }

    w.Write([]byte("foo"))
}
```

And an request to `/foo` using something like:

```bash
curl localhost:8080/foo -v
```

Would look like:

```bash
*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /foo HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.59.0
> Accept: */*
> 
< HTTP/1.1 200 OK
< Access-Control-Allow-Methods: GET,PUT,PATCH,OPTIONS
< Access-Control-Allow-Origin: *
< Date: Fri, 28 Jun 2019 20:13:30 GMT
< Content-Length: 3
< Content-Type: text/plain; charset=utf-8
< 
* Connection #0 to host localhost left intact
foo
```

### Testing Handlers

Testing handlers 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_.

First, our simple HTTP handler:

```go
// endpoints.go
package main

func HealthCheckHandler(w http.ResponseWriter, r *http.Request) {
    // A very simple health check.
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusOK)

    // In the future we could report back on the status of our DB, or our cache
    // (e.g. Redis) by performing a simple PING, and include them in the response.
    io.WriteString(w, `{"alive": true}`)
}

func main() {
    r := mux.NewRouter()
    r.HandleFunc("/health", HealthCheckHandler)

    log.Fatal(http.ListenAndServe("localhost:8080", r))
}
```

Our test code:

```go
// endpoints_test.go
package main

import (
    "net/http"
    "net/http/httptest"
    "testing"
)

func TestHealthCheckHandler(t *testing.T) {
    // Create a request to pass to our handler. We don't have any query parameters for now, so we'll
    // pass 'nil' as the third parameter.
    req, err := http.NewRequest("GET", "/health", nil)
    if err != nil {
        t.Fatal(err)
    }

    // We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response.
    rr := httptest.NewRecorder()
    handler := http.HandlerFunc(HealthCheckHandler)

    // Our handlers satisfy http.Handler, so we can call their ServeHTTP method
    // directly and pass in our Request and ResponseRecorder.
    handler.ServeHTTP(rr, req)

    // Check the status code is what we expect.
    if status := rr.Code; status != http.StatusOK {
        t.Errorf("handler returned wrong status code: got %v want %v",
            status, http.StatusOK)
    }

    // Check the response body is what we expect.
    expected := `{"alive": true}`
    if rr.Body.String() != expected {
        t.Errorf("handler returned unexpected body: got %v want %v",
            rr.Body.String(), expected)
    }
}
```

In the case that our routes have [variables](#examples), we can pass those in the request. We could write
[table-driven tests](https://dave.cheney.net/2013/06/09/writing-table-driven-tests-in-go) to test multiple
possible route variables as needed.

```go
// endpoints.go
func main() {
    r := mux.NewRouter()
    // A route with a route variable:
    r.HandleFunc("/metrics/{type}", MetricsHandler)

    log.Fatal(http.ListenAndServe("localhost:8080", r))
}
```

Our test file, with a table-driven test of `routeVariables`:

```go
// endpoints_test.go
func TestMetricsHandler(t *testing.T) {
    tt := []struct{
        routeVariable string
        shouldPass bool
    }{
        {"goroutines", true},
        {"heap", true},
        {"counters", true},
        {"queries", true},
        {"adhadaeqm3k", false},
    }

    for _, tc := range tt {
        path := fmt.Sprintf("/metrics/%s", tc.routeVariable)
        req, err := http.NewRequest("GET", path, nil)
        if err != nil {
            t.Fatal(err)
        }

        rr := httptest.NewRecorder()
	
	// To add the vars to the context, 
	// we need to create a router through which we can pass the request.
	router := mux.NewRouter()
        router.HandleFunc("/metrics/{type}", MetricsHandler)
        router.ServeHTTP(rr, req)

        // In this case, our MetricsHandler returns a non-200 response
        // for a route variable it doesn't know about.
        if rr.Code == http.StatusOK && !tc.shouldPass {
            t.Errorf("handler should have failed on routeVariable %s: got %v want %v",
                tc.routeVariable, rr.Code, http.StatusOK)
        }
    }
}
```

## Full Example

Here's a complete, runnable example of a small `mux` based server:

```go
package main

import (
    "net/http"
    "log"
    "github.com/gorilla/mux"
)

func YourHandler(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("Gorilla!\n"))
}

func main() {
    r := mux.NewRouter()
    // Routes consist of a path and a handler function.
    r.HandleFunc("/", YourHandler)

    // Bind to a port and pass our router in
    log.Fatal(http.ListenAndServe(":8000", r))
}
```

## License

BSD licensed. See the LICENSE file for details.


================================================
FILE: bench_test.go
================================================
// Copyright 2012 The Gorilla Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package mux

import (
	"net/http"
	"net/http/httptest"
	"testing"
)

func BenchmarkMux(b *testing.B) {
	router := new(Router)
	handler := func(w http.ResponseWriter, r *http.Request) {}
	router.HandleFunc("/v1/{v1}", handler)

	request, _ := http.NewRequest("GET", "/v1/anything", nil)
	for i := 0; i < b.N; i++ {
		router.ServeHTTP(nil, request)
	}
}

func BenchmarkMuxSimple(b *testing.B) {
	router := new(Router)
	handler := func(w http.ResponseWriter, r *http.Request) {}
	router.HandleFunc("/status", handler)

	testCases := []struct {
		name                 string
		omitRouteFromContext bool
	}{
		{
			name:                 "default",
			omitRouteFromContext: false,
		},
		{
			name:                 "omit route from ctx",
			omitRouteFromContext: true,
		},
	}
	for _, tc := range testCases {
		b.Run(tc.name, func(b *testing.B) {
			router.OmitRouteFromContext(tc.omitRouteFromContext)

			request, _ := http.NewRequest("GET", "/status", nil)
			b.ReportAllocs()
			b.ResetTimer()
			for i := 0; i < b.N; i++ {
				router.ServeHTTP(nil, request)
			}
		})
	}
}

func BenchmarkMuxAlternativeInRegexp(b *testing.B) {
	router := new(Router)
	handler := func(w http.ResponseWriter, r *http.Request) {}
	router.HandleFunc("/v1/{v1:(?:a|b)}", handler)

	requestA, _ := http.NewRequest("GET", "/v1/a", nil)
	requestB, _ := http.NewRequest("GET", "/v1/b", nil)
	for i := 0; i < b.N; i++ {
		router.ServeHTTP(nil, requestA)
		router.ServeHTTP(nil, requestB)
	}
}

func BenchmarkManyPathVariables(b *testing.B) {
	router := new(Router)
	handler := func(w http.ResponseWriter, r *http.Request) {}
	router.HandleFunc("/v1/{v1}/{v2}/{v3}/{v4}/{v5}", handler)

	matchingRequest, _ := http.NewRequest("GET", "/v1/1/2/3/4/5", nil)
	notMatchingRequest, _ := http.NewRequest("GET", "/v1/1/2/3/4", nil)
	recorder := httptest.NewRecorder()
	for i := 0; i < b.N; i++ {
		router.ServeHTTP(nil, matchingRequest)
		router.ServeHTTP(recorder, notMatchingRequest)
	}
}


================================================
FILE: doc.go
================================================
// Copyright 2012 The Gorilla Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

/*
Package mux implements a request router and dispatcher.

The name mux stands for "HTTP request multiplexer". Like the standard
http.ServeMux, mux.Router matches incoming requests against a list of
registered routes and calls a handler for the route that matches the URL
or other conditions. The main features are:

  - Requests can be matched based on URL host, path, path prefix, schemes,
    header and query values, HTTP methods or using custom matchers.
  - URL hosts, paths and query values can have variables with an optional
    regular expression.
  - Registered URLs can be built, or "reversed", which helps maintaining
    references to resources.
  - Routes can be used as subrouters: nested routes are only tested if the
    parent route matches. This is useful to define groups of routes that
    share common conditions like a host, a path prefix or other repeated
    attributes. As a bonus, this optimizes request matching.
  - It implements the http.Handler interface so it is compatible with the
    standard http.ServeMux.

Let's start registering a couple of URL paths and handlers:

	func main() {
		r := mux.NewRouter()
		r.HandleFunc("/", HomeHandler)
		r.HandleFunc("/products", ProductsHandler)
		r.HandleFunc("/articles", ArticlesHandler)
		http.Handle("/", r)
	}

Here we register three routes mapping URL paths to handlers. This is
equivalent to how http.HandleFunc() works: if an incoming request URL matches
one of the paths, the corresponding handler is called passing
(http.ResponseWriter, *http.Request) as parameters.

Paths can have variables. They are defined using the format {name} or
{name:pattern}. If a regular expression pattern is not defined, the matched
variable will be anything until the next slash. For example:

	r := mux.NewRouter()
	r.HandleFunc("/products/{key}", ProductHandler)
	r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler)
	r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)

Groups can be used inside patterns, as long as they are non-capturing (?:re). For example:

	r.HandleFunc("/articles/{category}/{sort:(?:asc|desc|new)}", ArticlesCategoryHandler)

The names are used to create a map of route variables which can be retrieved
calling mux.Vars():

	vars := mux.Vars(request)
	category := vars["category"]

Note that if any capturing groups are present, mux will panic() during parsing. To prevent
this, convert any capturing groups to non-capturing, e.g. change "/{sort:(asc|desc)}" to
"/{sort:(?:asc|desc)}". This is a change from prior versions which behaved unpredictably
when capturing groups were present.

And this is all you need to know about the basic usage. More advanced options
are explained below.

Routes can also be restricted to a domain or subdomain. Just define a host
pattern to be matched. They can also have variables:

	r := mux.NewRouter()
	// Only matches if domain is "www.example.com".
	r.Host("www.example.com")
	// Matches a dynamic subdomain.
	r.Host("{subdomain:[a-z]+}.domain.com")

There are several other matchers that can be added. To match path prefixes:

	r.PathPrefix("/products/")

...or HTTP methods:

	r.Methods("GET", "POST")

...or URL schemes:

	r.Schemes("https")

...or header values:

	r.Headers("X-Requested-With", "XMLHttpRequest")

...or query values:

	r.Queries("key", "value")

...or to use a custom matcher function:

	r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool {
		return r.ProtoMajor == 0
	})

...and finally, it is possible to combine several matchers in a single route:

	r.HandleFunc("/products", ProductsHandler).
	  Host("www.example.com").
	  Methods("GET").
	  Schemes("http")

Setting the same matching conditions again and again can be boring, so we have
a way to group several routes that share the same requirements.
We call it "subrouting".

For example, let's say we have several URLs that should only match when the
host is "www.example.com". Create a route for that host and get a "subrouter"
from it:

	r := mux.NewRouter()
	s := r.Host("www.example.com").Subrouter()

Then register routes in the subrouter:

	s.HandleFunc("/products/", ProductsHandler)
	s.HandleFunc("/products/{key}", ProductHandler)
	s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler)

The three URL paths we registered above will only be tested if the domain is
"www.example.com", because the subrouter is tested first. This is not
only convenient, but also optimizes request matching. You can create
subrouters combining any attribute matchers accepted by a route.

Subrouters can be used to create domain or path "namespaces": you define
subrouters in a central place and then parts of the app can register its
paths relatively to a given subrouter.

There's one more thing about subroutes. When a subrouter has a path prefix,
the inner routes use it as base for their paths:

	r := mux.NewRouter()
	s := r.PathPrefix("/products").Subrouter()
	// "/products/"
	s.HandleFunc("/", ProductsHandler)
	// "/products/{key}/"
	s.HandleFunc("/{key}/", ProductHandler)
	// "/products/{key}/details"
	s.HandleFunc("/{key}/details", ProductDetailsHandler)

Note that the path provided to PathPrefix() represents a "wildcard": calling
PathPrefix("/static/").Handler(...) means that the handler will be passed any
request that matches "/static/*". This makes it easy to serve static files with mux:

	func main() {
		var dir string

		flag.StringVar(&dir, "dir", ".", "the directory to serve files from. Defaults to the current dir")
		flag.Parse()
		r := mux.NewRouter()

		// This will serve files under http://localhost:8000/static/<filename>
		r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(dir))))

		srv := &http.Server{
			Handler:      r,
			Addr:         "127.0.0.1:8000",
			// Good practice: enforce timeouts for servers you create!
			WriteTimeout: 15 * time.Second,
			ReadTimeout:  15 * time.Second,
		}

		log.Fatal(srv.ListenAndServe())
	}

Now let's see how to build registered URLs.

Routes can be named. All routes that define a name can have their URLs built,
or "reversed". We define a name calling Name() on a route. For example:

	r := mux.NewRouter()
	r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
	  Name("article")

To build a URL, get the route and call the URL() method, passing a sequence of
key/value pairs for the route variables. For the previous route, we would do:

	url, err := r.Get("article").URL("category", "technology", "id", "42")

...and the result will be a url.URL with the following path:

	"/articles/technology/42"

This also works for host and query value variables:

	r := mux.NewRouter()
	r.Host("{subdomain}.domain.com").
	  Path("/articles/{category}/{id:[0-9]+}").
	  Queries("filter", "{filter}").
	  HandlerFunc(ArticleHandler).
	  Name("article")

	// url.String() will be "http://news.domain.com/articles/technology/42?filter=gorilla"
	url, err := r.Get("article").URL("subdomain", "news",
	                                 "category", "technology",
	                                 "id", "42",
	                                 "filter", "gorilla")

All variables defined in the route are required, and their values must
conform to the corresponding patterns. These requirements guarantee that a
generated URL will always match a registered route -- the only exception is
for explicitly defined "build-only" routes which never match.

Regex support also exists for matching Headers within a route. For example, we could do:

	r.HeadersRegexp("Content-Type", "application/(text|json)")

...and the route will match both requests with a Content-Type of `application/json` as well as
`application/text`

There's also a way to build only the URL host or path for a route:
use the methods URLHost() or URLPath() instead. For the previous route,
we would do:

	// "http://news.domain.com/"
	host, err := r.Get("article").URLHost("subdomain", "news")

	// "/articles/technology/42"
	path, err := r.Get("article").URLPath("category", "technology", "id", "42")

And if you use subrouters, host and path defined separately can be built
as well:

	r := mux.NewRouter()
	s := r.Host("{subdomain}.domain.com").Subrouter()
	s.Path("/articles/{category}/{id:[0-9]+}").
	  HandlerFunc(ArticleHandler).
	  Name("article")

	// "http://news.domain.com/articles/technology/42"
	url, err := r.Get("article").URL("subdomain", "news",
	                                 "category", "technology",
	                                 "id", "42")

Mux supports the addition of middlewares to a Router, which are executed in the order they are added if a match is found, including its subrouters. Middlewares are (typically) small pieces of code which take one request, do something with it, and pass it down to another middleware or the final handler. Some common use cases for middleware are request logging, header manipulation, or ResponseWriter hijacking.

	type MiddlewareFunc func(http.Handler) http.Handler

Typically, the returned handler is a closure which does something with the http.ResponseWriter and http.Request passed to it, and then calls the handler passed as parameter to the MiddlewareFunc (closures can access variables from the context where they are created).

A very basic middleware which logs the URI of the request being handled could be written as:

	func simpleMw(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			// Do stuff here
			log.Println(r.RequestURI)
			// Call the next handler, which can be another middleware in the chain, or the final handler.
			next.ServeHTTP(w, r)
		})
	}

Middlewares can be added to a router using `Router.Use()`:

	r := mux.NewRouter()
	r.HandleFunc("/", handler)
	r.Use(simpleMw)

A more complex authentication middleware, which maps session token to users, could be written as:

	// Define our struct
	type authenticationMiddleware struct {
		tokenUsers map[string]string
	}

	// Initialize it somewhere
	func (amw *authenticationMiddleware) Populate() {
		amw.tokenUsers["00000000"] = "user0"
		amw.tokenUsers["aaaaaaaa"] = "userA"
		amw.tokenUsers["05f717e5"] = "randomUser"
		amw.tokenUsers["deadbeef"] = "user0"
	}

	// Middleware function, which will be called for each request
	func (amw *authenticationMiddleware) Middleware(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			token := r.Header.Get("X-Session-Token")

			if user, found := amw.tokenUsers[token]; found {
				// We found the token in our map
				log.Printf("Authenticated user %s\n", user)
				next.ServeHTTP(w, r)
			} else {
				http.Error(w, "Forbidden", http.StatusForbidden)
			}
		})
	}

	r := mux.NewRouter()
	r.HandleFunc("/", handler)

	amw := authenticationMiddleware{tokenUsers: make(map[string]string)}
	amw.Populate()

	r.Use(amw.Middleware)

Note: The handler chain will be stopped if your middleware doesn't call `next.ServeHTTP()` with the corresponding parameters. This can be used to abort a request if the middleware writer wants to.
*/
package mux


================================================
FILE: example_authentication_middleware_test.go
================================================
package mux_test

import (
	"log"
	"net/http"

	"github.com/gorilla/mux"
)

// Define our struct
type authenticationMiddleware struct {
	tokenUsers map[string]string
}

// Initialize it somewhere
func (amw *authenticationMiddleware) Populate() {
	amw.tokenUsers["00000000"] = "user0"
	amw.tokenUsers["aaaaaaaa"] = "userA"
	amw.tokenUsers["05f717e5"] = "randomUser"
	amw.tokenUsers["deadbeef"] = "user0"
}

// Middleware function, which will be called for each request
func (amw *authenticationMiddleware) Middleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		token := r.Header.Get("X-Session-Token")

		if user, found := amw.tokenUsers[token]; found {
			// We found the token in our map
			log.Printf("Authenticated user %s\n", user)
			next.ServeHTTP(w, r)
		} else {
			http.Error(w, "Forbidden", http.StatusForbidden)
		}
	})
}

func Example_authenticationMiddleware() {
	r := mux.NewRouter()
	r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		// Do something here
	})
	amw := authenticationMiddleware{make(map[string]string)}
	amw.Populate()
	r.Use(amw.Middleware)
}


================================================
FILE: example_cors_method_middleware_test.go
================================================
package mux_test

import (
	"fmt"
	"net/http"
	"net/http/httptest"

	"github.com/gorilla/mux"
)

func ExampleCORSMethodMiddleware() {
	r := mux.NewRouter()

	r.HandleFunc("/foo", func(w http.ResponseWriter, r *http.Request) {
		// Handle the request
	}).Methods(http.MethodGet, http.MethodPut, http.MethodPatch)
	r.HandleFunc("/foo", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Access-Control-Allow-Origin", "http://example.com")
		w.Header().Set("Access-Control-Max-Age", "86400")
	}).Methods(http.MethodOptions)

	r.Use(mux.CORSMethodMiddleware(r))

	rw := httptest.NewRecorder()
	req, _ := http.NewRequest("OPTIONS", "/foo", nil)                 // needs to be OPTIONS
	req.Header.Set("Access-Control-Request-Method", "POST")           // needs to be non-empty
	req.Header.Set("Access-Control-Request-Headers", "Authorization") // needs to be non-empty
	req.Header.Set("Origin", "http://example.com")                    // needs to be non-empty

	r.ServeHTTP(rw, req)

	fmt.Println(rw.Header().Get("Access-Control-Allow-Methods"))
	fmt.Println(rw.Header().Get("Access-Control-Allow-Origin"))
	// Output:
	// GET,PUT,PATCH,OPTIONS
	// http://example.com
}


================================================
FILE: example_route_test.go
================================================
package mux_test

import (
	"fmt"
	"net/http"

	"github.com/gorilla/mux"
)

// This example demonstrates setting a regular expression matcher for
// the header value. A plain word will match any value that contains a
// matching substring as if the pattern was wrapped with `.*`.
func ExampleRoute_HeadersRegexp() {
	r := mux.NewRouter()
	route := r.NewRoute().HeadersRegexp("Accept", "html")

	req1, _ := http.NewRequest("GET", "example.com", nil)
	req1.Header.Add("Accept", "text/plain")
	req1.Header.Add("Accept", "text/html")

	req2, _ := http.NewRequest("GET", "example.com", nil)
	req2.Header.Set("Accept", "application/xhtml+xml")

	matchInfo := &mux.RouteMatch{}
	fmt.Printf("Match: %v %q\n", route.Match(req1, matchInfo), req1.Header["Accept"])
	fmt.Printf("Match: %v %q\n", route.Match(req2, matchInfo), req2.Header["Accept"])
	// Output:
	// Match: true ["text/plain" "text/html"]
	// Match: true ["application/xhtml+xml"]
}

// This example demonstrates setting a strict regular expression matcher
// for the header value. Using the start and end of string anchors, the
// value must be an exact match.
func ExampleRoute_HeadersRegexp_exactMatch() {
	r := mux.NewRouter()
	route := r.NewRoute().HeadersRegexp("Origin", "^https://example.co$")

	yes, _ := http.NewRequest("GET", "example.co", nil)
	yes.Header.Set("Origin", "https://example.co")

	no, _ := http.NewRequest("GET", "example.co.uk", nil)
	no.Header.Set("Origin", "https://example.co.uk")

	matchInfo := &mux.RouteMatch{}
	fmt.Printf("Match: %v %q\n", route.Match(yes, matchInfo), yes.Header["Origin"])
	fmt.Printf("Match: %v %q\n", route.Match(no, matchInfo), no.Header["Origin"])
	// Output:
	// Match: true ["https://example.co"]
	// Match: false ["https://example.co.uk"]
}


================================================
FILE: example_route_vars_test.go
================================================
package mux_test

import (
	"fmt"
	"github.com/gorilla/mux"
)

// This example demonstrates building a dynamic URL using
// required vars and values retrieve from another source
func ExampleRoute_GetVarNames() {
	r := mux.NewRouter()

	route := r.Host("{domain}").
		Path("/{group}/{item_id}").
		Queries("some_data1", "{some_data1}").
		Queries("some_data2_and_3", "{some_data2}.{some_data3}")

	dataSource := func(key string) string {
		return "my_value_for_" + key
	}

	varNames, _ := route.GetVarNames()

	pairs := make([]string, 0, len(varNames)*2)

	for _, varName := range varNames {
		pairs = append(pairs, varName, dataSource(varName))
	}

	url, err := route.URL(pairs...)
	if err != nil {
		panic(err)
	}
	fmt.Println(url.String())
}


================================================
FILE: go.mod
================================================
module github.com/gorilla/mux

go 1.20


================================================
FILE: middleware.go
================================================
package mux

import (
	"net/http"
	"strings"
)

// MiddlewareFunc is a function which receives an http.Handler and returns another http.Handler.
// Typically, the returned handler is a closure which does something with the http.ResponseWriter and http.Request passed
// to it, and then calls the handler passed as parameter to the MiddlewareFunc.
type MiddlewareFunc func(http.Handler) http.Handler

// middleware interface is anything which implements a MiddlewareFunc named Middleware.
type middleware interface {
	Middleware(handler http.Handler) http.Handler
}

// Middleware allows MiddlewareFunc to implement the middleware interface.
func (mw MiddlewareFunc) Middleware(handler http.Handler) http.Handler {
	return mw(handler)
}

// 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.
func (r *Router) Use(mwf ...MiddlewareFunc) {
	for _, fn := range mwf {
		r.middlewares = append(r.middlewares, fn)
	}
}

// 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.
func (r *Router) useInterface(mw middleware) {
	r.middlewares = append(r.middlewares, mw)
}

// RouteMiddleware -------------------------------------------------------------

// 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.
func (r *Route) Use(mwf ...MiddlewareFunc) *Route {
	for _, fn := range mwf {
		r.middlewares = append(r.middlewares, fn)
	}

	return r
}

// 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.
func (r *Route) useInterface(mw middleware) {
	r.middlewares = append(r.middlewares, mw)
}

// CORSMethodMiddleware automatically sets the Access-Control-Allow-Methods response header
// on requests for routes that have an OPTIONS method matcher to all the method matchers on
// the route. Routes that do not explicitly handle OPTIONS requests will not be processed
// by the middleware. See examples for usage.
func CORSMethodMiddleware(r *Router) MiddlewareFunc {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
			allMethods, err := getAllMethodsForRoute(r, req)
			if err == nil {
				for _, v := range allMethods {
					if v == http.MethodOptions {
						w.Header().Set("Access-Control-Allow-Methods", strings.Join(allMethods, ","))
					}
				}
			}

			next.ServeHTTP(w, req)
		})
	}
}

// getAllMethodsForRoute returns all the methods from method matchers matching a given
// request.
func getAllMethodsForRoute(r *Router, req *http.Request) ([]string, error) {
	var allMethods []string

	for _, route := range r.routes {
		var match RouteMatch
		if route.Match(req, &match) || match.MatchErr == ErrMethodMismatch {
			methods, err := route.GetMethods()
			if err != nil {
				return nil, err
			}

			allMethods = append(allMethods, methods...)
		}
	}

	return allMethods, nil
}


================================================
FILE: middleware_test.go
================================================
package mux

import (
	"bytes"
	"fmt"
	"net/http"
	"testing"
)

type testMiddleware struct {
	timesCalled uint
}

func (tm *testMiddleware) Middleware(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		tm.timesCalled++
		h.ServeHTTP(w, r)
	})
}

func dummyHandler(w http.ResponseWriter, r *http.Request) {}

func TestMiddlewareAdd(t *testing.T) {
	router := NewRouter()
	router.HandleFunc("/", dummyHandler).Methods("GET")

	mw := &testMiddleware{}

	router.useInterface(mw)
	if len(router.middlewares) != 1 || router.middlewares[0] != mw {
		t.Fatal("Middleware interface was not added correctly")
	}

	router.Use(mw.Middleware)
	if len(router.middlewares) != 2 {
		t.Fatal("Middleware method was not added correctly")
	}

	banalMw := func(handler http.Handler) http.Handler {
		return handler
	}
	router.Use(banalMw)
	if len(router.middlewares) != 3 {
		t.Fatal("Middleware function was not added correctly")
	}

	route := router.HandleFunc("/route", dummyHandler)
	route.useInterface(mw)
	if len(route.middlewares) != 1 {
		t.Fatal("Route middleware function was not added correctly")
	}

	route.Use(banalMw)
	if len(route.middlewares) != 2 {
		t.Fatal("Route middleware function was not added correctly")
	}
}

func TestMiddleware(t *testing.T) {
	router := NewRouter()
	router.HandleFunc("/", dummyHandler).Methods("GET")

	mw := &testMiddleware{}
	router.useInterface(mw)

	rw := NewRecorder()
	req := newRequest("GET", "/")

	t.Run("regular middleware call", func(t *testing.T) {
		router.ServeHTTP(rw, req)
		if mw.timesCalled != 1 {
			t.Fatalf("Expected %d calls, but got only %d", 1, mw.timesCalled)
		}
	})

	t.Run("not called for 404", func(t *testing.T) {
		req = newRequest("GET", "/not/found")
		router.ServeHTTP(rw, req)
		if mw.timesCalled != 1 {
			t.Fatalf("Expected %d calls, but got only %d", 1, mw.timesCalled)
		}
	})

	t.Run("not called for method mismatch", func(t *testing.T) {
		req = newRequest("POST", "/")
		router.ServeHTTP(rw, req)
		if mw.timesCalled != 1 {
			t.Fatalf("Expected %d calls, but got only %d", 1, mw.timesCalled)
		}
	})

	t.Run("regular call using function middleware", func(t *testing.T) {
		router.Use(mw.Middleware)
		req = newRequest("GET", "/")
		router.ServeHTTP(rw, req)
		if mw.timesCalled != 3 {
			t.Fatalf("Expected %d calls, but got only %d", 3, mw.timesCalled)
		}
	})

	t.Run("regular call using route middleware func", func(t *testing.T) {
		router.HandleFunc("/route", dummyHandler).Use(mw.Middleware)
		req = newRequest("GET", "/route")
		router.ServeHTTP(rw, req)
		if mw.timesCalled != 6 {
			t.Fatalf("Expected %d calls, but got only %d", 6, mw.timesCalled)
		}
	})

	t.Run("regular call using route middleware interface", func(t *testing.T) {
		router.HandleFunc("/route", dummyHandler).useInterface(mw)
		req = newRequest("GET", "/route")
		router.ServeHTTP(rw, req)
		if mw.timesCalled != 9 {
			t.Fatalf("Expected %d calls, but got only %d", 9, mw.timesCalled)
		}
	})
}

func TestMiddlewareSubrouter(t *testing.T) {
	router := NewRouter()
	router.HandleFunc("/", dummyHandler).Methods("GET")

	subrouter := router.PathPrefix("/sub").Subrouter()
	subrouter.HandleFunc("/x", dummyHandler).Methods("GET")

	mw := &testMiddleware{}
	subrouter.useInterface(mw)

	rw := NewRecorder()
	req := newRequest("GET", "/")

	t.Run("not called for route outside subrouter", func(t *testing.T) {
		router.ServeHTTP(rw, req)
		if mw.timesCalled != 0 {
			t.Fatalf("Expected %d calls, but got only %d", 0, mw.timesCalled)
		}
	})

	t.Run("not called for subrouter root 404", func(t *testing.T) {
		req = newRequest("GET", "/sub/")
		router.ServeHTTP(rw, req)
		if mw.timesCalled != 0 {
			t.Fatalf("Expected %d calls, but got only %d", 0, mw.timesCalled)
		}
	})

	t.Run("called once for route inside subrouter", func(t *testing.T) {
		req = newRequest("GET", "/sub/x")
		router.ServeHTTP(rw, req)
		if mw.timesCalled != 1 {
			t.Fatalf("Expected %d calls, but got only %d", 1, mw.timesCalled)
		}
	})

	t.Run("not called for 404 inside subrouter", func(t *testing.T) {
		req = newRequest("GET", "/sub/not/found")
		router.ServeHTTP(rw, req)
		if mw.timesCalled != 1 {
			t.Fatalf("Expected %d calls, but got only %d", 1, mw.timesCalled)
		}
	})

	t.Run("middleware added to router", func(t *testing.T) {
		router.useInterface(mw)

		t.Run("called once for route outside subrouter", func(t *testing.T) {
			req = newRequest("GET", "/")
			router.ServeHTTP(rw, req)
			if mw.timesCalled != 2 {
				t.Fatalf("Expected %d calls, but got only %d", 2, mw.timesCalled)
			}
		})

		t.Run("called twice for route inside subrouter", func(t *testing.T) {
			req = newRequest("GET", "/sub/x")
			router.ServeHTTP(rw, req)
			if mw.timesCalled != 4 {
				t.Fatalf("Expected %d calls, but got only %d", 4, mw.timesCalled)
			}
		})
	})
}

func TestMiddlewareExecution(t *testing.T) {
	mwStr := []byte("Middleware\n")
	handlerStr := []byte("Logic\n")

	handlerFunc := func(w http.ResponseWriter, e *http.Request) {
		_, err := w.Write(handlerStr)
		if err != nil {
			t.Fatalf("Failed writing HTTP response: %v", err)
		}
	}

	router := NewRouter()
	router.HandleFunc("/", handlerFunc)

	t.Run("responds normally without middleware", func(t *testing.T) {
		rw := NewRecorder()
		req := newRequest("GET", "/")

		router.ServeHTTP(rw, req)

		if !bytes.Equal(rw.Body.Bytes(), handlerStr) {
			t.Fatal("Handler response is not what it should be")
		}
	})

	t.Run("responds with handler and middleware response", func(t *testing.T) {
		rw := NewRecorder()
		req := newRequest("GET", "/")

		router.Use(func(h http.Handler) http.Handler {
			return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				_, err := w.Write(mwStr)
				if err != nil {
					t.Fatalf("Failed writing HTTP response: %v", err)
				}
				h.ServeHTTP(w, r)
			})
		})

		router.ServeHTTP(rw, req)
		if !bytes.Equal(rw.Body.Bytes(), append(mwStr, handlerStr...)) {
			t.Fatal("Middleware + handler response is not what it should be")
		}
	})

	t.Run("responds with handler, middleware and route middleware response", func(t *testing.T) {
		routeMwStr := []byte("Route Middleware\n")
		rw := NewRecorder()
		req := newRequest("GET", "/route")

		router.HandleFunc("/route", handlerFunc).Use(func(h http.Handler) http.Handler {
			return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				_, err := w.Write(routeMwStr)
				if err != nil {
					t.Fatalf("Failed writing HTTP response: %v", err)
				}
				h.ServeHTTP(w, r)
			})
		})

		router.ServeHTTP(rw, req)
		expectedString := append(append(mwStr, routeMwStr...), handlerStr...)
		if !bytes.Equal(rw.Body.Bytes(), expectedString) {
			fmt.Println(rw.Body.String())
			t.Fatal("Middleware + handler response is not what it should be")
		}
	})
}

func TestMiddlewareNotFound(t *testing.T) {
	mwStr := []byte("Middleware\n")
	handlerStr := []byte("Logic\n")

	router := NewRouter()
	router.HandleFunc("/", func(w http.ResponseWriter, e *http.Request) {
		_, err := w.Write(handlerStr)
		if err != nil {
			t.Fatalf("Failed writing HTTP response: %v", err)
		}
	})
	router.Use(func(h http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			_, err := w.Write(mwStr)
			if err != nil {
				t.Fatalf("Failed writing HTTP response: %v", err)
			}
			h.ServeHTTP(w, r)
		})
	})

	// Test not found call with default handler
	t.Run("not called", func(t *testing.T) {
		rw := NewRecorder()
		req := newRequest("GET", "/notfound")

		router.ServeHTTP(rw, req)
		if bytes.Contains(rw.Body.Bytes(), mwStr) {
			t.Fatal("Middleware was called for a 404")
		}
	})

	t.Run("not called with custom not found handler", func(t *testing.T) {
		rw := NewRecorder()
		req := newRequest("GET", "/notfound")

		router.NotFoundHandler = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
			_, err := rw.Write([]byte("Custom 404 handler"))
			if err != nil {
				t.Fatalf("Failed writing HTTP response: %v", err)
			}
		})
		router.ServeHTTP(rw, req)

		if bytes.Contains(rw.Body.Bytes(), mwStr) {
			t.Fatal("Middleware was called for a custom 404")
		}
	})
}

func TestMiddlewareMethodMismatch(t *testing.T) {
	mwStr := []byte("Middleware\n")
	handlerStr := []byte("Logic\n")

	router := NewRouter()
	router.HandleFunc("/", func(w http.ResponseWriter, e *http.Request) {
		_, err := w.Write(handlerStr)
		if err != nil {
			t.Fatalf("Failed writing HTTP response: %v", err)
		}
	}).Methods("GET")

	router.Use(func(h http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			_, err := w.Write(mwStr)
			if err != nil {
				t.Fatalf("Failed writing HTTP response: %v", err)
			}
			h.ServeHTTP(w, r)
		})
	})

	t.Run("not called", func(t *testing.T) {
		rw := NewRecorder()
		req := newRequest("POST", "/")

		router.ServeHTTP(rw, req)
		if bytes.Contains(rw.Body.Bytes(), mwStr) {
			t.Fatal("Middleware was called for a method mismatch")
		}
	})

	t.Run("not called with custom method not allowed handler", func(t *testing.T) {
		rw := NewRecorder()
		req := newRequest("POST", "/")

		router.MethodNotAllowedHandler = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
			_, err := rw.Write([]byte("Method not allowed"))
			if err != nil {
				t.Fatalf("Failed writing HTTP response: %v", err)
			}
		})
		router.ServeHTTP(rw, req)

		if bytes.Contains(rw.Body.Bytes(), mwStr) {
			t.Fatal("Middleware was called for a method mismatch")
		}
	})
}

func TestMiddlewareNotFoundSubrouter(t *testing.T) {
	mwStr := []byte("Middleware\n")
	handlerStr := []byte("Logic\n")

	router := NewRouter()
	router.HandleFunc("/", func(w http.ResponseWriter, e *http.Request) {
		_, err := w.Write(handlerStr)
		if err != nil {
			t.Fatalf("Failed writing HTTP response: %v", err)
		}
	})

	subrouter := router.PathPrefix("/sub/").Subrouter()
	subrouter.HandleFunc("/", func(w http.ResponseWriter, e *http.Request) {
		_, err := w.Write(handlerStr)
		if err != nil {
			t.Fatalf("Failed writing HTTP response: %v", err)
		}
	})

	router.Use(func(h http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			_, err := w.Write(mwStr)
			if err != nil {
				t.Fatalf("Failed writing HTTP response: %v", err)
			}
			h.ServeHTTP(w, r)
		})
	})

	t.Run("not called", func(t *testing.T) {
		rw := NewRecorder()
		req := newRequest("GET", "/sub/notfound")

		router.ServeHTTP(rw, req)
		if bytes.Contains(rw.Body.Bytes(), mwStr) {
			t.Fatal("Middleware was called for a 404")
		}
	})

	t.Run("not called with custom not found handler", func(t *testing.T) {
		rw := NewRecorder()
		req := newRequest("GET", "/sub/notfound")

		subrouter.NotFoundHandler = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
			_, err := rw.Write([]byte("Custom 404 handler"))
			if err != nil {
				t.Fatalf("Failed writing HTTP response: %v", err)
			}
		})
		router.ServeHTTP(rw, req)

		if bytes.Contains(rw.Body.Bytes(), mwStr) {
			t.Fatal("Middleware was called for a custom 404")
		}
	})
}

func TestMiddlewareMethodMismatchSubrouter(t *testing.T) {
	mwStr := []byte("Middleware\n")
	handlerStr := []byte("Logic\n")

	router := NewRouter()
	router.HandleFunc("/", func(w http.ResponseWriter, e *http.Request) {
		_, err := w.Write(handlerStr)
		if err != nil {
			t.Fatalf("Failed writing HTTP response: %v", err)
		}
	})

	subrouter := router.PathPrefix("/sub/").Subrouter()
	subrouter.HandleFunc("/", func(w http.ResponseWriter, e *http.Request) {
		_, err := w.Write(handlerStr)
		if err != nil {
			t.Fatalf("Failed writing HTTP response: %v", err)
		}
	}).Methods("GET")

	router.Use(func(h http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			_, err := w.Write(mwStr)
			if err != nil {
				t.Fatalf("Failed writing HTTP response: %v", err)
			}
			h.ServeHTTP(w, r)
		})
	})

	t.Run("not called", func(t *testing.T) {
		rw := NewRecorder()
		req := newRequest("POST", "/sub/")

		router.ServeHTTP(rw, req)
		if bytes.Contains(rw.Body.Bytes(), mwStr) {
			t.Fatal("Middleware was called for a method mismatch")
		}
	})

	t.Run("not called with custom method not allowed handler", func(t *testing.T) {
		rw := NewRecorder()
		req := newRequest("POST", "/sub/")

		router.MethodNotAllowedHandler = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
			_, err := rw.Write([]byte("Method not allowed"))
			if err != nil {
				t.Fatalf("Failed writing HTTP response: %v", err)
			}
		})
		router.ServeHTTP(rw, req)

		if bytes.Contains(rw.Body.Bytes(), mwStr) {
			t.Fatal("Middleware was called for a method mismatch")
		}
	})
}

func TestCORSMethodMiddleware(t *testing.T) {
	testCases := []struct {
		name                                    string
		registerRoutes                          func(r *Router)
		requestHeader                           http.Header
		requestMethod                           string
		requestPath                             string
		expectedAccessControlAllowMethodsHeader string
		expectedResponse                        string
	}{
		{
			name: "does not set without OPTIONS matcher",
			registerRoutes: func(r *Router) {
				r.HandleFunc("/foo", stringHandler("a")).Methods(http.MethodGet, http.MethodPut, http.MethodPatch)
			},
			requestMethod:                           "GET",
			requestPath:                             "/foo",
			expectedAccessControlAllowMethodsHeader: "",
			expectedResponse:                        "a",
		},
		{
			name: "sets on non OPTIONS",
			registerRoutes: func(r *Router) {
				r.HandleFunc("/foo", stringHandler("a")).Methods(http.MethodGet, http.MethodPut, http.MethodPatch)
				r.HandleFunc("/foo", stringHandler("b")).Methods(http.MethodOptions)
			},
			requestMethod:                           "GET",
			requestPath:                             "/foo",
			expectedAccessControlAllowMethodsHeader: "GET,PUT,PATCH,OPTIONS",
			expectedResponse:                        "a",
		},
		{
			name: "sets without preflight headers",
			registerRoutes: func(r *Router) {
				r.HandleFunc("/foo", stringHandler("a")).Methods(http.MethodGet, http.MethodPut, http.MethodPatch)
				r.HandleFunc("/foo", stringHandler("b")).Methods(http.MethodOptions)
			},
			requestMethod:                           "OPTIONS",
			requestPath:                             "/foo",
			expectedAccessControlAllowMethodsHeader: "GET,PUT,PATCH,OPTIONS",
			expectedResponse:                        "b",
		},
		{
			name: "does not set on error",
			registerRoutes: func(r *Router) {
				r.HandleFunc("/foo", stringHandler("a"))
			},
			requestMethod:                           "OPTIONS",
			requestPath:                             "/foo",
			expectedAccessControlAllowMethodsHeader: "",
			expectedResponse:                        "a",
		},
		{
			name: "sets header on valid preflight",
			registerRoutes: func(r *Router) {
				r.HandleFunc("/foo", stringHandler("a")).Methods(http.MethodGet, http.MethodPut, http.MethodPatch)
				r.HandleFunc("/foo", stringHandler("b")).Methods(http.MethodOptions)
			},
			requestMethod: "OPTIONS",
			requestPath:   "/foo",
			requestHeader: http.Header{
				"Access-Control-Request-Method":  []string{"GET"},
				"Access-Control-Request-Headers": []string{"Authorization"},
				"Origin":                         []string{"http://example.com"},
			},
			expectedAccessControlAllowMethodsHeader: "GET,PUT,PATCH,OPTIONS",
			expectedResponse:                        "b",
		},
		{
			name: "does not set methods from unmatching routes",
			registerRoutes: func(r *Router) {
				r.HandleFunc("/foo", stringHandler("c")).Methods(http.MethodDelete)
				r.HandleFunc("/foo/bar", stringHandler("a")).Methods(http.MethodGet, http.MethodPut, http.MethodPatch)
				r.HandleFunc("/foo/bar", stringHandler("b")).Methods(http.MethodOptions)
			},
			requestMethod: "OPTIONS",
			requestPath:   "/foo/bar",
			requestHeader: http.Header{
				"Access-Control-Request-Method":  []string{"GET"},
				"Access-Control-Request-Headers": []string{"Authorization"},
				"Origin":                         []string{"http://example.com"},
			},
			expectedAccessControlAllowMethodsHeader: "GET,PUT,PATCH,OPTIONS",
			expectedResponse:                        "b",
		},
	}

	for _, tt := range testCases {
		t.Run(tt.name, func(t *testing.T) {
			router := NewRouter()

			tt.registerRoutes(router)

			router.Use(CORSMethodMiddleware(router))

			rw := NewRecorder()
			req := newRequest(tt.requestMethod, tt.requestPath)
			req.Header = tt.requestHeader

			router.ServeHTTP(rw, req)

			actualMethodsHeader := rw.Header().Get("Access-Control-Allow-Methods")
			if actualMethodsHeader != tt.expectedAccessControlAllowMethodsHeader {
				t.Fatalf("Expected Access-Control-Allow-Methods to equal %s but got %s", tt.expectedAccessControlAllowMethodsHeader, actualMethodsHeader)
			}

			actualResponse := rw.Body.String()
			if actualResponse != tt.expectedResponse {
				t.Fatalf("Expected response to equal %s but got %s", tt.expectedResponse, actualResponse)
			}
		})
	}
}

func TestCORSMethodMiddlewareSubrouter(t *testing.T) {
	router := NewRouter().StrictSlash(true)

	subrouter := router.PathPrefix("/test").Subrouter()
	subrouter.HandleFunc("/hello", stringHandler("a")).Methods(http.MethodGet, http.MethodOptions, http.MethodPost)
	subrouter.HandleFunc("/hello/{name}", stringHandler("b")).Methods(http.MethodGet, http.MethodOptions)

	subrouter.Use(CORSMethodMiddleware(subrouter))

	rw := NewRecorder()
	req := newRequest("GET", "/test/hello/asdf")
	router.ServeHTTP(rw, req)

	actualMethods := rw.Header().Get("Access-Control-Allow-Methods")
	expectedMethods := "GET,OPTIONS"
	if actualMethods != expectedMethods {
		t.Fatalf("expected methods %q but got: %q", expectedMethods, actualMethods)
	}
}

func TestMiddlewareOnMultiSubrouter(t *testing.T) {
	first := "first"
	second := "second"
	notFound := "404 not found"

	router := NewRouter()
	firstSubRouter := router.PathPrefix("/").Subrouter()
	secondSubRouter := router.PathPrefix("/").Subrouter()

	router.NotFoundHandler = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
		_, err := rw.Write([]byte(notFound))
		if err != nil {
			t.Fatalf("Failed writing HTTP response: %v", err)
		}
	})

	firstSubRouter.HandleFunc("/first", func(w http.ResponseWriter, r *http.Request) {

	})

	secondSubRouter.HandleFunc("/second", func(w http.ResponseWriter, r *http.Request) {

	})

	firstSubRouter.Use(func(h http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			_, err := w.Write([]byte(first))
			if err != nil {
				t.Fatalf("Failed writing HTTP response: %v", err)
			}
			h.ServeHTTP(w, r)
		})
	})

	secondSubRouter.Use(func(h http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			_, err := w.Write([]byte(second))
			if err != nil {
				t.Fatalf("Failed writing HTTP response: %v", err)
			}
			h.ServeHTTP(w, r)
		})
	})

	t.Run("/first uses first middleware", func(t *testing.T) {
		rw := NewRecorder()
		req := newRequest("GET", "/first")

		router.ServeHTTP(rw, req)
		if rw.Body.String() != first {
			t.Fatalf("Middleware did not run: expected %s middleware to write a response (got %s)", first, rw.Body.String())
		}
	})

	t.Run("/second uses second middleware", func(t *testing.T) {
		rw := NewRecorder()
		req := newRequest("GET", "/second")

		router.ServeHTTP(rw, req)
		if rw.Body.String() != second {
			t.Fatalf("Middleware did not run: expected %s middleware to write a response (got %s)", second, rw.Body.String())
		}
	})

	t.Run("uses not found handler", func(t *testing.T) {
		rw := NewRecorder()
		req := newRequest("GET", "/second/not-exist")

		router.ServeHTTP(rw, req)
		if rw.Body.String() != notFound {
			t.Fatalf("Notfound handler did not run: expected %s for not-exist, (got %s)", notFound, rw.Body.String())
		}
	})
}


================================================
FILE: mux.go
================================================
// Copyright 2012 The Gorilla Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package mux

import (
	"context"
	"errors"
	"fmt"
	"net/http"
	"net/url"
	"path"
	"regexp"
)

var (
	// ErrMethodMismatch is returned when the method in the request does not match
	// the method defined against the route.
	ErrMethodMismatch = errors.New("method is not allowed")
	// ErrNotFound is returned when no route match is found.
	ErrNotFound = errors.New("no matching route was found")
	// RegexpCompileFunc aliases regexp.Compile and enables overriding it.
	// Do not run this function from `init()` in importable packages.
	// Changing this value is not safe for concurrent use.
	RegexpCompileFunc = regexp.Compile
	// ErrMetadataKeyNotFound is returned when the specified metadata key is not present in the map
	ErrMetadataKeyNotFound = errors.New("key not found in metadata")
)

// NewRouter returns a new router instance.
func NewRouter() *Router {
	return &Router{namedRoutes: make(map[string]*Route)}
}

// Router registers routes to be matched and dispatches a handler.
//
// It implements the http.Handler interface, so it can be registered to serve
// requests:
//
//	var router = mux.NewRouter()
//
//	func main() {
//	    http.Handle("/", router)
//	}
//
// Or, for Google App Engine, register it in a init() function:
//
//	func init() {
//	    http.Handle("/", router)
//	}
//
// This will send all incoming requests to the router.
type Router struct {
	// Configurable Handler to be used when no route matches.
	// This can be used to render your own 404 Not Found errors.
	NotFoundHandler http.Handler

	// Configurable Handler to be used when the request method does not match the route.
	// This can be used to render your own 405 Method Not Allowed errors.
	MethodNotAllowedHandler http.Handler

	// Routes to be matched, in order.
	routes []*Route

	// Routes by name for URL building.
	namedRoutes map[string]*Route

	// If true, do not clear the request context after handling the request.
	//
	// Deprecated: No effect, since the context is stored on the request itself.
	KeepContext bool

	// Slice of middlewares to be called after a match is found
	middlewares []middleware

	// configuration shared with `Route`
	routeConf
}

// common route configuration shared between `Router` and `Route`
type routeConf struct {
	// If true, "/path/foo%2Fbar/to" will match the path "/path/{var}/to"
	useEncodedPath bool

	// If true, when the path pattern is "/path/", accessing "/path" will
	// redirect to the former and vice versa.
	strictSlash bool

	// If true, when the path pattern is "/path//to", accessing "/path//to"
	// will not redirect
	skipClean bool

	// If true, the http.Request context will not contain the Route.
	omitRouteFromContext bool

	// if true, the the http.Request context will not contain the router
	omitRouterFromContext bool

	// Manager for the variables from host and path.
	regexp routeRegexpGroup

	// List of matchers.
	matchers []matcher

	// The scheme used when building URLs.
	buildScheme string

	buildVarsFunc BuildVarsFunc
}

// returns an effective deep copy of `routeConf`
func copyRouteConf(r routeConf) routeConf {
	c := r

	if r.regexp.path != nil {
		c.regexp.path = copyRouteRegexp(r.regexp.path)
	}

	if r.regexp.host != nil {
		c.regexp.host = copyRouteRegexp(r.regexp.host)
	}

	c.regexp.queries = make([]*routeRegexp, 0, len(r.regexp.queries))
	for _, q := range r.regexp.queries {
		c.regexp.queries = append(c.regexp.queries, copyRouteRegexp(q))
	}

	c.matchers = make([]matcher, len(r.matchers))
	copy(c.matchers, r.matchers)

	return c
}

func copyRouteRegexp(r *routeRegexp) *routeRegexp {
	c := *r
	return &c
}

// Match attempts to match the given request against the router's registered routes.
//
// If the request matches a route of this router or one of its subrouters the Route,
// Handler, and Vars fields of the the match argument are filled and this function
// returns true.
//
// If the request does not match any of this router's or its subrouters' routes
// then this function returns false. If available, a reason for the match failure
// will be filled in the match argument's MatchErr field. If the match failure type
// (eg: not found) has a registered handler, the handler is assigned to the Handler
// field of the match argument.
func (r *Router) Match(req *http.Request, match *RouteMatch) bool {
	for _, route := range r.routes {
		if route.Match(req, match) {
			// Build middleware chain if no error was found
			if match.MatchErr == nil {
				for i := len(r.middlewares) - 1; i >= 0; i-- {
					match.Handler = r.middlewares[i].Middleware(match.Handler)
				}
			}
			return true
		}
	}

	if match.MatchErr == ErrMethodMismatch {
		if r.MethodNotAllowedHandler != nil {
			match.Handler = r.MethodNotAllowedHandler
			return true
		}

		return false
	}

	// Closest match for a router (includes sub-routers)
	if r.NotFoundHandler != nil {
		match.Handler = r.NotFoundHandler
		match.MatchErr = ErrNotFound
		return true
	}

	match.MatchErr = ErrNotFound
	return false
}

// ServeHTTP dispatches the handler registered in the matched route.
//
// When there is a match, the route variables can be retrieved calling
// mux.Vars(request).
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	if !r.skipClean {
		path := req.URL.Path
		if r.useEncodedPath {
			path = req.URL.EscapedPath()
		}
		// Clean path to canonical form and redirect.
		if p := cleanPath(path); p != path {
			w.Header().Set("Location", replaceURLPath(req.URL, p))
			w.WriteHeader(http.StatusMovedPermanently)
			return
		}
	}
	var match RouteMatch
	var handler http.Handler
	if r.Match(req, &match) {
		handler = match.Handler
		if handler != nil {
			// Populate context for custom handlers
			if r.omitRouteFromContext {
				// Only populate the match vars (if any) into the context.
				req = requestWithVars(req, match.Vars)
			} else {
				req = requestWithRouteAndVars(req, match.Route, match.Vars)
			}

			if !r.omitRouterFromContext {
				req = requestWithRouter(req, r)
			}
		}
	}

	if handler == nil && match.MatchErr == ErrMethodMismatch {
		handler = methodNotAllowedHandler()
	}

	if handler == nil {
		handler = http.NotFoundHandler()
	}

	handler.ServeHTTP(w, req)
}

// Get returns a route registered with the given name.
func (r *Router) Get(name string) *Route {
	return r.namedRoutes[name]
}

// GetRoute returns a route registered with the given name. This method
// was renamed to Get() and remains here for backwards compatibility.
func (r *Router) GetRoute(name string) *Route {
	return r.namedRoutes[name]
}

// StrictSlash defines the trailing slash behavior for new routes. The initial
// value is false.
//
// When true, if the route path is "/path/", accessing "/path" will perform a redirect
// to the former and vice versa. In other words, your application will always
// see the path as specified in the route.
//
// When false, if the route path is "/path", accessing "/path/" will not match
// this route and vice versa.
//
// The redirect is a HTTP 301 (Moved Permanently). Note that when this is set for
// routes with a non-idempotent method (e.g. POST, PUT), the subsequent redirected
// request will be made as a GET by most clients. Use middleware or client settings
// to modify this behaviour as needed.
//
// Special case: when a route sets a path prefix using the PathPrefix() method,
// strict slash is ignored for that route because the redirect behavior can't
// be determined from a prefix alone. However, any subrouters created from that
// route inherit the original StrictSlash setting.
func (r *Router) StrictSlash(value bool) *Router {
	r.strictSlash = value
	return r
}

// SkipClean defines the path cleaning behaviour for new routes. The initial
// value is false. Users should be careful about which routes are not cleaned
//
// When true, if the route path is "/path//to", it will remain with the double
// slash. This is helpful if you have a route like: /fetch/http://xkcd.com/534/
//
// When false, the path will be cleaned, so /fetch/http://xkcd.com/534/ will
// become /fetch/http/xkcd.com/534
func (r *Router) SkipClean(value bool) *Router {
	r.skipClean = value
	return r
}

// OmitRouteFromContext defines the behavior of omitting the Route from the
//
//	http.Request context.
//
// CurrentRoute will yield nil with this option.
func (r *Router) OmitRouteFromContext(value bool) *Router {
	r.omitRouteFromContext = value
	return r
}

// OmitRouterFromContext defines the behavior of omitting the Router from the
// http.Request context.
//
// RouterFromRequest will yield nil with this option.
func (r *Router) OmitRouterFromContext(value bool) *Router {
	r.omitRouterFromContext = value
	return r
}

// UseEncodedPath tells the router to match the encoded original path
// to the routes.
// For eg. "/path/foo%2Fbar/to" will match the path "/path/{var}/to".
//
// If not called, the router will match the unencoded path to the routes.
// For eg. "/path/foo%2Fbar/to" will match the path "/path/foo/bar/to"
func (r *Router) UseEncodedPath() *Router {
	r.useEncodedPath = true
	return r
}

// ----------------------------------------------------------------------------
// Route factories
// ----------------------------------------------------------------------------

// NewRoute registers an empty route.
func (r *Router) NewRoute() *Route {
	// initialize a route with a copy of the parent router's configuration
	route := &Route{routeConf: copyRouteConf(r.routeConf), namedRoutes: r.namedRoutes}
	r.routes = append(r.routes, route)
	return route
}

// Name registers a new route with a name.
// See Route.Name().
func (r *Router) Name(name string) *Route {
	return r.NewRoute().Name(name)
}

// Handle registers a new route with a matcher for the URL path.
// See Route.Path() and Route.Handler().
func (r *Router) Handle(path string, handler http.Handler) *Route {
	return r.NewRoute().Path(path).Handler(handler)
}

// HandleFunc registers a new route with a matcher for the URL path.
// See Route.Path() and Route.HandlerFunc().
func (r *Router) HandleFunc(path string, f func(http.ResponseWriter,
	*http.Request)) *Route {
	return r.NewRoute().Path(path).HandlerFunc(f)
}

// Headers registers a new route with a matcher for request header values.
// See Route.Headers().
func (r *Router) Headers(pairs ...string) *Route {
	return r.NewRoute().Headers(pairs...)
}

// Host registers a new route with a matcher for the URL host.
// See Route.Host().
func (r *Router) Host(tpl string) *Route {
	return r.NewRoute().Host(tpl)
}

// MatcherFunc registers a new route with a custom matcher function.
// See Route.MatcherFunc().
func (r *Router) MatcherFunc(f MatcherFunc) *Route {
	return r.NewRoute().MatcherFunc(f)
}

// Methods registers a new route with a matcher for HTTP methods.
// See Route.Methods().
func (r *Router) Methods(methods ...string) *Route {
	return r.NewRoute().Methods(methods...)
}

// Path registers a new route with a matcher for the URL path.
// See Route.Path().
func (r *Router) Path(tpl string) *Route {
	return r.NewRoute().Path(tpl)
}

// PathPrefix registers a new route with a matcher for the URL path prefix.
// See Route.PathPrefix().
func (r *Router) PathPrefix(tpl string) *Route {
	return r.NewRoute().PathPrefix(tpl)
}

// Queries registers a new route with a matcher for URL query values.
// See Route.Queries().
func (r *Router) Queries(pairs ...string) *Route {
	return r.NewRoute().Queries(pairs...)
}

// Schemes registers a new route with a matcher for URL schemes.
// See Route.Schemes().
func (r *Router) Schemes(schemes ...string) *Route {
	return r.NewRoute().Schemes(schemes...)
}

// BuildVarsFunc registers a new route with a custom function for modifying
// route variables before building a URL.
func (r *Router) BuildVarsFunc(f BuildVarsFunc) *Route {
	return r.NewRoute().BuildVarsFunc(f)
}

// Walk walks the router and all its sub-routers, calling walkFn for each route
// in the tree. The routes are walked in the order they were added. Sub-routers
// are explored depth-first.
func (r *Router) Walk(walkFn WalkFunc) error {
	return r.walk(walkFn, []*Route{})
}

// SkipRouter is used as a return value from WalkFuncs to indicate that the
// router that walk is about to descend down to should be skipped.
var SkipRouter = errors.New("skip this router")

// WalkFunc is the type of the function called for each route visited by Walk.
// At every invocation, it is given the current route, and the current router,
// and a list of ancestor routes that lead to the current route.
type WalkFunc func(route *Route, router *Router, ancestors []*Route) error

func (r *Router) walk(walkFn WalkFunc, ancestors []*Route) error {
	for _, t := range r.routes {
		err := walkFn(t, r, ancestors)
		if err == SkipRouter {
			continue
		}
		if err != nil {
			return err
		}
		for _, sr := range t.matchers {
			if h, ok := sr.(*Router); ok {
				ancestors = append(ancestors, t)
				err := h.walk(walkFn, ancestors)
				if err != nil {
					return err
				}
				ancestors = ancestors[:len(ancestors)-1]
			}
		}
		if h, ok := t.handler.(*Router); ok {
			ancestors = append(ancestors, t)
			err := h.walk(walkFn, ancestors)
			if err != nil {
				return err
			}
			ancestors = ancestors[:len(ancestors)-1]
		}
	}
	return nil
}

// ----------------------------------------------------------------------------
// Context
// ----------------------------------------------------------------------------

// RouteMatch stores information about a matched route.
type RouteMatch struct {
	Route   *Route
	Handler http.Handler
	Vars    map[string]string

	// MatchErr is set to appropriate matching error
	// It is set to ErrMethodMismatch if there is a mismatch in
	// the request method and route method
	MatchErr error
}

type contextKey int

const (
	varsKey contextKey = iota
	routeKey
	routerKey
)

// Vars returns the route variables for the current request, if any.
func Vars(r *http.Request) map[string]string {
	if rv := r.Context().Value(varsKey); rv != nil {
		return rv.(map[string]string)
	}
	return nil
}

// CurrentRoute returns the matched route for the current request, if any.
// This only works when called inside the handler of the matched route
// because the matched route is stored in the request context which is cleared
// after the handler returns.
func CurrentRoute(r *http.Request) *Route {
	if rv := r.Context().Value(routeKey); rv != nil {
		return rv.(*Route)
	}
	return nil
}

func CurrentRouter(r *http.Request) *Router {
	if rv := r.Context().Value(routerKey); rv != nil {
		return rv.(*Router)
	}
	return nil
}

// requestWithVars adds the matched vars to the request ctx.
// It shortcuts the operation when the vars are empty.
func requestWithVars(r *http.Request, vars map[string]string) *http.Request {
	if len(vars) == 0 {
		return r
	}
	ctx := context.WithValue(r.Context(), varsKey, vars)
	return r.WithContext(ctx)
}

// requestWithRouteAndVars adds the matched route and vars to the request ctx.
// It saves extra allocations in cloning the request once and skipping the
//
//	population of empty vars, which in turn mux.Vars can handle gracefully.
func requestWithRouteAndVars(r *http.Request, route *Route, vars map[string]string) *http.Request {
	ctx := context.WithValue(r.Context(), routeKey, route)
	if len(vars) > 0 {
		ctx = context.WithValue(ctx, varsKey, vars)
	}
	return r.WithContext(ctx)
}

func requestWithRouter(r *http.Request, router *Router) *http.Request {
	ctx := context.WithValue(r.Context(), routerKey, router)
	return r.WithContext(ctx)
}

// ----------------------------------------------------------------------------
// Helpers
// ----------------------------------------------------------------------------

// cleanPath returns the canonical path for p, eliminating . and .. elements.
// Borrowed from the net/http package.
func cleanPath(p string) string {
	if p == "" {
		return "/"
	}
	if p[0] != '/' {
		p = "/" + p
	}
	np := path.Clean(p)
	// path.Clean removes trailing slash except for root;
	// put the trailing slash back if necessary.
	if p[len(p)-1] == '/' && np != "/" {
		np += "/"
	}

	return np
}

// replaceURLPath prints an url.URL with a different path.
func replaceURLPath(u *url.URL, p string) string {
	// Operate on a copy of the request url.
	u2 := *u
	u2.Path = p
	return u2.String()
}

// uniqueVars returns an error if two slices contain duplicated strings.
func uniqueVars(s1, s2 []string) error {
	for _, v1 := range s1 {
		for _, v2 := range s2 {
			if v1 == v2 {
				return fmt.Errorf("mux: duplicated route variable %q", v2)
			}
		}
	}
	return nil
}

// checkPairs returns the count of strings passed in, and an error if
// the count is not an even number.
func checkPairs(pairs ...string) (int, error) {
	length := len(pairs)
	if length%2 != 0 {
		return length, fmt.Errorf(
			"mux: number of parameters must be multiple of 2, got %v", pairs)
	}
	return length, nil
}

// mapFromPairsToString converts variadic string parameters to a
// string to string map.
func mapFromPairsToString(pairs ...string) (map[string]string, error) {
	length, err := checkPairs(pairs...)
	if err != nil {
		return nil, err
	}
	m := make(map[string]string, length/2)
	for i := 0; i < length; i += 2 {
		m[pairs[i]] = pairs[i+1]
	}
	return m, nil
}

// mapFromPairsToRegex converts variadic string parameters to a
// string to regex map.
func mapFromPairsToRegex(pairs ...string) (map[string]*regexp.Regexp, error) {
	length, err := checkPairs(pairs...)
	if err != nil {
		return nil, err
	}
	m := make(map[string]*regexp.Regexp, length/2)
	for i := 0; i < length; i += 2 {
		regex, err := RegexpCompileFunc(pairs[i+1])
		if err != nil {
			return nil, err
		}
		m[pairs[i]] = regex
	}
	return m, nil
}

// matchInArray returns true if the given string value is in the array.
func matchInArray(arr []string, value string) bool {
	for _, v := range arr {
		if v == value {
			return true
		}
	}
	return false
}

// matchMapWithString returns true if the given key/value pairs exist in a given map.
func matchMapWithString(toCheck map[string]string, toMatch map[string][]string, canonicalKey bool) bool {
	for k, v := range toCheck {
		// Check if key exists.
		if canonicalKey {
			k = http.CanonicalHeaderKey(k)
		}
		if values := toMatch[k]; values == nil {
			return false
		} else if v != "" {
			// If value was defined as an empty string we only check that the
			// key exists. Otherwise we also check for equality.
			valueExists := false
			for _, value := range values {
				if v == value {
					valueExists = true
					break
				}
			}
			if !valueExists {
				return false
			}
		}
	}
	return true
}

// matchMapWithRegex returns true if the given key/value pairs exist in a given map compiled against
// the given regex
func matchMapWithRegex(toCheck map[string]*regexp.Regexp, toMatch map[string][]string, canonicalKey bool) bool {
	for k, v := range toCheck {
		// Check if key exists.
		if canonicalKey {
			k = http.CanonicalHeaderKey(k)
		}
		if values := toMatch[k]; values == nil {
			return false
		} else if v != nil {
			// If value was defined as an empty string we only check that the
			// key exists. Otherwise we also check for equality.
			valueExists := false
			for _, value := range values {
				if v.MatchString(value) {
					valueExists = true
					break
				}
			}
			if !valueExists {
				return false
			}
		}
	}
	return true
}

// methodNotAllowed replies to the request with an HTTP status code 405.
func methodNotAllowed(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusMethodNotAllowed)
}

// methodNotAllowedHandler returns a simple request handler
// that replies to each request with a status code 405.
func methodNotAllowedHandler() http.Handler { return http.HandlerFunc(methodNotAllowed) }


================================================
FILE: mux_httpserver_test.go
================================================
//go:build go1.9
// +build go1.9

package mux

import (
	"bytes"
	"io"
	"net/http"
	"net/http/httptest"
	"testing"
)

func TestSchemeMatchers(t *testing.T) {
	router := NewRouter()
	router.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
		_, err := rw.Write([]byte("hello http world"))
		if err != nil {
			t.Fatalf("Failed writing HTTP response: %v", err)
		}
	}).Schemes("http")
	router.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
		_, err := rw.Write([]byte("hello https world"))
		if err != nil {
			t.Fatalf("Failed writing HTTP response: %v", err)
		}
	}).Schemes("https")

	assertResponseBody := func(t *testing.T, s *httptest.Server, expectedBody string) {
		resp, err := s.Client().Get(s.URL)
		if err != nil {
			t.Fatalf("unexpected error getting from server: %v", err)
		}
		if resp.StatusCode != 200 {
			t.Fatalf("expected a status code of 200, got %v", resp.StatusCode)
		}
		body, err := io.ReadAll(resp.Body)
		if err != nil {
			t.Fatalf("unexpected error reading body: %v", err)
		}
		if !bytes.Equal(body, []byte(expectedBody)) {
			t.Fatalf("response should be hello world, was: %q", string(body))
		}
	}

	t.Run("httpServer", func(t *testing.T) {
		s := httptest.NewServer(router)
		defer s.Close()
		assertResponseBody(t, s, "hello http world")
	})
	t.Run("httpsServer", func(t *testing.T) {
		s := httptest.NewTLSServer(router)
		defer s.Close()
		assertResponseBody(t, s, "hello https world")
	})
}


================================================
FILE: mux_test.go
================================================
// Copyright 2012 The Gorilla Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package mux

import (
	"bufio"
	"bytes"
	"context"
	"errors"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/http/httptest"
	"net/url"
	"reflect"
	"strings"
	"testing"
	"time"
)

func (r *Route) GoString() string {
	matchers := make([]string, len(r.matchers))
	for i, m := range r.matchers {
		matchers[i] = fmt.Sprintf("%#v", m)
	}
	return fmt.Sprintf("&Route{matchers:[]matcher{%s}}", strings.Join(matchers, ", "))
}

func (r *routeRegexp) GoString() string {
	return 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)
}

type routeTest struct {
	title           string            // title of the test
	route           *Route            // the route being tested
	request         *http.Request     // a request to test the route
	vars            map[string]string // the expected vars of the match
	scheme          string            // the expected scheme of the built URL
	host            string            // the expected host of the built URL
	path            string            // the expected path of the built URL
	query           string            // the expected query string of the built URL
	pathTemplate    string            // the expected path template of the route
	hostTemplate    string            // the expected host template of the route
	queriesTemplate string            // the expected query template of the route
	methods         []string          // the expected route methods
	pathRegexp      string            // the expected path regexp
	queriesRegexp   string            // the expected query regexp
	shouldMatch     bool              // whether the request is expected to match the route at all
	shouldRedirect  bool              // whether the request should result in a redirect
}

func TestHost(t *testing.T) {

	tests := []routeTest{
		{
			title:       "Host route match",
			route:       new(Route).Host("aaa.bbb.ccc"),
			request:     newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
			vars:        nil,
			host:        "aaa.bbb.ccc",
			path:        "",
			shouldMatch: true,
		},
		{
			title:       "Host route, wrong host in request URL",
			route:       new(Route).Host("aaa.bbb.ccc"),
			request:     newRequest("GET", "http://aaa.222.ccc/111/222/333"),
			vars:        nil,
			host:        "aaa.bbb.ccc",
			path:        "",
			shouldMatch: false,
		},
		{
			title:       "Host route with port, match",
			route:       new(Route).Host("aaa.bbb.ccc:1234"),
			request:     newRequest("GET", "http://aaa.bbb.ccc:1234/111/222/333"),
			vars:        nil,
			host:        "aaa.bbb.ccc:1234",
			path:        "",
			shouldMatch: true,
		},
		{
			title:       "Host route with port, wrong port in request URL",
			route:       new(Route).Host("aaa.bbb.ccc:1234"),
			request:     newRequest("GET", "http://aaa.bbb.ccc:9999/111/222/333"),
			vars:        nil,
			host:        "aaa.bbb.ccc:1234",
			path:        "",
			shouldMatch: false,
		},
		{
			title:       "Host route, match with host in request header",
			route:       new(Route).Host("aaa.bbb.ccc"),
			request:     newRequestHost("GET", "/111/222/333", "aaa.bbb.ccc"),
			vars:        nil,
			host:        "aaa.bbb.ccc",
			path:        "",
			shouldMatch: true,
		},
		{
			title:       "Host route, wrong host in request header",
			route:       new(Route).Host("aaa.bbb.ccc"),
			request:     newRequestHost("GET", "/111/222/333", "aaa.222.ccc"),
			vars:        nil,
			host:        "aaa.bbb.ccc",
			path:        "",
			shouldMatch: false,
		},
		{
			title:       "Host route with port, match with request header",
			route:       new(Route).Host("aaa.bbb.ccc:1234"),
			request:     newRequestHost("GET", "/111/222/333", "aaa.bbb.ccc:1234"),
			vars:        nil,
			host:        "aaa.bbb.ccc:1234",
			path:        "",
			shouldMatch: true,
		},
		{
			title:       "Host route with port, wrong host in request header",
			route:       new(Route).Host("aaa.bbb.ccc:1234"),
			request:     newRequestHost("GET", "/111/222/333", "aaa.bbb.ccc:9999"),
			vars:        nil,
			host:        "aaa.bbb.ccc:1234",
			path:        "",
			shouldMatch: false,
		},
		{
			title:        "Host route with pattern, match with request header",
			route:        new(Route).Host("aaa.{v1:[a-z]{3}}.ccc:1{v2:(?:23|4)}"),
			request:      newRequestHost("GET", "/111/222/333", "aaa.bbb.ccc:123"),
			vars:         map[string]string{"v1": "bbb", "v2": "23"},
			host:         "aaa.bbb.ccc:123",
			path:         "",
			hostTemplate: `aaa.{v1:[a-z]{3}}.ccc:1{v2:(?:23|4)}`,
			shouldMatch:  true,
		},
		{
			title:        "Host route with pattern, match",
			route:        new(Route).Host("aaa.{v1:[a-z]{3}}.ccc"),
			request:      newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
			vars:         map[string]string{"v1": "bbb"},
			host:         "aaa.bbb.ccc",
			path:         "",
			hostTemplate: `aaa.{v1:[a-z]{3}}.ccc`,
			shouldMatch:  true,
		},
		{
			title:        "Host route with pattern, additional capturing group, match",
			route:        new(Route).Host("aaa.{v1:[a-z]{2}(?:b|c)}.ccc"),
			request:      newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
			vars:         map[string]string{"v1": "bbb"},
			host:         "aaa.bbb.ccc",
			path:         "",
			hostTemplate: `aaa.{v1:[a-z]{2}(?:b|c)}.ccc`,
			shouldMatch:  true,
		},
		{
			title:        "Host route with pattern, wrong host in request URL",
			route:        new(Route).Host("aaa.{v1:[a-z]{3}}.ccc"),
			request:      newRequest("GET", "http://aaa.222.ccc/111/222/333"),
			vars:         map[string]string{"v1": "bbb"},
			host:         "aaa.bbb.ccc",
			path:         "",
			hostTemplate: `aaa.{v1:[a-z]{3}}.ccc`,
			shouldMatch:  false,
		},
		{
			title:        "Host route with multiple patterns, match",
			route:        new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}"),
			request:      newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
			vars:         map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc"},
			host:         "aaa.bbb.ccc",
			path:         "",
			hostTemplate: `{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}`,
			shouldMatch:  true,
		},
		{
			title:        "Host route with multiple patterns, wrong host in request URL",
			route:        new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}"),
			request:      newRequest("GET", "http://aaa.222.ccc/111/222/333"),
			vars:         map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc"},
			host:         "aaa.bbb.ccc",
			path:         "",
			hostTemplate: `{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}`,
			shouldMatch:  false,
		},
		{
			title:        "Host route with hyphenated name and pattern, match",
			route:        new(Route).Host("aaa.{v-1:[a-z]{3}}.ccc"),
			request:      newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
			vars:         map[string]string{"v-1": "bbb"},
			host:         "aaa.bbb.ccc",
			path:         "",
			hostTemplate: `aaa.{v-1:[a-z]{3}}.ccc`,
			shouldMatch:  true,
		},
		{
			title:        "Host route with hyphenated name and pattern, additional capturing group, match",
			route:        new(Route).Host("aaa.{v-1:[a-z]{2}(?:b|c)}.ccc"),
			request:      newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
			vars:         map[string]string{"v-1": "bbb"},
			host:         "aaa.bbb.ccc",
			path:         "",
			hostTemplate: `aaa.{v-1:[a-z]{2}(?:b|c)}.ccc`,
			shouldMatch:  true,
		},
		{
			title:        "Host route with multiple hyphenated names and patterns, match",
			route:        new(Route).Host("{v-1:[a-z]{3}}.{v-2:[a-z]{3}}.{v-3:[a-z]{3}}"),
			request:      newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
			vars:         map[string]string{"v-1": "aaa", "v-2": "bbb", "v-3": "ccc"},
			host:         "aaa.bbb.ccc",
			path:         "",
			hostTemplate: `{v-1:[a-z]{3}}.{v-2:[a-z]{3}}.{v-3:[a-z]{3}}`,
			shouldMatch:  true,
		},
	}
	for _, test := range tests {
		t.Run(test.title, func(t *testing.T) {
			testRoute(t, test)
			testTemplate(t, test)
		})
	}
}

func TestPath(t *testing.T) {
	tests := []routeTest{
		{
			title:       "Path route, match",
			route:       new(Route).Path("/111/222/333"),
			request:     newRequest("GET", "http://localhost/111/222/333"),
			vars:        nil,
			host:        "",
			path:        "/111/222/333",
			shouldMatch: true,
		},
		{
			title:       "Path route, match with trailing slash in request and path",
			route:       new(Route).Path("/111/"),
			request:     newRequest("GET", "http://localhost/111/"),
			vars:        nil,
			host:        "",
			path:        "/111/",
			shouldMatch: true,
		},
		{
			title:        "Path route, do not match with trailing slash in path",
			route:        new(Route).Path("/111/"),
			request:      newRequest("GET", "http://localhost/111"),
			vars:         nil,
			host:         "",
			path:         "/111",
			pathTemplate: `/111/`,
			pathRegexp:   `^/111/$`,
			shouldMatch:  false,
		},
		{
			title:        "Path route, do not match with trailing slash in request",
			route:        new(Route).Path("/111"),
			request:      newRequest("GET", "http://localhost/111/"),
			vars:         nil,
			host:         "",
			path:         "/111/",
			pathTemplate: `/111`,
			shouldMatch:  false,
		},
		{
			title:        "Path route, match root with no host",
			route:        new(Route).Path("/"),
			request:      newRequest("GET", "/"),
			vars:         nil,
			host:         "",
			path:         "/",
			pathTemplate: `/`,
			pathRegexp:   `^/$`,
			shouldMatch:  true,
		},
		{
			title: "Path route, match root with no host, App Engine format",
			route: new(Route).Path("/"),
			request: func() *http.Request {
				r := newRequest("GET", "http://localhost/")
				r.RequestURI = "/"
				return r
			}(),
			vars:         nil,
			host:         "",
			path:         "/",
			pathTemplate: `/`,
			shouldMatch:  true,
		},
		{
			title:       "Path route, wrong path in request in request URL",
			route:       new(Route).Path("/111/222/333"),
			request:     newRequest("GET", "http://localhost/1/2/3"),
			vars:        nil,
			host:        "",
			path:        "/111/222/333",
			shouldMatch: false,
		},
		{
			title:        "Path route with pattern, match",
			route:        new(Route).Path("/111/{v1:[0-9]{3}}/333"),
			request:      newRequest("GET", "http://localhost/111/222/333"),
			vars:         map[string]string{"v1": "222"},
			host:         "",
			path:         "/111/222/333",
			pathTemplate: `/111/{v1:[0-9]{3}}/333`,
			shouldMatch:  true,
		},
		{
			title:        "Path route with pattern, URL in request does not match",
			route:        new(Route).Path("/111/{v1:[0-9]{3}}/333"),
			request:      newRequest("GET", "http://localhost/111/aaa/333"),
			vars:         map[string]string{"v1": "222"},
			host:         "",
			path:         "/111/222/333",
			pathTemplate: `/111/{v1:[0-9]{3}}/333`,
			pathRegexp:   `^/111/(?P<v0>[0-9]{3})/333$`,
			shouldMatch:  false,
		},
		{
			title:        "Path route with multiple patterns, match",
			route:        new(Route).Path("/{v1:[0-9]{3}}/{v2:[0-9]{3}}/{v3:[0-9]{3}}"),
			request:      newRequest("GET", "http://localhost/111/222/333"),
			vars:         map[string]string{"v1": "111", "v2": "222", "v3": "333"},
			host:         "",
			path:         "/111/222/333",
			pathTemplate: `/{v1:[0-9]{3}}/{v2:[0-9]{3}}/{v3:[0-9]{3}}`,
			pathRegexp:   `^/(?P<v0>[0-9]{3})/(?P<v1>[0-9]{3})/(?P<v2>[0-9]{3})$`,
			shouldMatch:  true,
		},
		{
			title:        "Path route with multiple patterns, URL in request does not match",
			route:        new(Route).Path("/{v1:[0-9]{3}}/{v2:[0-9]{3}}/{v3:[0-9]{3}}"),
			request:      newRequest("GET", "http://localhost/111/aaa/333"),
			vars:         map[string]string{"v1": "111", "v2": "222", "v3": "333"},
			host:         "",
			path:         "/111/222/333",
			pathTemplate: `/{v1:[0-9]{3}}/{v2:[0-9]{3}}/{v3:[0-9]{3}}`,
			pathRegexp:   `^/(?P<v0>[0-9]{3})/(?P<v1>[0-9]{3})/(?P<v2>[0-9]{3})$`,
			shouldMatch:  false,
		},
		{
			title:        "Path route with multiple patterns with pipe, match",
			route:        new(Route).Path("/{category:a|(?:b/c)}/{product}/{id:[0-9]+}"),
			request:      newRequest("GET", "http://localhost/a/product_name/1"),
			vars:         map[string]string{"category": "a", "product": "product_name", "id": "1"},
			host:         "",
			path:         "/a/product_name/1",
			pathTemplate: `/{category:a|(?:b/c)}/{product}/{id:[0-9]+}`,
			pathRegexp:   `^/(?P<v0>a|(?:b/c))/(?P<v1>[^/]+)/(?P<v2>[0-9]+)$`,
			shouldMatch:  true,
		},
		{
			title:        "Path route with hyphenated name and pattern, match",
			route:        new(Route).Path("/111/{v-1:[0-9]{3}}/333"),
			request:      newRequest("GET", "http://localhost/111/222/333"),
			vars:         map[string]string{"v-1": "222"},
			host:         "",
			path:         "/111/222/333",
			pathTemplate: `/111/{v-1:[0-9]{3}}/333`,
			pathRegexp:   `^/111/(?P<v0>[0-9]{3})/333$`,
			shouldMatch:  true,
		},
		{
			title:        "Path route with multiple hyphenated names and patterns, match",
			route:        new(Route).Path("/{v-1:[0-9]{3}}/{v-2:[0-9]{3}}/{v-3:[0-9]{3}}"),
			request:      newRequest("GET", "http://localhost/111/222/333"),
			vars:         map[string]string{"v-1": "111", "v-2": "222", "v-3": "333"},
			host:         "",
			path:         "/111/222/333",
			pathTemplate: `/{v-1:[0-9]{3}}/{v-2:[0-9]{3}}/{v-3:[0-9]{3}}`,
			pathRegexp:   `^/(?P<v0>[0-9]{3})/(?P<v1>[0-9]{3})/(?P<v2>[0-9]{3})$`,
			shouldMatch:  true,
		},
		{
			title:        "Path route with multiple hyphenated names and patterns with pipe, match",
			route:        new(Route).Path("/{product-category:a|(?:b/c)}/{product-name}/{product-id:[0-9]+}"),
			request:      newRequest("GET", "http://localhost/a/product_name/1"),
			vars:         map[string]string{"product-category": "a", "product-name": "product_name", "product-id": "1"},
			host:         "",
			path:         "/a/product_name/1",
			pathTemplate: `/{product-category:a|(?:b/c)}/{product-name}/{product-id:[0-9]+}`,
			pathRegexp:   `^/(?P<v0>a|(?:b/c))/(?P<v1>[^/]+)/(?P<v2>[0-9]+)$`,
			shouldMatch:  true,
		},
		{
			title:        "Path route with multiple hyphenated names and patterns with pipe and case insensitive, match",
			route:        new(Route).Path("/{type:(?i:daily|mini|variety)}-{date:\\d{4,4}-\\d{2,2}-\\d{2,2}}"),
			request:      newRequest("GET", "http://localhost/daily-2016-01-01"),
			vars:         map[string]string{"type": "daily", "date": "2016-01-01"},
			host:         "",
			path:         "/daily-2016-01-01",
			pathTemplate: `/{type:(?i:daily|mini|variety)}-{date:\d{4,4}-\d{2,2}-\d{2,2}}`,
			pathRegexp:   `^/(?P<v0>(?i:daily|mini|variety))-(?P<v1>\d{4,4}-\d{2,2}-\d{2,2})$`,
			shouldMatch:  true,
		},
		{
			title:        "Path route with empty match right after other match",
			route:        new(Route).Path(`/{v1:[0-9]*}{v2:[a-z]*}/{v3:[0-9]*}`),
			request:      newRequest("GET", "http://localhost/111/222"),
			vars:         map[string]string{"v1": "111", "v2": "", "v3": "222"},
			host:         "",
			path:         "/111/222",
			pathTemplate: `/{v1:[0-9]*}{v2:[a-z]*}/{v3:[0-9]*}`,
			pathRegexp:   `^/(?P<v0>[0-9]*)(?P<v1>[a-z]*)/(?P<v2>[0-9]*)$`,
			shouldMatch:  true,
		},
		{
			title:        "Path route with single pattern with pipe, match",
			route:        new(Route).Path("/{category:a|b/c}"),
			request:      newRequest("GET", "http://localhost/a"),
			vars:         map[string]string{"category": "a"},
			host:         "",
			path:         "/a",
			pathTemplate: `/{category:a|b/c}`,
			shouldMatch:  true,
		},
		{
			title:        "Path route with single pattern with pipe, match",
			route:        new(Route).Path("/{category:a|b/c}"),
			request:      newRequest("GET", "http://localhost/b/c"),
			vars:         map[string]string{"category": "b/c"},
			host:         "",
			path:         "/b/c",
			pathTemplate: `/{category:a|b/c}`,
			shouldMatch:  true,
		},
		{
			title:        "Path route with multiple patterns with pipe, match",
			route:        new(Route).Path("/{category:a|b/c}/{product}/{id:[0-9]+}"),
			request:      newRequest("GET", "http://localhost/a/product_name/1"),
			vars:         map[string]string{"category": "a", "product": "product_name", "id": "1"},
			host:         "",
			path:         "/a/product_name/1",
			pathTemplate: `/{category:a|b/c}/{product}/{id:[0-9]+}`,
			shouldMatch:  true,
		},
		{
			title:        "Path route with multiple patterns with pipe, match",
			route:        new(Route).Path("/{category:a|b/c}/{product}/{id:[0-9]+}"),
			request:      newRequest("GET", "http://localhost/b/c/product_name/1"),
			vars:         map[string]string{"category": "b/c", "product": "product_name", "id": "1"},
			host:         "",
			path:         "/b/c/product_name/1",
			pathTemplate: `/{category:a|b/c}/{product}/{id:[0-9]+}`,
			shouldMatch:  true,
		},
	}

	for _, test := range tests {
		t.Run(test.title, func(t *testing.T) {
			testRoute(t, test)
			testTemplate(t, test)
			testUseEscapedRoute(t, test)
			testRegexp(t, test)
		})
	}
}

func TestPathPrefix(t *testing.T) {
	tests := []routeTest{
		{
			title:       "PathPrefix route, match",
			route:       new(Route).PathPrefix("/111"),
			request:     newRequest("GET", "http://localhost/111/222/333"),
			vars:        nil,
			host:        "",
			path:        "/111",
			shouldMatch: true,
		},
		{
			title:       "PathPrefix route, match substring",
			route:       new(Route).PathPrefix("/1"),
			request:     newRequest("GET", "http://localhost/111/222/333"),
			vars:        nil,
			host:        "",
			path:        "/1",
			shouldMatch: true,
		},
		{
			title:       "PathPrefix route, URL prefix in request does not match",
			route:       new(Route).PathPrefix("/111"),
			request:     newRequest("GET", "http://localhost/1/2/3"),
			vars:        nil,
			host:        "",
			path:        "/111",
			shouldMatch: false,
		},
		{
			title:        "PathPrefix route with pattern, match",
			route:        new(Route).PathPrefix("/111/{v1:[0-9]{3}}"),
			request:      newRequest("GET", "http://localhost/111/222/333"),
			vars:         map[string]string{"v1": "222"},
			host:         "",
			path:         "/111/222",
			pathTemplate: `/111/{v1:[0-9]{3}}`,
			shouldMatch:  true,
		},
		{
			title:        "PathPrefix route with pattern, URL prefix in request does not match",
			route:        new(Route).PathPrefix("/111/{v1:[0-9]{3}}"),
			request:      newRequest("GET", "http://localhost/111/aaa/333"),
			vars:         map[string]string{"v1": "222"},
			host:         "",
			path:         "/111/222",
			pathTemplate: `/111/{v1:[0-9]{3}}`,
			shouldMatch:  false,
		},
		{
			title:        "PathPrefix route with multiple patterns, match",
			route:        new(Route).PathPrefix("/{v1:[0-9]{3}}/{v2:[0-9]{3}}"),
			request:      newRequest("GET", "http://localhost/111/222/333"),
			vars:         map[string]string{"v1": "111", "v2": "222"},
			host:         "",
			path:         "/111/222",
			pathTemplate: `/{v1:[0-9]{3}}/{v2:[0-9]{3}}`,
			shouldMatch:  true,
		},
		{
			title:        "PathPrefix route with multiple patterns, URL prefix in request does not match",
			route:        new(Route).PathPrefix("/{v1:[0-9]{3}}/{v2:[0-9]{3}}"),
			request:      newRequest("GET", "http://localhost/111/aaa/333"),
			vars:         map[string]string{"v1": "111", "v2": "222"},
			host:         "",
			path:         "/111/222",
			pathTemplate: `/{v1:[0-9]{3}}/{v2:[0-9]{3}}`,
			shouldMatch:  false,
		},
	}

	for _, test := range tests {
		t.Run(test.title, func(t *testing.T) {
			testRoute(t, test)
			testTemplate(t, test)
			testUseEscapedRoute(t, test)
		})
	}
}

func TestSchemeHostPath(t *testing.T) {
	tests := []routeTest{
		{
			title:        "Host and Path route, match",
			route:        new(Route).Host("aaa.bbb.ccc").Path("/111/222/333"),
			request:      newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
			vars:         nil,
			scheme:       "http",
			host:         "aaa.bbb.ccc",
			path:         "/111/222/333",
			pathTemplate: `/111/222/333`,
			hostTemplate: `aaa.bbb.ccc`,
			shouldMatch:  true,
		},
		{
			title:        "Scheme, Host, and Path route, match",
			route:        new(Route).Schemes("https").Host("aaa.bbb.ccc").Path("/111/222/333"),
			request:      newRequest("GET", "https://aaa.bbb.ccc/111/222/333"),
			vars:         nil,
			scheme:       "https",
			host:         "aaa.bbb.ccc",
			path:         "/111/222/333",
			pathTemplate: `/111/222/333`,
			hostTemplate: `aaa.bbb.ccc`,
			shouldMatch:  true,
		},
		{
			title:        "Host and Path route, wrong host in request URL",
			route:        new(Route).Host("aaa.bbb.ccc").Path("/111/222/333"),
			request:      newRequest("GET", "http://aaa.222.ccc/111/222/333"),
			vars:         nil,
			scheme:       "http",
			host:         "aaa.bbb.ccc",
			path:         "/111/222/333",
			pathTemplate: `/111/222/333`,
			hostTemplate: `aaa.bbb.ccc`,
			shouldMatch:  false,
		},
		{
			title:        "Host and Path route with pattern, match",
			route:        new(Route).Host("aaa.{v1:[a-z]{3}}.ccc").Path("/111/{v2:[0-9]{3}}/333"),
			request:      newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
			vars:         map[string]string{"v1": "bbb", "v2": "222"},
			scheme:       "http",
			host:         "aaa.bbb.ccc",
			path:         "/111/222/333",
			pathTemplate: `/111/{v2:[0-9]{3}}/333`,
			hostTemplate: `aaa.{v1:[a-z]{3}}.ccc`,
			shouldMatch:  true,
		},
		{
			title:        "Scheme, Host, and Path route with host and path patterns, match",
			route:        new(Route).Schemes("ftp", "ssss").Host("aaa.{v1:[a-z]{3}}.ccc").Path("/111/{v2:[0-9]{3}}/333"),
			request:      newRequest("GET", "ssss://aaa.bbb.ccc/111/222/333"),
			vars:         map[string]string{"v1": "bbb", "v2": "222"},
			scheme:       "ftp",
			host:         "aaa.bbb.ccc",
			path:         "/111/222/333",
			pathTemplate: `/111/{v2:[0-9]{3}}/333`,
			hostTemplate: `aaa.{v1:[a-z]{3}}.ccc`,
			shouldMatch:  true,
		},
		{
			title:        "Host and Path route with pattern, URL in request does not match",
			route:        new(Route).Host("aaa.{v1:[a-z]{3}}.ccc").Path("/111/{v2:[0-9]{3}}/333"),
			request:      newRequest("GET", "http://aaa.222.ccc/111/222/333"),
			vars:         map[string]string{"v1": "bbb", "v2": "222"},
			scheme:       "http",
			host:         "aaa.bbb.ccc",
			path:         "/111/222/333",
			pathTemplate: `/111/{v2:[0-9]{3}}/333`,
			hostTemplate: `aaa.{v1:[a-z]{3}}.ccc`,
			shouldMatch:  false,
		},
		{
			title:        "Host and Path route with multiple patterns, match",
			route:        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}}"),
			request:      newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
			vars:         map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc", "v4": "111", "v5": "222", "v6": "333"},
			scheme:       "http",
			host:         "aaa.bbb.ccc",
			path:         "/111/222/333",
			pathTemplate: `/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}`,
			hostTemplate: `{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}`,
			shouldMatch:  true,
		},
		{
			title:        "Host and Path route with multiple patterns, URL in request does not match",
			route:        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}}"),
			request:      newRequest("GET", "http://aaa.222.ccc/111/222/333"),
			vars:         map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc", "v4": "111", "v5": "222", "v6": "333"},
			scheme:       "http",
			host:         "aaa.bbb.ccc",
			path:         "/111/222/333",
			pathTemplate: `/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}`,
			hostTemplate: `{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}`,
			shouldMatch:  false,
		},
	}

	for _, test := range tests {
		t.Run(test.title, func(t *testing.T) {
			testRoute(t, test)
			testTemplate(t, test)
			testUseEscapedRoute(t, test)
		})
	}
}

func TestHeaders(t *testing.T) {
	// newRequestHeaders creates a new request with a method, url, and headers
	newRequestHeaders := func(method, url string, headers map[string]string) *http.Request {
		req, err := http.NewRequest(method, url, nil)
		if err != nil {
			panic(err)
		}
		for k, v := range headers {
			req.Header.Add(k, v)
		}
		return req
	}

	tests := []routeTest{
		{
			title:       "Headers route, match",
			route:       new(Route).Headers("foo", "bar", "baz", "ding"),
			request:     newRequestHeaders("GET", "http://localhost", map[string]string{"foo": "bar", "baz": "ding"}),
			vars:        nil,
			host:        "",
			path:        "",
			shouldMatch: true,
		},
		{
			title:       "Headers route, bad header values",
			route:       new(Route).Headers("foo", "bar", "baz", "ding"),
			request:     newRequestHeaders("GET", "http://localhost", map[string]string{"foo": "bar", "baz": "dong"}),
			vars:        nil,
			host:        "",
			path:        "",
			shouldMatch: false,
		},
		{
			title:       "Headers route, regex header values to match",
			route:       new(Route).HeadersRegexp("foo", "ba[zr]"),
			request:     newRequestHeaders("GET", "http://localhost", map[string]string{"foo": "baw"}),
			vars:        nil,
			host:        "",
			path:        "",
			shouldMatch: false,
		},
		{
			title:       "Headers route, regex header values to match",
			route:       new(Route).HeadersRegexp("foo", "ba[zr]"),
			request:     newRequestHeaders("GET", "http://localhost", map[string]string{"foo": "baz"}),
			vars:        nil,
			host:        "",
			path:        "",
			shouldMatch: true,
		},
	}

	for _, test := range tests {
		t.Run(test.title, func(t *testing.T) {
			testRoute(t, test)
			testTemplate(t, test)
		})
	}
}

func TestMethods(t *testing.T) {
	tests := []routeTest{
		{
			title:       "Methods route, match GET",
			route:       new(Route).Methods("GET", "POST"),
			request:     newRequest("GET", "http://localhost"),
			vars:        nil,
			host:        "",
			path:        "",
			methods:     []string{"GET", "POST"},
			shouldMatch: true,
		},
		{
			title:       "Methods route, match POST",
			route:       new(Route).Methods("GET", "POST"),
			request:     newRequest("POST", "http://localhost"),
			vars:        nil,
			host:        "",
			path:        "",
			methods:     []string{"GET", "POST"},
			shouldMatch: true,
		},
		{
			title:       "Methods route, bad method",
			route:       new(Route).Methods("GET", "POST"),
			request:     newRequest("PUT", "http://localhost"),
			vars:        nil,
			host:        "",
			path:        "",
			methods:     []string{"GET", "POST"},
			shouldMatch: false,
		},
		{
			title:       "Route without methods",
			route:       new(Route),
			request:     newRequest("PUT", "http://localhost"),
			vars:        nil,
			host:        "",
			path:        "",
			methods:     []string{},
			shouldMatch: true,
		},
	}

	for _, test := range tests {
		t.Run(test.title, func(t *testing.T) {
			testRoute(t, test)
			testTemplate(t, test)
			testMethods(t, test)
		})
	}
}

func TestQueries(t *testing.T) {
	tests := []routeTest{
		{
			title:           "Queries route, match",
			route:           new(Route).Queries("foo", "bar", "baz", "ding"),
			request:         newRequest("GET", "http://localhost?foo=bar&baz=ding"),
			vars:            nil,
			host:            "",
			path:            "",
			query:           "foo=bar&baz=ding",
			queriesTemplate: "foo=bar,baz=ding",
			queriesRegexp:   "^foo=bar$,^baz=ding$",
			shouldMatch:     true,
		},
		{
			title:           "Queries route, match with a query string",
			route:           new(Route).Host("www.example.com").Path("/api").Queries("foo", "bar", "baz", "ding"),
			request:         newRequest("GET", "http://www.example.com/api?foo=bar&baz=ding"),
			vars:            nil,
			host:            "",
			path:            "",
			query:           "foo=bar&baz=ding",
			pathTemplate:    `/api`,
			hostTemplate:    `www.example.com`,
			queriesTemplate: "foo=bar,baz=ding",
			queriesRegexp:   "^foo=bar$,^baz=ding$",
			shouldMatch:     true,
		},
		{
			title:           "Queries route, match with a query string out of order",
			route:           new(Route).Host("www.example.com").Path("/api").Queries("foo", "bar", "baz", "ding"),
			request:         newRequest("GET", "http://www.example.com/api?baz=ding&foo=bar"),
			vars:            nil,
			host:            "",
			path:            "",
			query:           "foo=bar&baz=ding",
			pathTemplate:    `/api`,
			hostTemplate:    `www.example.com`,
			queriesTemplate: "foo=bar,baz=ding",
			queriesRegexp:   "^foo=bar$,^baz=ding$",
			shouldMatch:     true,
		},
		{
			title:           "Queries route, bad query",
			route:           new(Route).Queries("foo", "bar", "baz", "ding"),
			request:         newRequest("GET", "http://localhost?foo=bar&baz=dong"),
			vars:            nil,
			host:            "",
			path:            "",
			queriesTemplate: "foo=bar,baz=ding",
			queriesRegexp:   "^foo=bar$,^baz=ding$",
			shouldMatch:     false,
		},
		{
			title:           "Queries route with pattern, match",
			route:           new(Route).Queries("foo", "{v1}"),
			request:         newRequest("GET", "http://localhost?foo=bar"),
			vars:            map[string]string{"v1": "bar"},
			host:            "",
			path:            "",
			query:           "foo=bar",
			queriesTemplate: "foo={v1}",
			queriesRegexp:   "^foo=(?P<v0>.*)$",
			shouldMatch:     true,
		},
		{
			title:           "Queries route with multiple patterns, match",
			route:           new(Route).Queries("foo", "{v1}", "baz", "{v2}"),
			request:         newRequest("GET", "http://localhost?foo=bar&baz=ding"),
			vars:            map[string]string{"v1": "bar", "v2": "ding"},
			host:            "",
			path:            "",
			query:           "foo=bar&baz=ding",
			queriesTemplate: "foo={v1},baz={v2}",
			queriesRegexp:   "^foo=(?P<v0>.*)$,^baz=(?P<v0>.*)$",
			shouldMatch:     true,
		},
		{
			title:           "Queries route with regexp pattern, match",
			route:           new(Route).Queries("foo", "{v1:[0-9]+}"),
			request:         newRequest("GET", "http://localhost?foo=10"),
			vars:            map[string]string{"v1": "10"},
			host:            "",
			path:            "",
			query:           "foo=10",
			queriesTemplate: "foo={v1:[0-9]+}",
			queriesRegexp:   "^foo=(?P<v0>[0-9]+)$",
			shouldMatch:     true,
		},
		{
			title:           "Queries route with regexp pattern, regexp does not match",
			route:           new(Route).Queries("foo", "{v1:[0-9]+}"),
			request:         newRequest("GET", "http://localhost?foo=a"),
			vars:            nil,
			host:            "",
			path:            "",
			queriesTemplate: "foo={v1:[0-9]+}",
			queriesRegexp:   "^foo=(?P<v0>[0-9]+)$",
			shouldMatch:     false,
		},
		{
			title:           "Queries route with regexp pattern with quantifier, match",
			route:           new(Route).Queries("foo", "{v1:[0-9]{1}}"),
			request:         newRequest("GET", "http://localhost?foo=1"),
			vars:            map[string]string{"v1": "1"},
			host:            "",
			path:            "",
			query:           "foo=1",
			queriesTemplate: "foo={v1:[0-9]{1}}",
			queriesRegexp:   "^foo=(?P<v0>[0-9]{1})$",
			shouldMatch:     true,
		},
		{
			title:           "Queries route with regexp pattern with quantifier, additional variable in query string, match",
			route:           new(Route).Queries("foo", "{v1:[0-9]{1}}"),
			request:         newRequest("GET", "http://localhost?bar=2&foo=1"),
			vars:            map[string]string{"v1": "1"},
			host:            "",
			path:            "",
			query:           "foo=1",
			queriesTemplate: "foo={v1:[0-9]{1}}",
			queriesRegexp:   "^foo=(?P<v0>[0-9]{1})$",
			shouldMatch:     true,
		},
		{
			title:           "Queries route with regexp pattern with quantifier, regexp does not match",
			route:           new(Route).Queries("foo", "{v1:[0-9]{1}}"),
			request:         newRequest("GET", "http://localhost?foo=12"),
			vars:            nil,
			host:            "",
			path:            "",
			queriesTemplate: "foo={v1:[0-9]{1}}",
			queriesRegexp:   "^foo=(?P<v0>[0-9]{1})$",
			shouldMatch:     false,
		},
		{
			title:           "Queries route with regexp pattern with quantifier, additional capturing group",
			route:           new(Route).Queries("foo", "{v1:[0-9]{1}(?:a|b)}"),
			request:         newRequest("GET", "http://localhost?foo=1a"),
			vars:            map[string]string{"v1": "1a"},
			host:            "",
			path:            "",
			query:           "foo=1a",
			queriesTemplate: "foo={v1:[0-9]{1}(?:a|b)}",
			queriesRegexp:   "^foo=(?P<v0>[0-9]{1}(?:a|b))$",
			shouldMatch:     true,
		},
		{
			title:           "Queries route with regexp pattern with quantifier, additional variable in query string, regexp does not match",
			route:           new(Route).Queries("foo", "{v1:[0-9]{1}}"),
			request:         newRequest("GET", "http://localhost?foo=12"),
			vars:            nil,
			host:            "",
			path:            "",
			queriesTemplate: "foo={v1:[0-9]{1}}",
			queriesRegexp:   "^foo=(?P<v0>[0-9]{1})$",
			shouldMatch:     false,
		},
		{
			title:           "Queries route with hyphenated name, match",
			route:           new(Route).Queries("foo", "{v-1}"),
			request:         newRequest("GET", "http://localhost?foo=bar"),
			vars:            map[string]string{"v-1": "bar"},
			host:            "",
			path:            "",
			query:           "foo=bar",
			queriesTemplate: "foo={v-1}",
			queriesRegexp:   "^foo=(?P<v0>.*)$",
			shouldMatch:     true,
		},
		{
			title:           "Queries route with multiple hyphenated names, match",
			route:           new(Route).Queries("foo", "{v-1}", "baz", "{v-2}"),
			request:         newRequest("GET", "http://localhost?foo=bar&baz=ding"),
			vars:            map[string]string{"v-1": "bar", "v-2": "ding"},
			host:            "",
			path:            "",
			query:           "foo=bar&baz=ding",
			queriesTemplate: "foo={v-1},baz={v-2}",
			queriesRegexp:   "^foo=(?P<v0>.*)$,^baz=(?P<v0>.*)$",
			shouldMatch:     true,
		},
		{
			title:           "Queries route with hyphenate name and pattern, match",
			route:           new(Route).Queries("foo", "{v-1:[0-9]+}"),
			request:         newRequest("GET", "http://localhost?foo=10"),
			vars:            map[string]string{"v-1": "10"},
			host:            "",
			path:            "",
			query:           "foo=10",
			queriesTemplate: "foo={v-1:[0-9]+}",
			queriesRegexp:   "^foo=(?P<v0>[0-9]+)$",
			shouldMatch:     true,
		},
		{
			title:           "Queries route with hyphenated name and pattern with quantifier, additional capturing group",
			route:           new(Route).Queries("foo", "{v-1:[0-9]{1}(?:a|b)}"),
			request:         newRequest("GET", "http://localhost?foo=1a"),
			vars:            map[string]string{"v-1": "1a"},
			host:            "",
			path:            "",
			query:           "foo=1a",
			queriesTemplate: "foo={v-1:[0-9]{1}(?:a|b)}",
			queriesRegexp:   "^foo=(?P<v0>[0-9]{1}(?:a|b))$",
			shouldMatch:     true,
		},
		{
			title:           "Queries route with empty value, should match",
			route:           new(Route).Queries("foo", ""),
			request:         newRequest("GET", "http://localhost?foo=bar"),
			vars:            nil,
			host:            "",
			path:            "",
			query:           "foo=",
			queriesTemplate: "foo=",
			queriesRegexp:   "^foo=.*$",
			shouldMatch:     true,
		},
		{
			title:           "Queries route with empty value and no parameter in request, should not match",
			route:           new(Route).Queries("foo", ""),
			request:         newRequest("GET", "http://localhost"),
			vars:            nil,
			host:            "",
			path:            "",
			queriesTemplate: "foo=",
			queriesRegexp:   "^foo=.*$",
			shouldMatch:     false,
		},
		{
			title:           "Queries route with empty value and empty parameter in request, should match",
			route:           new(Route).Queries("foo", ""),
			request:         newRequest("GET", "http://localhost?foo="),
			vars:            nil,
			host:            "",
			path:            "",
			query:           "foo=",
			queriesTemplate: "foo=",
			queriesRegexp:   "^foo=.*$",
			shouldMatch:     true,
		},
		{
			title:           "Queries route with overlapping value, should not match",
			route:           new(Route).Queries("foo", "bar"),
			request:         newRequest("GET", "http://localhost?foo=barfoo"),
			vars:            nil,
			host:            "",
			path:            "",
			queriesTemplate: "foo=bar",
			queriesRegexp:   "^foo=bar$",
			shouldMatch:     false,
		},
		{
			title:           "Queries route with no parameter in request, should not match",
			route:           new(Route).Queries("foo", "{bar}"),
			request:         newRequest("GET", "http://localhost"),
			vars:            nil,
			host:            "",
			path:            "",
			queriesTemplate: "foo={bar}",
			queriesRegexp:   "^foo=(?P<v0>.*)$",
			shouldMatch:     false,
		},
		{
			title:           "Queries route with empty parameter in request, should match",
			route:           new(Route).Queries("foo", "{bar}"),
			request:         newRequest("GET", "http://localhost?foo="),
			vars:            map[string]string{"foo": ""},
			host:            "",
			path:            "",
			query:           "foo=",
			queriesTemplate: "foo={bar}",
			queriesRegexp:   "^foo=(?P<v0>.*)$",
			shouldMatch:     true,
		},
		{
			title:           "Queries route, bad submatch",
			route:           new(Route).Queries("foo", "bar", "baz", "ding"),
			request:         newRequest("GET", "http://localhost?fffoo=bar&baz=dingggg"),
			vars:            nil,
			host:            "",
			path:            "",
			queriesTemplate: "foo=bar,baz=ding",
			queriesRegexp:   "^foo=bar$,^baz=ding$",
			shouldMatch:     false,
		},
		{
			title:           "Queries route with pattern, match, escaped value",
			route:           new(Route).Queries("foo", "{v1}"),
			request:         newRequest("GET", "http://localhost?foo=%25bar%26%20%2F%3D%3F"),
			vars:            map[string]string{"v1": "%bar& /=?"},
			host:            "",
			path:            "",
			query:           "foo=%25bar%26+%2F%3D%3F",
			queriesTemplate: "foo={v1}",
			queriesRegexp:   "^foo=(?P<v0>.*)$",
			shouldMatch:     true,
		},
	}

	for _, test := range tests {
		t.Run(test.title, func(t *testing.T) {
			testTemplate(t, test)
			testQueriesTemplates(t, test)
			testUseEscapedRoute(t, test)
			testQueriesRegexp(t, test)
		})
	}
}

func TestSchemes(t *testing.T) {
	tests := []routeTest{
		// Schemes
		{
			title:       "Schemes route, default scheme, match http, build http",
			route:       new(Route).Host("localhost"),
			request:     newRequest("GET", "http://localhost"),
			scheme:      "http",
			host:        "localhost",
			shouldMatch: true,
		},
		{
			title:       "Schemes route, match https, build https",
			route:       new(Route).Schemes("https", "ftp").Host("localhost"),
			request:     newRequest("GET", "https://localhost"),
			scheme:      "https",
			host:        "localhost",
			shouldMatch: true,
		},
		{
			title:       "Schemes route, match ftp, build https",
			route:       new(Route).Schemes("https", "ftp").Host("localhost"),
			request:     newRequest("GET", "ftp://localhost"),
			scheme:      "https",
			host:        "localhost",
			shouldMatch: true,
		},
		{
			title:       "Schemes route, match ftp, build ftp",
			route:       new(Route).Schemes("ftp", "https").Host("localhost"),
			request:     newRequest("GET", "ftp://localhost"),
			scheme:      "ftp",
			host:        "localhost",
			shouldMatch: true,
		},
		{
			title:       "Schemes route, bad scheme",
			route:       new(Route).Schemes("https", "ftp").Host("localhost"),
			request:     newRequest("GET", "http://localhost"),
			scheme:      "https",
			host:        "localhost",
			shouldMatch: false,
		},
	}
	for _, test := range tests {
		t.Run(test.title, func(t *testing.T) {
			testRoute(t, test)
			testTemplate(t, test)
		})
	}
}

func TestMatcherFunc(t *testing.T) {
	m := func(r *http.Request, m *RouteMatch) bool {
		return r.URL.Host == "aaa.bbb.ccc"
	}

	tests := []routeTest{
		{
			title:       "MatchFunc route, match",
			route:       new(Route).MatcherFunc(m),
			request:     newRequest("GET", "http://aaa.bbb.ccc"),
			vars:        nil,
			host:        "",
			path:        "",
			shouldMatch: true,
		},
		{
			title:       "MatchFunc route, non-match",
			route:       new(Route).MatcherFunc(m),
			request:     newRequest("GET", "http://aaa.222.ccc"),
			vars:        nil,
			host:        "",
			path:        "",
			shouldMatch: false,
		},
	}

	for _, test := range tests {
		t.Run(test.title, func(t *testing.T) {
			testRoute(t, test)
			testTemplate(t, test)
		})
	}
}

func TestBuildVarsFunc(t *testing.T) {
	tests := []routeTest{
		{
			title: "BuildVarsFunc set on route",
			route: new(Route).Path(`/111/{v1:\d}{v2:.*}`).BuildVarsFunc(func(vars map[string]string) map[string]string {
				vars["v1"] = "3"
				vars["v2"] = "a"
				return vars
			}),
			request:      newRequest("GET", "http://localhost/111/2"),
			path:         "/111/3a",
			pathTemplate: `/111/{v1:\d}{v2:.*}`,
			shouldMatch:  true,
		},
		{
			title: "BuildVarsFunc set on route and parent route",
			route: new(Route).PathPrefix(`/{v1:\d}`).BuildVarsFunc(func(vars map[string]string) map[string]string {
				vars["v1"] = "2"
				return vars
			}).Subrouter().Path(`/{v2:\w}`).BuildVarsFunc(func(vars map[string]string) map[string]string {
				vars["v2"] = "b"
				return vars
			}),
			request:      newRequest("GET", "http://localhost/1/a"),
			path:         "/2/b",
			pathTemplate: `/{v1:\d}/{v2:\w}`,
			shouldMatch:  true,
		},
	}

	for _, test := range tests {
		t.Run(test.title, func(t *testing.T) {
			testRoute(t, test)
			testTemplate(t, test)
		})
	}
}

func TestSubRouter(t *testing.T) {
	subrouter1 := new(Route).Host("{v1:[a-z]+}.google.com").Subrouter()
	subrouter2 := new(Route).PathPrefix("/foo/{v1}").Subrouter()
	subrouter3 := new(Route).PathPrefix("/foo").Subrouter()
	subrouter4 := new(Route).PathPrefix("/foo/bar").Subrouter()
	subrouter5 := new(Route).PathPrefix("/{category}").Subrouter()
	tests := []routeTest{
		{
			route:        subrouter1.Path("/{v2:[a-z]+}"),
			request:      newRequest("GET", "http://aaa.google.com/bbb"),
			vars:         map[string]string{"v1": "aaa", "v2": "bbb"},
			host:         "aaa.google.com",
			path:         "/bbb",
			pathTemplate: `/{v2:[a-z]+}`,
			hostTemplate: `{v1:[a-z]+}.google.com`,
			shouldMatch:  true,
		},
		{
			route:        subrouter1.Path("/{v2:[a-z]+}"),
			request:      newRequest("GET", "http://111.google.com/111"),
			vars:         map[string]string{"v1": "aaa", "v2": "bbb"},
			host:         "aaa.google.com",
			path:         "/bbb",
			pathTemplate: `/{v2:[a-z]+}`,
			hostTemplate: `{v1:[a-z]+}.google.com`,
			shouldMatch:  false,
		},
		{
			route:        subrouter2.Path("/baz/{v2}"),
			request:      newRequest("GET", "http://localhost/foo/bar/baz/ding"),
			vars:         map[string]string{"v1": "bar", "v2": "ding"},
			host:         "",
			path:         "/foo/bar/baz/ding",
			pathTemplate: `/foo/{v1}/baz/{v2}`,
			shouldMatch:  true,
		},
		{
			route:        subrouter2.Path("/baz/{v2}"),
			request:      newRequest("GET", "http://localhost/foo/bar"),
			vars:         map[string]string{"v1": "bar", "v2": "ding"},
			host:         "",
			path:         "/foo/bar/baz/ding",
			pathTemplate: `/foo/{v1}/baz/{v2}`,
			shouldMatch:  false,
		},
		{
			route:        subrouter3.Path("/"),
			request:      newRequest("GET", "http://localhost/foo/"),
			vars:         nil,
			host:         "",
			path:         "/foo/",
			pathTemplate: `/foo/`,
			shouldMatch:  true,
		},
		{
			route:        subrouter3.Path(""),
			request:      newRequest("GET", "http://localhost/foo"),
			vars:         nil,
			host:         "",
			path:         "/foo",
			pathTemplate: `/foo`,
			shouldMatch:  true,
		},

		{
			route:        subrouter4.Path("/"),
			request:      newRequest("GET", "http://localhost/foo/bar/"),
			vars:         nil,
			host:         "",
			path:         "/foo/bar/",
			pathTemplate: `/foo/bar/`,
			shouldMatch:  true,
		},
		{
			route:        subrouter4.Path(""),
			request:      newRequest("GET", "http://localhost/foo/bar"),
			vars:         nil,
			host:         "",
			path:         "/foo/bar",
			pathTemplate: `/foo/bar`,
			shouldMatch:  true,
		},
		{
			route:        subrouter5.Path("/"),
			request:      newRequest("GET", "http://localhost/baz/"),
			vars:         map[string]string{"category": "baz"},
			host:         "",
			path:         "/baz/",
			pathTemplate: `/{category}/`,
			shouldMatch:  true,
		},
		{
			route:        subrouter5.Path(""),
			request:      newRequest("GET", "http://localhost/baz"),
			vars:         map[string]string{"category": "baz"},
			host:         "",
			path:         "/baz",
			pathTemplate: `/{category}`,
			shouldMatch:  true,
		},
		{
			title:        "Mismatch method specified on parent route",
			route:        new(Route).Methods("POST").PathPrefix("/foo").Subrouter().Path("/"),
			request:      newRequest("GET", "http://localhost/foo/"),
			vars:         nil,
			host:         "",
			path:         "/foo/",
			pathTemplate: `/foo/`,
			shouldMatch:  false,
		},
		{
			title:        "Match method specified on parent route",
			route:        new(Route).Methods("POST").PathPrefix("/foo").Subrouter().Path("/"),
			request:      newRequest("POST", "http://localhost/foo/"),
			vars:         nil,
			host:         "",
			path:         "/foo/",
			pathTemplate: `/foo/`,
			shouldMatch:  true,
		},
		{
			title:        "Mismatch scheme specified on parent route",
			route:        new(Route).Schemes("https").Subrouter().PathPrefix("/"),
			request:      newRequest("GET", "http://localhost/"),
			vars:         nil,
			host:         "",
			path:         "/",
			pathTemplate: `/`,
			shouldMatch:  false,
		},
		{
			title:        "Match scheme specified on parent route",
			route:        new(Route).Schemes("http").Subrouter().PathPrefix("/"),
			request:      newRequest("GET", "http://localhost/"),
			vars:         nil,
			host:         "",
			path:         "/",
			pathTemplate: `/`,
			shouldMatch:  true,
		},
		{
			title:        "No match header specified on parent route",
			route:        new(Route).Headers("X-Forwarded-Proto", "https").Subrouter().PathPrefix("/"),
			request:      newRequest("GET", "http://localhost/"),
			vars:         nil,
			host:         "",
			path:         "/",
			pathTemplate: `/`,
			shouldMatch:  false,
		},
		{
			title:        "Header mismatch value specified on parent route",
			route:        new(Route).Headers("X-Forwarded-Proto", "https").Subrouter().PathPrefix("/"),
			request:      newRequestWithHeaders("GET", "http://localhost/", "X-Forwarded-Proto", "http"),
			vars:         nil,
			host:         "",
			path:         "/",
			pathTemplate: `/`,
			shouldMatch:  false,
		},
		{
			title:        "Header match value specified on parent route",
			route:        new(Route).Headers("X-Forwarded-Proto", "https").Subrouter().PathPrefix("/"),
			request:      newRequestWithHeaders("GET", "http://localhost/", "X-Forwarded-Proto", "https"),
			vars:         nil,
			host:         "",
			path:         "/",
			pathTemplate: `/`,
			shouldMatch:  true,
		},
		{
			title:        "Query specified on parent route not present",
			route:        new(Route).Headers("key", "foobar").Subrouter().PathPrefix("/"),
			request:      newRequest("GET", "http://localhost/"),
			vars:         nil,
			host:         "",
			path:         "/",
			pathTemplate: `/`,
			shouldMatch:  false,
		},
		{
			title:        "Query mismatch value specified on parent route",
			route:        new(Route).Queries("key", "foobar").Subrouter().PathPrefix("/"),
			request:      newRequest("GET", "http://localhost/?key=notfoobar"),
			vars:         nil,
			host:         "",
			path:         "/",
			pathTemplate: `/`,
			shouldMatch:  false,
		},
		{
			title:        "Query match value specified on subroute",
			route:        new(Route).Queries("key", "foobar").Subrouter().PathPrefix("/"),
			request:      newRequest("GET", "http://localhost/?key=foobar"),
			vars:         nil,
			host:         "",
			path:         "/",
			pathTemplate: `/`,
			shouldMatch:  true,
		},
		{
			title:        "Build with scheme on parent router",
			route:        new(Route).Schemes("ftp").Host("google.com").Subrouter().Path("/"),
			request:      newRequest("GET", "ftp://google.com/"),
			scheme:       "ftp",
			host:         "google.com",
			path:         "/",
			pathTemplate: `/`,
			hostTemplate: `google.com`,
			shouldMatch:  true,
		},
		{
			title:        "Prefer scheme on child route when building URLs",
			route:        new(Route).Schemes("https", "ftp").Host("google.com").Subrouter().Schemes("ftp").Path("/"),
			request:      newRequest("GET", "ftp://google.com/"),
			scheme:       "ftp",
			host:         "google.com",
			path:         "/",
			pathTemplate: `/`,
			hostTemplate: `google.com`,
			shouldMatch:  true,
		},
	}

	for _, test := range tests {
		t.Run(test.title, func(t *testing.T) {
			testRoute(t, test)
			testTemplate(t, test)
			testUseEscapedRoute(t, test)
		})
	}
}

func TestNamedRoutes(t *testing.T) {
	r1 := NewRouter()
	r1.NewRoute().Name("a")
	r1.NewRoute().Name("b")
	r1.NewRoute().Name("c")

	r2 := r1.NewRoute().Subrouter()
	r2.NewRoute().Name("d")
	r2.NewRoute().Name("e")
	r2.NewRoute().Name("f")

	r3 := r2.NewRoute().Subrouter()
	r3.NewRoute().Name("g")
	r3.NewRoute().Name("h")
	r3.NewRoute().Name("i")
	r3.Name("j")

	if r1.namedRoutes == nil || len(r1.namedRoutes) != 10 {
		t.Errorf("Expected 10 named routes, got %v", r1.namedRoutes)
	} else if r1.Get("j") == nil {
		t.Errorf("Subroute name not registered")
	}
}

func TestNameMultipleCalls(t *testing.T) {
	r1 := NewRouter()
	rt := r1.NewRoute().Name("foo").Name("bar")
	err := rt.GetError()
	if err == nil {
		t.Errorf("Expected an error")
	}
}

func TestStrictSlash(t *testing.T) {
	r := NewRouter()
	r.StrictSlash(true)

	tests := []routeTest{
		{
			title:          "Redirect path without slash",
			route:          r.NewRoute().Path("/111/"),
			request:        newRequest("GET", "http://localhost/111"),
			vars:           nil,
			host:           "",
			path:           "/111/",
			shouldMatch:    true,
			shouldRedirect: true,
		},
		{
			title:          "Do not redirect path with slash",
			route:          r.NewRoute().Path("/111/"),
			request:        newRequest("GET", "http://localhost/111/"),
			vars:           nil,
			host:           "",
			path:           "/111/",
			shouldMatch:    true,
			shouldRedirect: false,
		},
		{
			title:          "Redirect path with slash",
			route:          r.NewRoute().Path("/111"),
			request:        newRequest("GET", "http://localhost/111/"),
			vars:           nil,
			host:           "",
			path:           "/111",
			shouldMatch:    true,
			shouldRedirect: true,
		},
		{
			title:          "Do not redirect path without slash",
			route:          r.NewRoute().Path("/111"),
			request:        newRequest("GET", "http://localhost/111"),
			vars:           nil,
			host:           "",
			path:           "/111",
			shouldMatch:    true,
			shouldRedirect: false,
		},
		{
			title:          "Propagate StrictSlash to subrouters",
			route:          r.NewRoute().PathPrefix("/static/").Subrouter().Path("/images/"),
			request:        newRequest("GET", "http://localhost/static/images"),
			vars:           nil,
			host:           "",
			path:           "/static/images/",
			shouldMatch:    true,
			shouldRedirect: true,
		},
		{
			title:          "Ignore StrictSlash for path prefix",
			route:          r.NewRoute().PathPrefix("/static/"),
			request:        newRequest("GET", "http://localhost/static/logo.png"),
			vars:           nil,
			host:           "",
			path:           "/static/",
			shouldMatch:    true,
			shouldRedirect: false,
		},
	}

	for _, test := range tests {
		t.Run(test.title, func(t *testing.T) {
			testRoute(t, test)
			testTemplate(t, test)
			testUseEscapedRoute(t, test)
		})
	}
}

func TestUseEncodedPath(t *testing.T) {
	r := NewRouter()
	r.UseEncodedPath()

	tests := []routeTest{
		{
			title:        "Router with useEncodedPath, URL with encoded slash does match",
			route:        r.NewRoute().Path("/v1/{v1}/v2"),
			request:      newRequest("GET", "http://localhost/v1/1%2F2/v2"),
			vars:         map[string]string{"v1": "1%2F2"},
			host:         "",
			path:         "/v1/1%2F2/v2",
			pathTemplate: `/v1/{v1}/v2`,
			shouldMatch:  true,
		},
		{
			title:        "Router with useEncodedPath, URL with encoded slash doesn't match",
			route:        r.NewRoute().Path("/v1/1/2/v2"),
			request:      newRequest("GET", "http://localhost/v1/1%2F2/v2"),
			vars:         map[string]string{"v1": "1%2F2"},
			host:         "",
			path:         "/v1/1%2F2/v2",
			pathTemplate: `/v1/1/2/v2`,
			shouldMatch:  false,
		},
	}

	for _, test := range tests {
		t.Run(test.title, func(t *testing.T) {
			testRoute(t, test)
			testTemplate(t, test)
		})
	}
}

func TestWalkSingleDepth(t *testing.T) {
	r0 := NewRouter()
	r1 := NewRouter()
	r2 := NewRouter()

	r0.Path("/g")
	r0.Path("/o")
	r0.Path("/d").Handler(r1)
	r0.Path("/r").Handler(r2)
	r0.Path("/a")

	r1.Path("/z")
	r1.Path("/i")
	r1.Path("/l")
	r1.Path("/l")

	r2.Path("/i")
	r2.Path("/l")
	r2.Path("/l")

	paths := []string{"g", "o", "r", "i", "l", "l", "a"}
	depths := []int{0, 0, 0, 1, 1, 1, 0}
	i := 0
	err := r0.Walk(func(route *Route, router *Router, ancestors []*Route) error {
		matcher := route.matchers[0].(*routeRegexp)
		if matcher.template == "/d" {
			return SkipRouter
		}
		if len(ancestors) != depths[i] {
			t.Errorf(`Expected depth of %d at i = %d; got "%d"`, depths[i], i, len(ancestors))
		}
		if matcher.template != "/"+paths[i] {
			t.Errorf(`Expected "/%s" at i = %d; got "%s"`, paths[i], i, matcher.template)
		}
		i++
		return nil
	})
	if err != nil {
		panic(err)
	}
	if i != len(paths) {
		t.Errorf("Expected %d routes, found %d", len(paths), i)
	}
}

func TestWalkNested(t *testing.T) {
	router := NewRouter()

	routeSubrouter := func(r *Route) (*Route, *Router) {
		return r, r.Subrouter()
	}

	gRoute, g := routeSubrouter(router.Path("/g"))
	oRoute, o := routeSubrouter(g.PathPrefix("/o"))
	rRoute, r := routeSubrouter(o.PathPrefix("/r"))
	iRoute, i := routeSubrouter(r.PathPrefix("/i"))
	l1Route, l1 := routeSubrouter(i.PathPrefix("/l"))
	l2Route, l2 := routeSubrouter(l1.PathPrefix("/l"))
	l2.Path("/a")

	testCases := []struct {
		path      string
		ancestors []*Route
	}{
		{"/g", []*Route{}},
		{"/g/o", []*Route{gRoute}},
		{"/g/o/r", []*Route{gRoute, oRoute}},
		{"/g/o/r/i", []*Route{gRoute, oRoute, rRoute}},
		{"/g/o/r/i/l", []*Route{gRoute, oRoute, rRoute, iRoute}},
		{"/g/o/r/i/l/l", []*Route{gRoute, oRoute, rRoute, iRoute, l1Route}},
		{"/g/o/r/i/l/l/a", []*Route{gRoute, oRoute, rRoute, iRoute, l1Route, l2Route}},
	}

	idx := 0
	err := router.Walk(func(route *Route, router *Router, ancestors []*Route) error {
		path := testCases[idx].path
		tpl := route.regexp.path.template
		if tpl != path {
			t.Errorf(`Expected %s got %s`, path, tpl)
		}
		currWantAncestors := testCases[idx].ancestors
		if !reflect.DeepEqual(currWantAncestors, ancestors) {
			t.Errorf(`Expected %+v got %+v`, currWantAncestors, ancestors)
		}
		idx++
		return nil
	})
	if err != nil {
		panic(err)
	}
	if idx != len(testCases) {
		t.Errorf("Expected %d routes, found %d", len(testCases), idx)
	}
}

func TestWalkSubrouters(t *testing.T) {
	router := NewRouter()

	g := router.Path("/g").Subrouter()
	o := g.PathPrefix("/o").Subrouter()
	o.Methods("GET")
	o.Methods("PUT")

	// all 4 routes should be matched
	paths := []string{"/g", "/g/o", "/g/o", "/g/o"}
	idx := 0
	err := router.Walk(func(route *Route, router *Router, ancestors []*Route) error {
		path := paths[idx]
		tpl, _ := route.GetPathTemplate()
		if tpl != path {
			t.Errorf(`Expected %s got %s`, path, tpl)
		}
		idx++
		return nil
	})
	if err != nil {
		panic(err)
	}
	if idx != len(paths) {
		t.Errorf("Expected %d routes, found %d", len(paths), idx)
	}
}

func TestWalkErrorRoute(t *testing.T) {
	router := NewRouter()
	router.Path("/g")
	expectedError := errors.New("error")
	err := router.Walk(func(route *Route, router *Router, ancestors []*Route) error {
		return expectedError
	})
	if err != expectedError {
		t.Errorf("Expected %v routes, found %v", expectedError, err)
	}
}

func TestWalkErrorMatcher(t *testing.T) {
	router := NewRouter()
	expectedError := router.Path("/g").Subrouter().Path("").GetError()
	err := router.Walk(func(route *Route, router *Router, ancestors []*Route) error {
		return route.GetError()
	})
	if err != expectedError {
		t.Errorf("Expected %v routes, found %v", expectedError, err)
	}
}

func TestWalkErrorHandler(t *testing.T) {
	handler := NewRouter()
	expectedError := handler.Path("/path").Subrouter().Path("").GetError()
	router := NewRouter()
	router.Path("/g").Handler(handler)
	err := router.Walk(func(route *Route, router *Router, ancestors []*Route) error {
		return route.GetError()
	})
	if err != expectedError {
		t.Errorf("Expected %v routes, found %v", expectedError, err)
	}
}

func TestSubrouterErrorHandling(t *testing.T) {
	superRouterCalled := false
	subRouterCalled := false

	router := NewRouter()
	router.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		superRouterCalled = true
	})
	subRouter := router.PathPrefix("/bign8").Subrouter()
	subRouter.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		subRouterCalled = true
	})

	req, _ := http.NewRequest("GET", "http://localhost/bign8/was/here", nil)
	router.ServeHTTP(NewRecorder(), req)

	if superRouterCalled {
		t.Error("Super router 404 handler called when sub-router 404 handler is available.")
	}
	if !subRouterCalled {
		t.Error("Sub-router 404 handler was not called.")
	}
}

// See: https://github.com/gorilla/mux/issues/200
func TestPanicOnCapturingGroups(t *testing.T) {
	defer func() {
		if recover() == nil {
			t.Errorf("(Test that capturing groups now fail fast) Expected panic, however test completed successfully.\n")
		}
	}()
	NewRouter().NewRoute().Path("/{type:(promo|special)}/{promoId}.json")
}

func TestRouterInContext(t *testing.T) {
	router := NewRouter()
	router.HandleFunc("/r1", func(w http.ResponseWriter, r *http.Request) {
		contextRouter := CurrentRouter(r)
		if contextRouter == nil {
			t.Fatal("Router not found in context")
			return
		}

		route := contextRouter.Get("r2")
		if route == nil {
			t.Fatal("Route with name not found")
			return
		}

		url, err := route.URL()
		if err != nil {
			t.Fatal("Error while getting url for r2: ", err)
			return
		}

		_, err = w.Write([]byte(url.String()))
		if err != nil {
			t.Fatalf("Failed writing HTTP response: %v", err)
		}
	}).Name("r1")

	noRouterMsg := []byte("no-router")
	haveRouterMsg := []byte("have-router")
	router.HandleFunc("/r2", func(w http.ResponseWriter, r *http.Request) {
		var msg []byte

		contextRouter := CurrentRouter(r)
		if contextRouter == nil {
			msg = noRouterMsg
		} else {
			msg = haveRouterMsg
		}

		_, err := w.Write(msg)
		if err != nil {
			t.Fatalf("Failed writing HTTP response: %v", err)
		}
	}).Name("r2")

	t.Run("router in request context get route by name", func(t *testing.T) {
		rw := NewRecorder()
		req := newRequest("GET", "/r1")

		router.ServeHTTP(rw, req)
		if !bytes.Equal(rw.Body.Bytes(), []byte("/r2")) {
			t.Fatalf("Expected output to be '/r1' but got '%s'", rw.Body.String())
		}
	})

	t.Run("omit router from request context", func(t *testing.T) {
		rw := NewRecorder()
		req := newRequest("GET", "/r2")

		router.OmitRouterFromContext(true)
		router.ServeHTTP(rw, req)
		if !bytes.Equal(rw.Body.Bytes(), noRouterMsg) {
			t.Fatal("Router not omitted from context")
		}
	})
}

// ----------------------------------------------------------------------------
// Helpers
// ----------------------------------------------------------------------------

func getRouteTemplate(route *Route) string {
	host, err := route.GetHostTemplate()
	if err != nil {
		host = "none"
	}
	path, err := route.GetPathTemplate()
	if err != nil {
		path = "none"
	}
	return fmt.Sprintf("Host: %v, Path: %v", host, path)
}

func testRoute(t *testing.T, test routeTest) {
	request := test.request
	route := test.route
	vars := test.vars
	shouldMatch := test.shouldMatch
	query := test.query
	shouldRedirect := test.shouldRedirect
	uri := url.URL{
		Scheme: test.scheme,
		Host:   test.host,
		Path:   test.path,
	}
	if uri.Scheme == "" {
		uri.Scheme = "http"
	}

	var match RouteMatch
	ok := route.Match(request, &match)
	if ok != shouldMatch {
		msg := "Should match"
		if !shouldMatch {
			msg = "Should not match"
		}
		t.Errorf("(%v) %v:\nRoute: %#v\nRequest: %#v\nVars: %v\n", test.title, msg, route, request, vars)
		return
	}
	if shouldMatch {
		if vars != nil && !stringMapEqual(vars, match.Vars) {
			t.Errorf("(%v) Vars not equal: expected %v, got %v", test.title, vars, match.Vars)
			return
		}
		if test.scheme != "" {
			u, err := route.URL(mapToPairs(match.Vars)...)
			if err != nil {
				t.Fatalf("(%v) URL error: %v -- %v", test.title, err, getRouteTemplate(route))
			}
			if uri.Scheme != u.Scheme {
				t.Errorf("(%v) URLScheme not equal: expected %v, got %v", test.title, uri.Scheme, u.Scheme)
				return
			}
		}
		if test.host != "" {
			u, err := test.route.URLHost(mapToPairs(match.Vars)...)
			if err != nil {
				t.Fatalf("(%v) URLHost error: %v -- %v", test.title, err, getRouteTemplate(route))
			}
			if uri.Scheme != u.Scheme {
				t.Errorf("(%v) URLHost scheme not equal: expected %v, got %v -- %v", test.title, uri.Scheme, u.Scheme, getRouteTemplate(route))
				return
			}
			if uri.Host != u.Host {
				t.Errorf("(%v) URLHost host not equal: expected %v, got %v -- %v", test.title, uri.Host, u.Host, getRouteTemplate(route))
				return
			}
		}
		if test.path != "" {
			u, err := route.URLPath(mapToPairs(match.Vars)...)
			if err != nil {
				t.Fatalf("(%v) URLPath error: %v -- %v", test.title, err, getRouteTemplate(route))
			}
			if uri.Path != u.Path {
				t.Errorf("(%v) URLPath not equal: expected %v, got %v -- %v", test.title, uri.Path, u.Path, getRouteTemplate(route))
				return
			}
		}
		if test.host != "" && test.path != "" {
			u, err := route.URL(mapToPairs(match.Vars)...)
			if err != nil {
				t.Fatalf("(%v) URL error: %v -- %v", test.title, err, getRouteTemplate(route))
			}
			if expected, got := uri.String(), u.String(); expected != got {
				t.Errorf("(%v) URL not equal: expected %v, got %v -- %v", test.title, expected, got, getRouteTemplate(route))
				return
			}
		}
		if query != "" {
			u, err := route.URL(mapToPairs(match.Vars)...)
			if err != nil {
				t.Errorf("(%v) erred while creating url: %v", test.title, err)
				return
			}
			if query != u.RawQuery {
				t.Errorf("(%v) URL query not equal: expected %v, got %v", test.title, query, u.RawQuery)
				return
			}
		}
		if shouldRedirect && match.Handler == nil {
			t.Errorf("(%v) Did not redirect", test.title)
			return
		}
		if !shouldRedirect && match.Handler != nil {
			t.Errorf("(%v) Unexpected redirect", test.title)
			return
		}
	}
}

func testUseEscapedRoute(t *testing.T, test routeTest) {
	test.route.useEncodedPath = true
	testRoute(t, test)
}

func testTemplate(t *testing.T, test routeTest) {
	route := test.route
	pathTemplate := test.pathTemplate
	if len(pathTemplate) == 0 {
		pathTemplate = test.path
	}
	hostTemplate := test.hostTemplate
	if len(hostTemplate) == 0 {
		hostTemplate = test.host
	}

	routePathTemplate, pathErr := route.GetPathTemplate()
	if pathErr == nil && routePathTemplate != pathTemplate {
		t.Errorf("(%v) GetPathTemplate not equal: expected %v, got %v", test.title, pathTemplate, routePathTemplate)
	}

	routeHostTemplate, hostErr := route.GetHostTemplate()
	if hostErr == nil && routeHostTemplate != hostTemplate {
		t.Errorf("(%v) GetHostTemplate not equal: expected %v, got %v", test.title, hostTemplate, routeHostTemplate)
	}
}

func testMethods(t *testing.T, test routeTest) {
	route := test.route
	methods, _ := route.GetMethods()
	if strings.Join(methods, ",") != strings.Join(test.methods, ",") {
		t.Errorf("(%v) GetMethods not equal: expected %v, got %v", test.title, test.methods, methods)
	}
}

func testRegexp(t *testing.T, test routeTest) {
	route := test.route
	routePathRegexp, regexpErr := route.GetPathRegexp()
	if test.pathRegexp != "" && regexpErr == nil && routePathRegexp != test.pathRegexp {
		t.Errorf("(%v) GetPathRegexp not equal: expected %v, got %v", test.title, test.pathRegexp, routePathRegexp)
	}
}

func testQueriesRegexp(t *testing.T, test routeTest) {
	route := test.route
	queries, queriesErr := route.GetQueriesRegexp()
	gotQueries := strings.Join(queries, ",")
	if test.queriesRegexp != "" && queriesErr == nil && gotQueries != test.queriesRegexp {
		t.Errorf("(%v) GetQueriesRegexp not equal: expected %v, got %v", test.title, test.queriesRegexp, gotQueries)
	}
}

func testQueriesTemplates(t *testing.T, test routeTest) {
	route := test.route
	queries, queriesErr := route.GetQueriesTemplates()
	gotQueries := strings.Join(queries, ",")
	if test.queriesTemplate != "" && queriesErr == nil && gotQueries != test.queriesTemplate {
		t.Errorf("(%v) GetQueriesTemplates not equal: expected %v, got %v", test.title, test.queriesTemplate, gotQueries)
	}
}

type TestA301ResponseWriter struct {
	hh     http.Header
	status int
}

func (ho *TestA301ResponseWriter) Header() http.Header {
	return ho.hh
}

func (ho *TestA301ResponseWriter) Write(b []byte) (int, error) {
	return 0, nil
}

func (ho *TestA301ResponseWriter) WriteHeader(code int) {
	ho.status = code
}

func Test301Redirect(t *testing.T) {
	m := make(http.Header)

	func1 := func(w http.ResponseWriter, r *http.Request) {}
	func2 := func(w http.ResponseWriter, r *http.Request) {}

	r := NewRouter()
	r.HandleFunc("/api/", func2).Name("func2")
	r.HandleFunc("/", func1).Name("func1")

	req, _ := http.NewRequest("GET", "http://localhost//api/?abc=def", nil)

	res := TestA301ResponseWriter{
		hh:     m,
		status: 0,
	}
	r.ServeHTTP(&res, req)

	if "http://localhost/api/?abc=def" != res.hh["Location"][0] {
		t.Errorf("Should have complete URL with query string")
	}
}

func TestSkipClean(t *testing.T) {
	func1 := func(w http.ResponseWriter, r *http.Request) {}
	func2 := func(w http.ResponseWriter, r *http.Request) {}

	r := NewRouter()
	r.SkipClean(true)
	r.HandleFunc("/api/", func2).Name("func2")
	r.HandleFunc("/", func1).Name("func1")

	req, _ := http.NewRequest("GET", "http://localhost//api/?abc=def", nil)
	res := NewRecorder()
	r.ServeHTTP(res, req)

	if len(res.HeaderMap["Location"]) != 0 {
		t.Errorf("Shouldn't redirect since skip clean is disabled")
	}
}

// https://plus.google.com/101022900381697718949/posts/eWy6DjFJ6uW
func TestSubrouterHeader(t *testing.T) {
	expected := "func1 response"
	func1 := func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, expected)
	}
	func2 := func(http.ResponseWriter, *http.Request) {}

	r := NewRouter()
	s := r.Headers("SomeSpecialHeader", "").Subrouter()
	s.HandleFunc("/", func1).Name("func1")
	r.HandleFunc("/", func2).Name("func2")

	req, _ := http.NewRequest("GET", "http://localhost/", nil)
	req.Header.Add("SomeSpecialHeader", "foo")
	match := new(RouteMatch)
	matched := r.Match(req, match)
	if !matched {
		t.Errorf("Should match request")
	}
	if match.Route.GetName() != "func1" {
		t.Errorf("Expecting func1 handler, got %s", match.Route.GetName())
	}
	resp := NewRecorder()
	match.Handler.ServeHTTP(resp, req)
	if resp.Body.String() != expected {
		t.Errorf("Expecting %q", expected)
	}
}

func TestNoMatchMethodErrorHandler(t *testing.T) {
	func1 := func(w http.ResponseWriter, r *http.Request) {}

	r := NewRouter()
	r.HandleFunc("/", func1).Methods("GET", "POST")

	req, _ := http.NewRequest("PUT", "http://localhost/", nil)
	match := new(RouteMatch)
	matched := r.Match(req, match)

	if matched {
		t.Error("Should not have matched route for methods")
	}

	if match.MatchErr != ErrMethodMismatch {
		t.Error("Should get ErrMethodMismatch error")
	}

	resp := NewRecorder()
	r.ServeHTTP(resp, req)
	if resp.Code != http.StatusMethodNotAllowed {
		t.Errorf("Expecting code %v", 405)
	}

	// Add matching route
	r.HandleFunc("/", func1).Methods("PUT")

	match = new(RouteMatch)
	matched = r.Match(req, match)

	if !matched {
		t.Error("Should have matched route for methods")
	}

	if match.MatchErr != nil {
		t.Error("Should not have any matching error. Found:", match.MatchErr)
	}
}

func TestMultipleDefinitionOfSamePathWithDifferentMethods(t *testing.T) {
	emptyHandler := func(w http.ResponseWriter, r *http.Request) {}

	r := NewRouter()
	r.HandleFunc("/api", emptyHandler).Methods("POST")
	r.HandleFunc("/api", emptyHandler).Queries("time", "{time:[0-9]+}").Methods("GET")

	t.Run("Post Method should be matched properly", func(t *testing.T) {
		req, _ := http.NewRequest("POST", "http://localhost/api", nil)
		match := new(RouteMatch)
		matched := r.Match(req, match)
		if !matched {
			t.Error("Should have matched route for methods")
		}
		if match.MatchErr != nil {
			t.Error("Should not have any matching error. Found:", match.MatchErr)
		}
	})

	t.Run("Get Method with invalid query value should not match", func(t *testing.T) {
		req, _ := http.NewRequest("GET", "http://localhost/api?time=-4", nil)
		match := new(RouteMatch)
		matched := r.Match(req, match)
		if matched {
			t.Error("Should not have matched route for methods")
		}
		if match.MatchErr != ErrNotFound {
			t.Error("Should have ErrNotFound error. Found:", match.MatchErr)
		}
	})

	t.Run("A mismach method of a valid path should return ErrMethodMismatch", func(t *testing.T) {
		r := NewRouter()
		r.HandleFunc("/api2", emptyHandler).Methods("POST")
		req, _ := http.NewRequest("GET", "http://localhost/api2", nil)
		match := new(RouteMatch)
		matched := r.Match(req, match)
		if matched {
			t.Error("Should not have matched route for methods")
		}
		if match.MatchErr != ErrMethodMismatch {
			t.Error("Should have ErrMethodMismatch error. Found:", match.MatchErr)
		}
	})

}

func TestMultipleDefinitionOfSamePathWithDifferentQueries(t *testing.T) {
	emptyHandler := func(w http.ResponseWriter, r *http.Request) {}

	r := NewRouter()
	r.HandleFunc("/api", emptyHandler).Queries("foo", "{foo:[0-9]+}").Methods(http.MethodGet)
	r.HandleFunc("/api", emptyHandler).Queries("bar", "{bar:[0-9]+}").Methods(http.MethodGet)

	req := newRequest(http.MethodGet, "/api?bar=4")
	match := new(RouteMatch)
	matched := r.Match(req, match)
	if !matched {
		t.Error("Should have matched route for methods")
	}
	if match.MatchErr != nil {
		t.Error("Should have no error. Found:", match.MatchErr)
	}
}

func TestErrMatchNotFound(t *testing.T) {
	emptyHandler := func(w http.ResponseWriter, r *http.Request) {}

	r := NewRouter()
	r.HandleFunc("/", emptyHandler)
	s := r.PathPrefix("/sub/").Subrouter()
	s.HandleFunc("/", emptyHandler)

	// Regular 404 not found
	req, _ := http.NewRequest("GET", "/sub/whatever", nil)
	match := new(RouteMatch)
	matched := r.Match(req, match)

	if matched {
		t.Errorf("Subrouter should not have matched that, got %v", match.Route)
	}
	// Even without a custom handler, MatchErr is set to ErrNotFound
	if match.MatchErr != ErrNotFound {
		t.Errorf("Expected ErrNotFound MatchErr, but was %v", match.MatchErr)
	}

	// Now lets add a 404 handler to subrouter
	s.NotFoundHandler = http.NotFoundHandler()
	req, _ = http.NewRequest("GET", "/sub/whatever", nil)

	// Test the subrouter first
	match = new(RouteMatch)
	matched = s.Match(req, match)
	// Now we should get a match
	if !matched {
		t.Errorf("Subrouter should have matched %s", req.RequestURI)
	}
	// But MatchErr should be set to ErrNotFound anyway
	if match.MatchErr != ErrNotFound {
		t.Errorf("Expected ErrNotFound MatchErr, but was %v", match.MatchErr)
	}

	// Now test the parent (MatchErr should propagate)
	match = new(RouteMatch)
	matched = r.Match(req, match)

	// Now we should get a match
	if !matched {
		t.Errorf("Router should have matched %s via subrouter", req.RequestURI)
	}
	// But MatchErr should be set to ErrNotFound anyway
	if match.MatchErr != ErrNotFound {
		t.Errorf("Expected ErrNotFound MatchErr, but was %v", match.MatchErr)
	}
}

// methodsSubrouterTest models the data necessary for testing handler
// matching for subrouters created after HTTP methods matcher registration.
type methodsSubrouterTest struct {
	title    string
	wantCode int
	router   *Router
	// method is the input into the request and expected response
	method string
	// input request path
	path string
	// redirectTo is the expected location path for strict-slash matches
	redirectTo string
}

// methodHandler writes the method string in response.
func methodHandler(method string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		_, err := w.Write([]byte(method))
		if err != nil {
			log.Printf("Failed writing HTTP response: %v", err)
		}
	}
}

// TestMethodsSubrouterCatchall matches handlers for subrouters where a
// catchall handler is set for a mis-matching method.
func TestMethodsSubrouterCatchall(t *testing.T) {
	t.Parallel()

	router := NewRouter()
	router.Methods("PATCH").Subrouter().PathPrefix("/").HandlerFunc(methodHandler("PUT"))
	router.Methods("GET").Subrouter().HandleFunc("/foo", methodHandler("GET"))
	router.Methods("POST").Subrouter().HandleFunc("/foo", methodHandler("POST"))
	router.Methods("DELETE").Subrouter().HandleFunc("/foo", methodHandler("DELETE"))

	tests := []methodsSubrouterTest{
		{
			title:    "match GET handler",
			router:   router,
			path:     "http://localhost/foo",
			method:   "GET",
			wantCode: http.StatusOK,
		},
		{
			title:    "match POST handler",
			router:   router,
			method:   "POST",
			path:     "http://localhost/foo",
			wantCode: http.StatusOK,
		},
		{
			title:    "match DELETE handler",
			router:   router,
			method:   "DELETE",
			path:     "http://localhost/foo",
			wantCode: http.StatusOK,
		},
		{
			title:    "disallow PUT method",
			router:   router,
			method:   "PUT",
			path:     "http://localhost/foo",
			wantCode: http.StatusMethodNotAllowed,
		},
	}

	for _, test := range tests {
		t.Run(test.title, func(t *testing.T) {
			testMethodsSubrouter(t, test)
		})
	}
}

// TestMethodsSubrouterStrictSlash matches handlers on subrouters with
// strict-slash matchers.
func TestMethodsSubrouterStrictSlash(t *testing.T) {
	t.Parallel()

	router := NewRouter()
	sub := router.PathPrefix("/").Subrouter()
	sub.StrictSlash(true).Path("/foo").Methods("GET").Subrouter().HandleFunc("", methodHandler("GET"))
	sub.StrictSlash(true).Path("/foo/").Methods("PUT").Subrouter().HandleFunc("/", methodHandler("PUT"))
	sub.StrictSlash(true).Path("/foo/").Methods("POST").Subrouter().HandleFunc("/", methodHandler("POST"))

	tests := []methodsSubrouterTest{
		{
			title:    "match POST handler",
			router:   router,
			method:   "POST",
			path:     "http://localhost/foo/",
			wantCode: http.StatusOK,
		},
		{
			title:    "match GET handler",
			router:   router,
			method:   "GET",
			path:     "http://localhost/foo",
			wantCode: http.StatusOK,
		},
		{
			title:      "match POST handler, redirect strict-slash",
			router:     router,
			method:     "POST",
			path:       "http://localhost/foo",
			redirectTo: "http://localhost/foo/",
			wantCode:   http.StatusMovedPermanently,
		},
		{
			title:      "match GET handler, redirect strict-slash",
			router:     router,
			method:     "GET",
			path:       "http://localhost/foo/",
			redirectTo: "http://localhost/foo",
			wantCode:   http.StatusMovedPermanently,
		},
		{
			title:    "disallow DELETE method",
			router:   router,
			method:   "DELETE",
			path:     "http://localhost/foo",
			wantCode: http.StatusMethodNotAllowed,
		},
	}

	for _, test := range tests {
		t.Run(test.title, func(t *testing.T) {
			testMethodsSubrouter(t, test)
		})
	}
}

// TestMethodsSubrouterPathPrefix matches handlers on subrouters created
// on a router with a path prefix matcher and method matcher.
func TestMethodsSubrouterPathPrefix(t *testing.T) {
	t.Parallel()

	router := NewRouter()
	router.PathPrefix("/1").Methods("POST").Subrouter().HandleFunc("/2", methodHandler("POST"))
	router.PathPrefix("/1").Methods("DELETE").Subrouter().HandleFunc("/2", methodHandler("DELETE"))
	router.PathPrefix("/1").Methods("PUT").Subrouter().HandleFunc("/2", methodHandler("PUT"))
	router.PathPrefix("/1").Methods("POST").Subrouter().HandleFunc("/2", methodHandler("POST2"))

	tests := []methodsSubrouterTest{
		{
			title:    "match first POST handler",
			router:   router,
			method:   "POST",
			path:     "http://localhost/1/2",
			wantCode: http.StatusOK,
		},
		{
			title:    "match DELETE handler",
			router:   router,
			method:   "DELETE",
			path:     "http://localhost/1/2",
			wantCode: http.StatusOK,
		},
		{
			title:    "match PUT handler",
			router:   router,
			method:   "PUT",
			path:     "http://localhost/1/2",
			wantCode: http.StatusOK,
		},
		{
			title:    "disallow PATCH method",
			router:   router,
			method:   "PATCH",
			path:     "http://localhost/1/2",
			wantCode: http.StatusMethodNotAllowed,
		},
	}

	for _, test := range tests {
		t.Run(test.title, func(t *testing.T) {
			testMethodsSubrouter(t, test)
		})
	}
}

// TestMethodsSubrouterSubrouter matches handlers on subrouters produced
// from method matchers registered on a root subrouter.
func TestMethodsSubrouterSubrouter(t *testing.T) {
	t.Parallel()

	router := NewRouter()
	sub := router.PathPrefix("/1").Subrouter()
	sub.Methods("POST").Subrouter().HandleFunc("/2", methodHandler("POST"))
	sub.Methods("GET").Subrouter().HandleFunc("/2", methodHandler("GET"))
	sub.Methods("PATCH").Subrouter().HandleFunc("/2", methodHandler("PATCH"))
	sub.HandleFunc("/2", methodHandler("PUT")).Subrouter().Methods("PUT")
	sub.HandleFunc("/2", methodHandler("POST2")).Subrouter().Methods("POST")

	tests := []methodsSubrouterTest{
		{
			title:    "match first POST handler",
			router:   router,
			method:   "POST",
			path:     "http://localhost/1/2",
			wantCode: http.StatusOK,
		},
		{
			title:    "match GET handler",
			router:   router,
			method:   "GET",
			path:     "http://localhost/1/2",
			wantCode: http.StatusOK,
		},
		{
			title:    "match PATCH handler",
			router:   router,
			method:   "PATCH",
			path:     "http://localhost/1/2",
			wantCode: http.StatusOK,
		},
		{
			title:    "match PUT handler",
			router:   router,
			method:   "PUT",
			path:     "http://localhost/1/2",
			wantCode: http.StatusOK,
		},
		{
			title:    "disallow DELETE method",
			router:   router,
			method:   "DELETE",
			path:     "http://localhost/1/2",
			wantCode: http.StatusMethodNotAllowed,
		},
	}

	for _, test := range tests {
		t.Run(test.title, func(t *testing.T) {
			testMethodsSubrouter(t, test)
		})
	}
}

// TestMethodsSubrouterPathVariable matches handlers on matching paths
// with path variables in them.
func TestMethodsSubrouterPathVariable(t *testing.T) {
	t.Parallel()

	router := NewRouter()
	router.Methods("GET").Subrouter().HandleFunc("/foo", methodHandler("GET"))
	router.Methods("POST").Subrouter().HandleFunc("/{any}", methodHandler("POST"))
	router.Methods("DELETE").Subrouter().HandleFunc("/1/{any}", methodHandler("DELETE"))
	router.Methods("PUT").Subrouter().HandleFunc("/1/{any}", methodHandler("PUT"))

	tests := []methodsSubrouterTest{
		{
			title:    "match GET handler",
			router:   router,
			method:   "GET",
			path:     "http://localhost/foo",
			wantCode: http.StatusOK,
		},
		{
			title:    "match POST handler",
			router:   router,
			method:   "POST",
			path:     "http://localhost/foo",
			wantCode: http.StatusOK,
		},
		{
			title:    "match DELETE handler",
			router:   router,
			method:   "DELETE",
			path:     "http://localhost/1/foo",
			wantCode: http.StatusOK,
		},
		{
			title:    "match PUT handler",
			router:   router,
			method:   "PUT",
			path:     "http://localhost/1/foo",
			wantCode: http.StatusOK,
		},
		{
			title:    "disallow PATCH method",
			router:   router,
			method:   "PATCH",
			path:     "http://localhost/1/foo",
			wantCode: http.StatusMethodNotAllowed,
		},
	}

	for _, test := range tests {
		t.Run(test.title, func(t *testing.T) {
			testMethodsSubrouter(t, test)
		})
	}
}

func ExampleSetURLVars() {
	req, _ := http.NewRequest("GET", "/foo", nil)
	req = SetURLVars(req, map[string]string{"foo": "bar"})

	fmt.Println(Vars(req)["foo"])

	// Output: bar
}

// testMethodsSubrouter runs an individual methodsSubrouterTest.
func testMethodsSubrouter(t *testing.T, test methodsSubrouterTest) {
	// Execute request
	req, _ := http.NewRequest(test.method, test.path, nil)
	resp := NewRecorder()
	test.router.ServeHTTP(resp, req)

	switch test.wantCode {
	case http.StatusMethodNotAllowed:
		if resp.Code != http.StatusMethodNotAllowed {
			t.Errorf(`(%s) Expected "405 Method Not Allowed", but got %d code`, test.title, resp.Code)
		} else if matchedMethod := resp.Body.String(); matchedMethod != "" {
			t.Errorf(`(%s) Expected "405 Method Not Allowed", but %q handler was called`, test.title, matchedMethod)
		}

	case http.StatusMovedPermanently:
		if gotLocation := resp.HeaderMap.Get("Location"); gotLocation != test.redirectTo {
			t.Errorf("(%s) Expected %q route-match to redirect to %q, but got %q", test.title, test.method, test.redirectTo, gotLocation)
		}

	case http.StatusOK:
		if matchedMethod := resp.Body.String(); matchedMethod != test.method {
			t.Errorf("(%s) Expected %q handler to be called, but %q handler was called", test.title, test.method, matchedMethod)
		}

	default:
		expectedCodes := []int{http.StatusMethodNotAllowed, http.StatusMovedPermanently, http.StatusOK}
		t.Errorf("(%s) Expected wantCode to be one of: %v, but got %d", test.title, expectedCodes, test.wantCode)
	}
}

func TestSubrouterMatching(t *testing.T) {
	const (
		none, stdOnly, subOnly uint8 = 0, 1 << 0, 1 << 1
		both                         = subOnly | stdOnly
	)

	type request struct {
		Name    string
		Request *http.Request
		Flags   uint8
	}

	cases := []struct {
		Name                string
		Standard, Subrouter func(*Router)
		Requests            []request
	}{
		{
			"pathPrefix",
			func(r *Router) {
				r.PathPrefix("/before").PathPrefix("/after")
			},
			func(r *Router) {
				r.PathPrefix("/before").Subrouter().PathPrefix("/after")
			},
			[]request{
				{"no match final path prefix", newRequest("GET", "/after"), none},
				{"no match parent path prefix", newRequest("GET", "/before"), none},
				{"matches append", newRequest("GET", "/before/after"), both},
				{"matches as prefix", newRequest("GET", "/before/after/1234"), both},
			},
		},
		{
			"path",
			func(r *Router) {
				r.Path("/before").Path("/after")
			},
			func(r *Router) {
				r.Path("/before").Subrouter().Path("/after")
			},
			[]request{
				{"no match subroute path", newRequest("GET", "/after"), none},
				{"no match parent path", newRequest("GET", "/before"), none},
				{"no match as prefix", newRequest("GET", "/before/after/1234"), none},
				{"no match append", newRequest("GET", "/before/after"), none},
			},
		},
		{
			"host",
			func(r *Router) {
				r.Host("before.com").Host("after.com")
			},
			func(r *Router) {
				r.Host("before.com").Subrouter().Host("after.com")
			},
			[]request{
				{"no match before", newRequestHost("GET", "/", "before.com"), none},
				{"no match other", newRequestHost("GET", "/", "other.com"), none},
				{"matches after", newRequestHost("GET", "/", "after.com"), none},
			},
		},
		{
			"queries variant keys",
			func(r *Router) {
				r.Queries("foo", "bar").Queries("cricket", "baseball")
			},
			func(r *Router) {
				r.Queries("foo", "bar").Subrouter().Queries("cricket", "baseball")
			},
			[]request{
				{"matches with all", newRequest("GET", "/?foo=bar&cricket=baseball"), both},
				{"matches with more", newRequest("GET", "/?foo=bar&cricket=baseball&something=else"), both},
				{"no match with none", newRequest("GET", "/"), none},
				{"no match with some", newRequest("GET", "/?cricket=baseball"), none},
			},
		},
		{
			"queries overlapping keys",
			func(r *Router) {
				r.Queries("foo", "bar").Queries("foo", "baz")
			},
			func(r *Router) {
				r.Queries("foo", "bar").Subrouter().Queries("foo", "baz")
			},
			[]request{
				{"no match old value", newRequest("GET", "/?foo=bar"), none},
				{"no match diff value", newRequest("GET", "/?foo=bak"), none},
				{"no match with none", newRequest("GET", "/"), none},
				{"matches override", newRequest("GET", "/?foo=baz"), none},
			},
		},
		{
			"header variant keys",
			func(r *Router) {
				r.Headers("foo", "bar").Headers("cricket", "baseball")
			},
			func(r *Router) {
				r.Headers("foo", "bar").Subrouter().Headers("cricket", "baseball")
			},
			[]request{
				{
					"matches with all",
					newRequestWithHeaders("GET", "/", "foo", "bar", "cricket", "baseball"),
					both,
				},
				{
					"matches with more",
					newRequestWithHeaders("GET", "/", "foo", "bar", "cricket", "baseball", "something", "else"),
					both,
				},
				{"no match with none", newRequest("GET", "/"), none},
				{"no match with some", newRequestWithHeaders("GET", "/", "cricket", "baseball"), none},
			},
		},
		{
			"header overlapping keys",
			func(r *Router) {
				r.Headers("foo", "bar").Headers("foo", "baz")
			},
			func(r *Router) {
				r.Headers("foo", "bar").Subrouter().Headers("foo", "baz")
			},
			[]request{
				{"no match old value", newRequestWithHeaders("GET", "/", "foo", "bar"), none},
				{"no match diff value", newRequestWithHeaders("GET", "/", "foo", "bak"), none},
				{"no match with none", newRequest("GET", "/"), none},
				{"matches override", newRequestWithHeaders("GET", "/", "foo", "baz"), none},
			},
		},
		{
			"method",
			func(r *Router) {
				r.Methods("POST").Methods("GET")
			},
			func(r *Router) {
				r.Methods("POST").Subrouter().Methods("GET")
			},
			[]request{
				{"matches before", newRequest("POST", "/"), none},
				{"no match other", newRequest("HEAD", "/"), none},
				{"matches override", newRequest("GET", "/"), none},
			},
		},
		{
			"schemes",
			func(r *Router) {
				r.Schemes("http").Schemes("https")
			},
			func(r *Router) {
				r.Schemes("http").Subrouter().Schemes("https")
			},
			[]request{
				{"matches overrides", newRequest("GET", "https://www.example.com/"), none},
				{"matches original", newRequest("GET", "http://www.example.com/"), none},
				{"no match other", newRequest("GET", "ftp://www.example.com/"), none},
			},
		},
	}

	// case -> request -> router
	for _, c := range cases {
		t.Run(c.Name, func(t *testing.T) {
			for _, req := range c.Requests {
				t.Run(req.Name, func(t *testing.T) {
					for _, v := range []struct {
						Name     string
						Config   func(*Router)
						Expected bool
					}{
						{"subrouter", c.Subrouter, (req.Flags & subOnly) != 0},
						{"standard", c.Standard, (req.Flags & stdOnly) != 0},
					} {
						r := NewRouter()
						v.Config(r)
						if r.Match(req.Request, &RouteMatch{}) != v.Expected {
							if v.Expected {
								t.Errorf("expected %v match", v.Name)
							} else {
								t.Errorf("expected %v no match", v.Name)
							}
						}
					}
				})
			}
		})
	}
}

// verify that copyRouteConf copies fields as expected.
func Test_copyRouteConf(t *testing.T) {
	var (
		m MatcherFunc = func(*http.Request, *RouteMatch) bool {
			return true
		}
		b BuildVarsFunc = func(i map[string]string) map[string]string {
			return i
		}
		r, _ = newRouteRegexp("hi", regexpTypeHost, routeRegexpOptions{})
	)

	tests := []struct {
		name string
		args routeConf
		want routeConf
	}{
		{
			"empty",
			routeConf{},
			routeConf{},
		},
		{
			"full",
			routeConf{
				useEncodedPath: true,
				strictSlash:    true,
				skipClean:      true,
				regexp:         routeRegexpGroup{host: r, path: r, queries: []*routeRegexp{r}},
				matchers:       []matcher{m},
				buildScheme:    "https",
				buildVarsFunc:  b,
			},
			routeConf{
				useEncodedPath: true,
				strictSlash:    true,
				skipClean:      true,
				regexp:         routeRegexpGroup{host: r, path: r, queries: []*routeRegexp{r}},
				matchers:       []matcher{m},
				buildScheme:    "https",
				buildVarsFunc:  b,
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// special case some incomparable fields of routeConf before delegating to reflect.DeepEqual
			got := copyRouteConf(tt.args)

			// funcs not comparable, just compare length of slices
			if len(got.matchers) != len(tt.want.matchers) {
				t.Errorf("matchers different lengths: %v %v", len(got.matchers), len(tt.want.matchers))
			}
			got.matchers, tt.want.matchers = nil, nil

			// deep equal treats nil slice differently to empty slice so check for zero len first
			{
				bothZero := len(got.regexp.queries) == 0 && len(tt.want.regexp.queries) == 0
				if !bothZero && !reflect.DeepEqual(got.regexp.queries, tt.want.regexp.queries) {
					t.Errorf("queries unequal: %v %v", got.regexp.queries, tt.want.regexp.queries)
				}
				got.regexp.queries, tt.want.regexp.queries = nil, nil
			}

			// funcs not comparable, just compare nullity
			if (got.buildVarsFunc == nil) != (tt.want.buildVarsFunc == nil) {
				t.Errorf("build vars funcs unequal: %v %v", got.buildVarsFunc == nil, tt.want.buildVarsFunc == nil)
			}
			got.buildVarsFunc, tt.want.buildVarsFunc = nil, nil

			// finish the deal
			if !reflect.DeepEqual(got, tt.want) {
				t.Errorf("route confs unequal: %v %v", got, tt.want)
			}
		})
	}
}

func TestMethodNotAllowed(t *testing.T) {
	handler := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }
	router := NewRouter()
	router.HandleFunc("/thing", handler).Methods(http.MethodGet)
	router.HandleFunc("/something", handler).Methods(http.MethodGet)

	w := NewRecorder()
	req := newRequest(http.MethodPut, "/thing")

	router.ServeHTTP(w, req)

	if w.Code != http.StatusMethodNotAllowed {
		t.Fatalf("Expected status code 405 (got %d)", w.Code)
	}
}

func TestMethodNotAllowedSubrouterWithSeveralRoutes(t *testing.T) {
	handler := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }

	router := NewRouter()
	subrouter := router.PathPrefix("/v1").Subrouter()
	subrouter.HandleFunc("/api", handler).Methods(http.MethodGet)
	subrouter.HandleFunc("/api/{id}", handler).Methods(http.MethodGet)

	w := NewRecorder()
	req := newRequest(http.MethodPut, "/v1/api")
	router.ServeHTTP(w, req)

	if w.Code != http.StatusMethodNotAllowed {
		t.Errorf("Expected status code 405 (got %d)", w.Code)
	}
}

type customMethodNotAllowedHandler struct {
	msg string
}

func (h customMethodNotAllowedHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusMethodNotAllowed)
	fmt.Fprint(w, h.msg)
}

func TestSubrouterCustomMethodNotAllowed(t *testing.T) {
	handler := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }

	router := NewRouter()
	router.HandleFunc("/test", handler).Methods(http.MethodGet)
	router.MethodNotAllowedHandler = customMethodNotAllowedHandler{msg: "custom router handler"}

	subrouter := router.PathPrefix("/sub").Subrouter()
	subrouter.HandleFunc("/test", handler).Methods(http.MethodGet)
	subrouter.MethodNotAllowedHandler = customMethodNotAllowedHandler{msg: "custom sub router handler"}

	testCases := map[string]struct {
		path   string
		expMsg string
	}{
		"router method not allowed": {
			path:   "/test",
			expMsg: "custom router handler",
		},
		"subrouter method not allowed": {
			path:   "/sub/test",
			expMsg: "custom sub router handler",
		},
	}

	for name, tc := range testCases {
		t.Run(name, func(tt *testing.T) {
			w := NewRecorder()
			req := newRequest(http.MethodPut, tc.path)

			router.ServeHTTP(w, req)

			if w.Code != http.StatusMethodNotAllowed {
				tt.Errorf("Expected status code 405 (got %d)", w.Code)
			}

			b, err := io.ReadAll(w.Body)
			if err != nil {
				tt.Errorf("failed to read body: %v", err)
			}

			if string(b) != tc.expMsg {
				tt.Errorf("expected msg %q, got %q", tc.expMsg, string(b))
			}
		})
	}
}

func TestSubrouterNotFound(t *testing.T) {
	handler := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }
	router := NewRouter()
	router.Path("/a").Subrouter().HandleFunc("/thing", handler).Methods(http.MethodGet)
	router.Path("/b").Subrouter().HandleFunc("/something", handler).Methods(http.MethodGet)

	w := NewRecorder()
	req := newRequest(http.MethodPut, "/not-present")

	router.ServeHTTP(w, req)

	if w.Code != http.StatusNotFound {
		t.Fatalf("Expected status code 404 (got %d)", w.Code)
	}
}

func TestContextMiddleware(t *testing.T) {
	withTimeout := func(h http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			ctx, cancel := context.WithTimeout(r.Context(), time.Minute)
			defer cancel()
			h.ServeHTTP(w, r.WithContext(ctx))
		})
	}

	r := NewRouter()
	r.Handle("/path/{foo}", withTimeout(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		vars := Vars(r)
		if vars["foo"] != "bar" {
			t.Fatal("Expected foo var to be set")
		}
	})))

	rec := NewRecorder()
	req := newRequest("GET", "/path/bar")
	r.ServeHTTP(rec, req)
}

func TestGetVarNames(t *testing.T) {
	r := NewRouter()

	route := r.Host("{domain}").
		Path("/{group}/{item_id}").
		Queries("some_data1", "{some_data1}").
		Queries("some_data2_and_3", "{some_data2}.{some_data3}")

	// Order of vars in the slice is not guaranteed, so just check for existence
	expected := map[string]bool{
		"domain":     true,
		"group":      true,
		"item_id":    true,
		"some_data1": true,
		"some_data2": true,
		"some_data3": true,
	}

	varNames, err := route.GetVarNames()
	if err != nil {
		t.Fatal(err)
	}

	if len(varNames) != len(expected) {
		t.Fatalf("expected %d names, got %d", len(expected), len(varNames))
	}

	for _, varName := range varNames {
		if !expected[varName] {
			t.Fatalf("got unexpected %s", varName)
		}
	}
}

func getPopulateContextTestCases() []struct {
	name                 string
	path                 string
	omitRouteFromContext bool
	wantVar              string
	wantStaticRoute      bool
	wantDynamicRoute     bool
} {
	return []struct {
		name                 string
		path                 string
		omitRouteFromContext bool
		wantVar              string
		wantStaticRoute      bool
		wantDynamicRoute     bool
	}{
		{
			name:            "no populated vars",
			path:            "/static",
			wantVar:         "",
			wantStaticRoute: true,
		},
		{
			name:             "empty var",
			path:             "/dynamic/",
			wantVar:          "",
			wantDynamicRoute: true,
		},
		{
			name:             "populated vars",
			path:             "/dynamic/foo",
			wantVar:          "foo",
			wantDynamicRoute: true,
		},
		{
			name:                 "omit route /static",
			path:                 "/static",
			omitRouteFromContext: true,
			wantVar:              "",
			wantStaticRoute:      false,
		},
		{
			name:                 "omit route /dynamic",
			path:                 "/dynamic/",
			omitRouteFromContext: true,
			wantVar:              "",
			wantDynamicRoute:     false,
		},
	}
}

func TestPopulateContext(t *testing.T) {
	testCases := getPopulateContextTestCases()
	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			matched := false
			r := NewRouter()
			r.OmitRouteFromContext(tc.omitRouteFromContext)
			var static *Route
			var dynamic *Route
			fn := func(w http.ResponseWriter, r *http.Request) {
				matched = true
				if got := Vars(r)["x"]; got != tc.wantVar {
					t.Fatalf("wantVar=%q, got=%q", tc.wantVar, got)
				}
				switch {
				case tc.wantDynamicRoute:
					r2 := CurrentRoute(r)
					if r2 != dynamic || r2.GetName() != "dynamic" {
						t.Fatalf("expected dynmic route in ctx, got %v", r2)
					}
				case tc.wantStaticRoute:
					r2 := CurrentRoute(r)
					if r2 != static || r2.GetName() != "static" {
						t.Fatalf("expected static route in ctx, got %v", r2)
					}
				default:
					if r2 := CurrentRoute(r); r2 != nil {
						t.Fatalf("expected no route in ctx, got %v", r2)
					}
				}
				w.WriteHeader(http.StatusNoContent)
			}
			static = r.Name("static").Path("/static").HandlerFunc(fn)
			dynamic = r.Name("dynamic").Path("/dynamic/{x:.*}").HandlerFunc(fn)
			req := newRequest(http.MethodGet, "http://localhost"+tc.path)
			rec := NewRecorder()
			r.ServeHTTP(rec, req)
			if !matched {
				t.Fatal("Expected route to match")
			}
		})
	}
}

func BenchmarkPopulateContext(b *testing.B) {
	testCases := getPopulateContextTestCases()
	for _, tc := range testCases {
		b.Run(tc.name, func(b *testing.B) {
			matched := false
			r := NewRouter()
			r.OmitRouteFromContext(tc.omitRouteFromContext)
			fn := func(w http.ResponseWriter, r *http.Request) {
				matched = true
				w.WriteHeader(http.StatusNoContent)
			}
			r.Name("static").Path("/static").HandlerFunc(fn)
			r.Name("dynamic").Path("/dynamic/{x:.*}").HandlerFunc(fn)
			req := newRequest(http.MethodGet, "http://localhost"+tc.path)
			rec := NewRecorder()
			b.ReportAllocs()
			b.ResetTimer()
			for i := 0; i < b.N; i++ {
				r.ServeHTTP(rec, req)
			}
			if !matched {
				b.Fatal("Expected route to match")
			}
		})
	}
}

// mapToPairs converts a string map to a slice of string pairs
func mapToPairs(m map[string]string) []string {
	var i int
	p := make([]string, len(m)*2)
	for k, v := range m {
		p[i] = k
		p[i+1] = v
		i += 2
	}
	return p
}

// stringMapEqual checks the equality of two string maps
func stringMapEqual(m1, m2 map[string]string) bool {
	nil1 := m1 == nil
	nil2 := m2 == nil
	if nil1 != nil2 || len(m1) != len(m2) {
		return false
	}
	for k, v := range m1 {
		if v != m2[k] {
			return false
		}
	}
	return true
}

// stringHandler returns a handler func that writes a message 's' to the
// http.ResponseWriter.
func stringHandler(s string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		_, err := w.Write([]byte(s))
		if err != nil {
			log.Printf("Failed writing HTTP response: %v", err)
		}
	}
}

// newRequest is a helper function to create a new request with a method and url.
// The request returned is a 'server' request as opposed to a 'client' one through
// simulated write onto the wire and read off of the wire.
// The differences between requests are detailed in the net/http package.
func newRequest(method, url string) *http.Request {
	req, err := http.NewRequest(method, url, nil)
	if err != nil {
		panic(err)
	}
	// extract the escaped original host+path from url
	// http://localhost/path/here?v=1#frag -> //localhost/path/here
	opaque := ""
	if i := len(req.URL.Scheme); i > 0 {
		opaque = url[i+1:]
	}

	if i := strings.LastIndex(opaque, "?"); i > -1 {
		opaque = opaque[:i]
	}
	if i := strings.LastIndex(opaque, "#"); i > -1 {
		opaque = opaque[:i]
	}

	// Escaped host+path workaround as detailed in https://golang.org/pkg/net/url/#URL
	// for < 1.5 client side workaround
	req.URL.Opaque = opaque

	// Simulate writing to wire
	var buff bytes.Buffer
	err = req.Write(&buff)
	if err != nil {
		log.Printf("Failed writing HTTP request: %v", err)
	}
	ioreader := bufio.NewReader(&buff)

	// Parse request off of 'wire'
	req, err = http.ReadRequest(ioreader)
	if err != nil {
		panic(err)
	}
	return req
}

// create a new request with the provided headers
func newRequestWithHeaders(method, url string, headers ...string) *http.Request {
	req := newRequest(method, url)

	if len(headers)%2 != 0 {
		panic(fmt.Sprintf("Expected headers length divisible by 2 but got %v", len(headers)))
	}

	for i := 0; i < len(headers); i += 2 {
		req.Header.Set(headers[i], headers[i+1])
	}

	return req
}

// newRequestHost a new request with a method, url, and host header
func newRequestHost(method, url, host string) *http.Request {
	req := httptest.NewRequest(method, url, nil)
	req.Host = host
	return req
}


================================================
FILE: old_test.go
================================================
// Old tests ported to Go1. This is a mess. Want to drop it one day.

// Copyright 2011 Gorilla Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package mux

import (
	"bytes"
	"net/http"
	"testing"
)

// ----------------------------------------------------------------------------
// ResponseRecorder
// ----------------------------------------------------------------------------
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// ResponseRecorder is an implementation of http.ResponseWriter that
// records its mutations for later inspection in tests.
type ResponseRecorder struct {
	Code      int           // the HTTP response code from WriteHeader
	HeaderMap http.Header   // the HTTP response headers
	Body      *bytes.Buffer // if non-nil, the bytes.Buffer to append written data to
	Flushed   bool
}

// NewRecorder returns an initialized ResponseRecorder.
func NewRecorder() *ResponseRecorder {
	return &ResponseRecorder{
		HeaderMap: make(http.Header),
		Body:      new(bytes.Buffer),
	}
}

// Header returns the response headers.
func (rw *ResponseRecorder) Header() http.Header {
	return rw.HeaderMap
}

// Write always succeeds and writes to rw.Body, if not nil.
func (rw *ResponseRecorder) Write(buf []byte) (int, error) {
	if rw.Body != nil {
		rw.Body.Write(buf)
	}
	if rw.Code == 0 {
		rw.Code = http.StatusOK
	}
	return len(buf), nil
}

// WriteHeader sets rw.Code.
func (rw *ResponseRecorder) WriteHeader(code int) {
	rw.Code = code
}

// Flush sets rw.Flushed to true.
func (rw *ResponseRecorder) Flush() {
	rw.Flushed = true
}

// ----------------------------------------------------------------------------

func TestRouteMatchers(t *testing.T) {
	var scheme, host, path, query, method string
	var headers map[string]string
	var resultVars map[bool]map[string]string

	router := NewRouter()
	router.NewRoute().Host("{var1}.google.com").
		Path("/{var2:[a-z]+}/{var3:[0-9]+}").
		Queries("foo", "bar").
		Methods("GET").
		Schemes("https").
		Headers("x-requested-with", "XMLHttpRequest")
	router.NewRoute().Host("www.{var4}.com").
		PathPrefix("/foo/{var5:[a-z]+}/{var6:[0-9]+}").
		Queries("baz", "ding").
		Methods("POST").
		Schemes("http").
		Headers("Content-Type", "application/json")

	reset := func() {
		// Everything match.
		scheme = "https"
		host = "www.google.com"
		path = "/product/42"
		query = "?foo=bar"
		method = "GET"
		headers = map[string]string{"X-Requested-With": "XMLHttpRequest"}
		resultVars = map[bool]map[string]string{
			true:  {"var1": "www", "var2": "product", "var3": "42"},
			false: {},
		}
	}

	reset2 := func() {
		// Everything match.
		scheme = "http"
		host = "www.google.com"
		path = "/foo/product/42/path/that/is/ignored"
		query = "?baz=ding"
		method = "POST"
		headers = map[string]string{"Content-Type": "app
Download .txt
gitextract_gscv0_ce/

├── .editorconfig
├── .github/
│   └── workflows/
│       ├── issues.yml
│       ├── security.yml
│       ├── test.yml
│       └── verify.yml
├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── bench_test.go
├── doc.go
├── example_authentication_middleware_test.go
├── example_cors_method_middleware_test.go
├── example_route_test.go
├── example_route_vars_test.go
├── go.mod
├── middleware.go
├── middleware_test.go
├── mux.go
├── mux_httpserver_test.go
├── mux_test.go
├── old_test.go
├── regexp.go
├── regexp_test.go
├── route.go
├── route_test.go
└── test_helpers.go
Download .txt
SYMBOL INDEX (267 symbols across 16 files)

FILE: bench_test.go
  function BenchmarkMux (line 13) | func BenchmarkMux(b *testing.B) {
  function BenchmarkMuxSimple (line 24) | func BenchmarkMuxSimple(b *testing.B) {
  function BenchmarkMuxAlternativeInRegexp (line 56) | func BenchmarkMuxAlternativeInRegexp(b *testing.B) {
  function BenchmarkManyPathVariables (line 69) | func BenchmarkManyPathVariables(b *testing.B) {

FILE: example_authentication_middleware_test.go
  type authenticationMiddleware (line 11) | type authenticationMiddleware struct
    method Populate (line 16) | func (amw *authenticationMiddleware) Populate() {
    method Middleware (line 24) | func (amw *authenticationMiddleware) Middleware(next http.Handler) htt...
  function Example_authenticationMiddleware (line 38) | func Example_authenticationMiddleware() {

FILE: example_cors_method_middleware_test.go
  function ExampleCORSMethodMiddleware (line 11) | func ExampleCORSMethodMiddleware() {

FILE: example_route_test.go
  function ExampleRoute_HeadersRegexp (line 13) | func ExampleRoute_HeadersRegexp() {
  function ExampleRoute_HeadersRegexp_exactMatch (line 35) | func ExampleRoute_HeadersRegexp_exactMatch() {

FILE: example_route_vars_test.go
  function ExampleRoute_GetVarNames (line 10) | func ExampleRoute_GetVarNames() {

FILE: middleware.go
  type MiddlewareFunc (line 11) | type MiddlewareFunc
    method Middleware (line 19) | func (mw MiddlewareFunc) Middleware(handler http.Handler) http.Handler {
  type middleware (line 14) | type middleware interface
  method Use (line 24) | func (r *Router) Use(mwf ...MiddlewareFunc) {
  method useInterface (line 31) | func (r *Router) useInterface(mw middleware) {
  method Use (line 38) | func (r *Route) Use(mwf ...MiddlewareFunc) *Route {
  method useInterface (line 47) | func (r *Route) useInterface(mw middleware) {
  function CORSMethodMiddleware (line 55) | func CORSMethodMiddleware(r *Router) MiddlewareFunc {
  function getAllMethodsForRoute (line 74) | func getAllMethodsForRoute(r *Router, req *http.Request) ([]string, erro...

FILE: middleware_test.go
  type testMiddleware (line 10) | type testMiddleware struct
    method Middleware (line 14) | func (tm *testMiddleware) Middleware(h http.Handler) http.Handler {
  function dummyHandler (line 21) | func dummyHandler(w http.ResponseWriter, r *http.Request) {}
  function TestMiddlewareAdd (line 23) | func TestMiddlewareAdd(t *testing.T) {
  function TestMiddleware (line 59) | func TestMiddleware(t *testing.T) {
  function TestMiddlewareSubrouter (line 120) | func TestMiddlewareSubrouter(t *testing.T) {
  function TestMiddlewareExecution (line 185) | func TestMiddlewareExecution(t *testing.T) {
  function TestMiddlewareNotFound (line 254) | func TestMiddlewareNotFound(t *testing.T) {
  function TestMiddlewareMethodMismatch (line 304) | func TestMiddlewareMethodMismatch(t *testing.T) {
  function TestMiddlewareNotFoundSubrouter (line 354) | func TestMiddlewareNotFoundSubrouter(t *testing.T) {
  function TestMiddlewareMethodMismatchSubrouter (line 412) | func TestMiddlewareMethodMismatchSubrouter(t *testing.T) {
  function TestCORSMethodMiddleware (line 470) | func TestCORSMethodMiddleware(t *testing.T) {
  function TestCORSMethodMiddlewareSubrouter (line 584) | func TestCORSMethodMiddlewareSubrouter(t *testing.T) {
  function TestMiddlewareOnMultiSubrouter (line 604) | func TestMiddlewareOnMultiSubrouter(t *testing.T) {

FILE: mux.go
  function NewRouter (line 32) | func NewRouter() *Router {
  type Router (line 54) | type Router struct
    method Match (line 151) | func (r *Router) Match(req *http.Request, match *RouteMatch) bool {
    method ServeHTTP (line 188) | func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    method Get (line 232) | func (r *Router) Get(name string) *Route {
    method GetRoute (line 238) | func (r *Router) GetRoute(name string) *Route {
    method StrictSlash (line 261) | func (r *Router) StrictSlash(value bool) *Router {
    method SkipClean (line 274) | func (r *Router) SkipClean(value bool) *Router {
    method OmitRouteFromContext (line 284) | func (r *Router) OmitRouteFromContext(value bool) *Router {
    method OmitRouterFromContext (line 293) | func (r *Router) OmitRouterFromContext(value bool) *Router {
    method UseEncodedPath (line 304) | func (r *Router) UseEncodedPath() *Router {
    method NewRoute (line 314) | func (r *Router) NewRoute() *Route {
    method Name (line 323) | func (r *Router) Name(name string) *Route {
    method Handle (line 329) | func (r *Router) Handle(path string, handler http.Handler) *Route {
    method HandleFunc (line 335) | func (r *Router) HandleFunc(path string, f func(http.ResponseWriter,
    method Headers (line 342) | func (r *Router) Headers(pairs ...string) *Route {
    method Host (line 348) | func (r *Router) Host(tpl string) *Route {
    method MatcherFunc (line 354) | func (r *Router) MatcherFunc(f MatcherFunc) *Route {
    method Methods (line 360) | func (r *Router) Methods(methods ...string) *Route {
    method Path (line 366) | func (r *Router) Path(tpl string) *Route {
    method PathPrefix (line 372) | func (r *Router) PathPrefix(tpl string) *Route {
    method Queries (line 378) | func (r *Router) Queries(pairs ...string) *Route {
    method Schemes (line 384) | func (r *Router) Schemes(schemes ...string) *Route {
    method BuildVarsFunc (line 390) | func (r *Router) BuildVarsFunc(f BuildVarsFunc) *Route {
    method Walk (line 397) | func (r *Router) Walk(walkFn WalkFunc) error {
    method walk (line 410) | func (r *Router) walk(walkFn WalkFunc, ancestors []*Route) error {
  type routeConf (line 82) | type routeConf struct
  function copyRouteConf (line 113) | func copyRouteConf(r routeConf) routeConf {
  function copyRouteRegexp (line 135) | func copyRouteRegexp(r *routeRegexp) *routeRegexp {
  type WalkFunc (line 408) | type WalkFunc
  type RouteMatch (line 446) | type RouteMatch struct
  type contextKey (line 457) | type contextKey
  constant varsKey (line 460) | varsKey contextKey = iota
  constant routeKey (line 461) | routeKey
  constant routerKey (line 462) | routerKey
  function Vars (line 466) | func Vars(r *http.Request) map[string]string {
  function CurrentRoute (line 477) | func CurrentRoute(r *http.Request) *Route {
  function CurrentRouter (line 484) | func CurrentRouter(r *http.Request) *Router {
  function requestWithVars (line 493) | func requestWithVars(r *http.Request, vars map[string]string) *http.Requ...
  function requestWithRouteAndVars (line 505) | func requestWithRouteAndVars(r *http.Request, route *Route, vars map[str...
  function requestWithRouter (line 513) | func requestWithRouter(r *http.Request, router *Router) *http.Request {
  function cleanPath (line 524) | func cleanPath(p string) string {
  function replaceURLPath (line 542) | func replaceURLPath(u *url.URL, p string) string {
  function uniqueVars (line 550) | func uniqueVars(s1, s2 []string) error {
  function checkPairs (line 563) | func checkPairs(pairs ...string) (int, error) {
  function mapFromPairsToString (line 574) | func mapFromPairsToString(pairs ...string) (map[string]string, error) {
  function mapFromPairsToRegex (line 588) | func mapFromPairsToRegex(pairs ...string) (map[string]*regexp.Regexp, er...
  function matchInArray (line 605) | func matchInArray(arr []string, value string) bool {
  function matchMapWithString (line 615) | func matchMapWithString(toCheck map[string]string, toMatch map[string][]...
  function matchMapWithRegex (line 643) | func matchMapWithRegex(toCheck map[string]*regexp.Regexp, toMatch map[st...
  function methodNotAllowed (line 670) | func methodNotAllowed(w http.ResponseWriter, r *http.Request) {
  function methodNotAllowedHandler (line 676) | func methodNotAllowedHandler() http.Handler { return http.HandlerFunc(me...

FILE: mux_httpserver_test.go
  function TestSchemeMatchers (line 14) | func TestSchemeMatchers(t *testing.T) {

FILE: mux_test.go
  method GoString (line 24) | func (r *Route) GoString() string {
  method GoString (line 32) | func (r *routeRegexp) GoString() string {
  type routeTest (line 36) | type routeTest struct
  function TestHost (line 55) | func TestHost(t *testing.T) {
  function TestPath (line 229) | func TestPath(t *testing.T) {
  function TestPathPrefix (line 465) | func TestPathPrefix(t *testing.T) {
  function TestSchemeHostPath (line 545) | func TestSchemeHostPath(t *testing.T) {
  function TestHeaders (line 654) | func TestHeaders(t *testing.T) {
  function TestMethods (line 714) | func TestMethods(t *testing.T) {
  function TestQueries (line 767) | func TestQueries(t *testing.T) {
  function TestSchemes (line 1077) | func TestSchemes(t *testing.T) {
  function TestMatcherFunc (line 1129) | func TestMatcherFunc(t *testing.T) {
  function TestBuildVarsFunc (line 1163) | func TestBuildVarsFunc(t *testing.T) {
  function TestSubRouter (line 1201) | func TestSubRouter(t *testing.T) {
  function TestNamedRoutes (line 1434) | func TestNamedRoutes(t *testing.T) {
  function TestNameMultipleCalls (line 1458) | func TestNameMultipleCalls(t *testing.T) {
  function TestStrictSlash (line 1467) | func TestStrictSlash(t *testing.T) {
  function TestUseEncodedPath (line 1543) | func TestUseEncodedPath(t *testing.T) {
  function TestWalkSingleDepth (line 1578) | func TestWalkSingleDepth(t *testing.T) {
  function TestWalkNested (line 1623) | func TestWalkNested(t *testing.T) {
  function TestWalkSubrouters (line 1673) | func TestWalkSubrouters(t *testing.T) {
  function TestWalkErrorRoute (line 1701) | func TestWalkErrorRoute(t *testing.T) {
  function TestWalkErrorMatcher (line 1713) | func TestWalkErrorMatcher(t *testing.T) {
  function TestWalkErrorHandler (line 1724) | func TestWalkErrorHandler(t *testing.T) {
  function TestSubrouterErrorHandling (line 1737) | func TestSubrouterErrorHandling(t *testing.T) {
  function TestPanicOnCapturingGroups (line 1762) | func TestPanicOnCapturingGroups(t *testing.T) {
  function TestRouterInContext (line 1771) | func TestRouterInContext(t *testing.T) {
  function getRouteTemplate (line 1842) | func getRouteTemplate(route *Route) string {
  function testRoute (line 1854) | func testRoute(t *testing.T, test routeTest) {
  function testUseEscapedRoute (line 1951) | func testUseEscapedRoute(t *testing.T, test routeTest) {
  function testTemplate (line 1956) | func testTemplate(t *testing.T, test routeTest) {
  function testMethods (line 1978) | func testMethods(t *testing.T, test routeTest) {
  function testRegexp (line 1986) | func testRegexp(t *testing.T, test routeTest) {
  function testQueriesRegexp (line 1994) | func testQueriesRegexp(t *testing.T, test routeTest) {
  function testQueriesTemplates (line 2003) | func testQueriesTemplates(t *testing.T, test routeTest) {
  type TestA301ResponseWriter (line 2012) | type TestA301ResponseWriter struct
    method Header (line 2017) | func (ho *TestA301ResponseWriter) Header() http.Header {
    method Write (line 2021) | func (ho *TestA301ResponseWriter) Write(b []byte) (int, error) {
    method WriteHeader (line 2025) | func (ho *TestA301ResponseWriter) WriteHeader(code int) {
  function Test301Redirect (line 2029) | func Test301Redirect(t *testing.T) {
  function TestSkipClean (line 2052) | func TestSkipClean(t *testing.T) {
  function TestSubrouterHeader (line 2071) | func TestSubrouterHeader(t *testing.T) {
  function TestNoMatchMethodErrorHandler (line 2100) | func TestNoMatchMethodErrorHandler(t *testing.T) {
  function TestMultipleDefinitionOfSamePathWithDifferentMethods (line 2139) | func TestMultipleDefinitionOfSamePathWithDifferentMethods(t *testing.T) {
  function TestMultipleDefinitionOfSamePathWithDifferentQueries (line 2186) | func TestMultipleDefinitionOfSamePathWithDifferentQueries(t *testing.T) {
  function TestErrMatchNotFound (line 2204) | func TestErrMatchNotFound(t *testing.T) {
  type methodsSubrouterTest (line 2257) | type methodsSubrouterTest struct
  function methodHandler (line 2270) | func methodHandler(method string) http.HandlerFunc {
  function TestMethodsSubrouterCatchall (line 2281) | func TestMethodsSubrouterCatchall(t *testing.T) {
  function TestMethodsSubrouterStrictSlash (line 2330) | func TestMethodsSubrouterStrictSlash(t *testing.T) {
  function TestMethodsSubrouterPathPrefix (line 2388) | func TestMethodsSubrouterPathPrefix(t *testing.T) {
  function TestMethodsSubrouterSubrouter (line 2437) | func TestMethodsSubrouterSubrouter(t *testing.T) {
  function TestMethodsSubrouterPathVariable (line 2495) | func TestMethodsSubrouterPathVariable(t *testing.T) {
  function ExampleSetURLVars (line 2549) | func ExampleSetURLVars() {
  function testMethodsSubrouter (line 2559) | func testMethodsSubrouter(t *testing.T, test methodsSubrouterTest) {
  function TestSubrouterMatching (line 2589) | func TestSubrouterMatching(t *testing.T) {
  function Test_copyRouteConf (line 2778) | func Test_copyRouteConf(t *testing.T) {
  function TestMethodNotAllowed (line 2856) | func TestMethodNotAllowed(t *testing.T) {
  function TestMethodNotAllowedSubrouterWithSeveralRoutes (line 2872) | func TestMethodNotAllowedSubrouterWithSeveralRoutes(t *testing.T) {
  type customMethodNotAllowedHandler (line 2889) | type customMethodNotAllowedHandler struct
    method ServeHTTP (line 2893) | func (h customMethodNotAllowedHandler) ServeHTTP(w http.ResponseWriter...
  function TestSubrouterCustomMethodNotAllowed (line 2898) | func TestSubrouterCustomMethodNotAllowed(t *testing.T) {
  function TestSubrouterNotFound (line 2946) | func TestSubrouterNotFound(t *testing.T) {
  function TestContextMiddleware (line 2962) | func TestContextMiddleware(t *testing.T) {
  function TestGetVarNames (line 2984) | func TestGetVarNames(t *testing.T) {
  function getPopulateContextTestCases (line 3018) | func getPopulateContextTestCases() []struct {
  function TestPopulateContext (line 3069) | func TestPopulateContext(t *testing.T) {
  function BenchmarkPopulateContext (line 3113) | func BenchmarkPopulateContext(b *testing.B) {
  function mapToPairs (line 3141) | func mapToPairs(m map[string]string) []string {
  function stringMapEqual (line 3153) | func stringMapEqual(m1, m2 map[string]string) bool {
  function stringHandler (line 3169) | func stringHandler(s string) http.HandlerFunc {
  function newRequest (line 3182) | func newRequest(method, url string) *http.Request {
  function newRequestWithHeaders (line 3222) | func newRequestWithHeaders(method, url string, headers ...string) *http....
  function newRequestHost (line 3237) | func newRequestHost(method, url, host string) *http.Request {

FILE: old_test.go
  type ResponseRecorder (line 24) | type ResponseRecorder struct
    method Header (line 40) | func (rw *ResponseRecorder) Header() http.Header {
    method Write (line 45) | func (rw *ResponseRecorder) Write(buf []byte) (int, error) {
    method WriteHeader (line 56) | func (rw *ResponseRecorder) WriteHeader(code int) {
    method Flush (line 61) | func (rw *ResponseRecorder) Flush() {
  function NewRecorder (line 32) | func NewRecorder() *ResponseRecorder {
  function TestRouteMatchers (line 67) | func TestRouteMatchers(t *testing.T) {
  type headerMatcherTest (line 225) | type headerMatcherTest struct
  type hostMatcherTest (line 249) | type hostMatcherTest struct
  type methodMatcherTest (line 283) | type methodMatcherTest struct
  type pathMatcherTest (line 312) | type pathMatcherTest struct
  type schemeMatcherTest (line 334) | type schemeMatcherTest struct
  type urlBuildingTest (line 363) | type urlBuildingTest struct
  function TestHeaderMatcher (line 412) | func TestHeaderMatcher(t *testing.T) {
  function TestHostMatcher (line 430) | func TestHostMatcher(t *testing.T) {
  function TestMethodMatcher (line 464) | func TestMethodMatcher(t *testing.T) {
  function TestPathMatcher (line 479) | func TestPathMatcher(t *testing.T) {
  function TestSchemeMatcher (line 509) | func TestSchemeMatcher(t *testing.T) {
  function TestUrlBuilding (line 524) | func TestUrlBuilding(t *testing.T) {
  function TestMatchedRouteName (line 547) | func TestMatchedRouteName(t *testing.T) {
  function TestSubRouting (line 567) | func TestSubRouting(t *testing.T) {
  function TestVariableNames (line 590) | func TestVariableNames(t *testing.T) {
  function TestRedirectSlash (line 597) | func TestRedirectSlash(t *testing.T) {
  function TestNewRegexp (line 654) | func TestNewRegexp(t *testing.T) {

FILE: regexp.go
  type routeRegexpOptions (line 17) | type routeRegexpOptions struct
  type regexpType (line 22) | type regexpType
  constant regexpTypePath (line 25) | regexpTypePath regexpType = iota
  constant regexpTypeHost (line 26) | regexpTypeHost
  constant regexpTypePrefix (line 27) | regexpTypePrefix
  constant regexpTypeQuery (line 28) | regexpTypeQuery
  function newRouteRegexp (line 41) | func newRouteRegexp(tpl string, typ regexpType, options routeRegexpOptio...
  type routeRegexp (line 169) | type routeRegexp struct
    method Match (line 189) | func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool {
    method url (line 212) | func (r *routeRegexp) url(values map[string]string) (string, error) {
    method getURLQuery (line 243) | func (r *routeRegexp) getURLQuery(req *http.Request) string {
    method matchQueryString (line 293) | func (r *routeRegexp) matchQueryString(req *http.Request) bool {
  function findFirstQueryKey (line 257) | func findFirstQueryKey(rawQuery, key string) (value string, ok bool) {
  function braceIndices (line 299) | func braceIndices(s string) ([]int, error) {
  function varGroupName (line 323) | func varGroupName(idx int) string {
  type routeRegexpGroup (line 332) | type routeRegexpGroup struct
    method setMatch (line 339) | func (v routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r...
  function getHost (line 399) | func getHost(r *http.Request) string {
  function extractVars (line 406) | func extractVars(input string, matches []int, names []string, output map...

FILE: regexp_test.go
  function Test_newRouteRegexp_Errors (line 11) | func Test_newRouteRegexp_Errors(t *testing.T) {
  function Test_findFirstQueryKey (line 36) | func Test_findFirstQueryKey(t *testing.T) {
  function Benchmark_findQueryKey (line 68) | func Benchmark_findQueryKey(b *testing.B) {
  function Benchmark_findQueryKeyGoLib (line 91) | func Benchmark_findQueryKeyGoLib(b *testing.B) {

FILE: route.go
  type Route (line 17) | type Route struct
    method SkipClean (line 42) | func (r *Route) SkipClean() bool {
    method Match (line 47) | func (r *Route) Match(req *http.Request, match *RouteMatch) bool {
    method GetError (line 121) | func (r *Route) GetError() error {
    method BuildOnly (line 126) | func (r *Route) BuildOnly() *Route {
    method Metadata (line 134) | func (r *Route) Metadata(key any, value any) *Route {
    method GetMetadata (line 144) | func (r *Route) GetMetadata() map[any]any {
    method MetadataContains (line 149) | func (r *Route) MetadataContains(key any) bool {
    method GetMetadataValue (line 155) | func (r *Route) GetMetadataValue(key any) (any, error) {
    method GetMetadataValueOr (line 165) | func (r *Route) GetMetadataValueOr(key any, fallbackValue any) any {
    method Handler (line 177) | func (r *Route) Handler(handler http.Handler) *Route {
    method HandlerFunc (line 185) | func (r *Route) HandlerFunc(f func(http.ResponseWriter, *http.Request)...
    method GetHandler (line 190) | func (r *Route) GetHandler() http.Handler {
    method GetHandlerWithMiddlewares (line 196) | func (r *Route) GetHandlerWithMiddlewares() http.Handler {
    method Name (line 212) | func (r *Route) Name(name string) *Route {
    method GetName (line 225) | func (r *Route) GetName() string {
    method addMatcher (line 239) | func (r *Route) addMatcher(m matcher) *Route {
    method addRegexpMatcher (line 247) | func (r *Route) addRegexpMatcher(tpl string, typ regexpType) error {
    method Headers (line 312) | func (r *Route) Headers(pairs ...string) *Route {
    method HeadersRegexp (line 338) | func (r *Route) HeadersRegexp(pairs ...string) *Route {
    method Host (line 366) | func (r *Route) Host(tpl string) *Route {
    method MatcherFunc (line 382) | func (r *Route) MatcherFunc(f MatcherFunc) *Route {
    method Methods (line 398) | func (r *Route) Methods(methods ...string) *Route {
    method Path (line 426) | func (r *Route) Path(tpl string) *Route {
    method PathPrefix (line 442) | func (r *Route) PathPrefix(tpl string) *Route {
    method Queries (line 466) | func (r *Route) Queries(pairs ...string) *Route {
    method Schemes (line 512) | func (r *Route) Schemes(schemes ...string) *Route {
    method BuildVarsFunc (line 530) | func (r *Route) BuildVarsFunc(f BuildVarsFunc) *Route {
    method Subrouter (line 557) | func (r *Route) Subrouter() *Router {
    method URL (line 606) | func (r *Route) URL(pairs ...string) (*url.URL, error) {
    method URLHost (line 648) | func (r *Route) URLHost(pairs ...string) (*url.URL, error) {
    method URLPath (line 676) | func (r *Route) URLPath(pairs ...string) (*url.URL, error) {
    method GetPathTemplate (line 701) | func (r *Route) GetPathTemplate() (string, error) {
    method GetPathRegexp (line 715) | func (r *Route) GetPathRegexp() (string, error) {
    method GetQueriesRegexp (line 730) | func (r *Route) GetQueriesRegexp() ([]string, error) {
    method GetQueriesTemplates (line 749) | func (r *Route) GetQueriesTemplates() ([]string, error) {
    method GetMethods (line 767) | func (r *Route) GetMethods() ([]string, error) {
    method GetHostTemplate (line 784) | func (r *Route) GetHostTemplate() (string, error) {
    method GetVarNames (line 796) | func (r *Route) GetVarNames() ([]string, error) {
    method prepareVars (line 815) | func (r *Route) prepareVars(pairs ...string) (map[string]string, error) {
    method buildVars (line 823) | func (r *Route) buildVars(m map[string]string) map[string]string {
  type matcher (line 234) | type matcher interface
  type headerMatcher (line 297) | type headerMatcher
    method Match (line 299) | func (m headerMatcher) Match(r *http.Request, match *RouteMatch) bool {
  type headerRegexMatcher (line 322) | type headerRegexMatcher
    method Match (line 324) | func (m headerRegexMatcher) Match(r *http.Request, match *RouteMatch) ...
  type MatcherFunc (line 374) | type MatcherFunc
    method Match (line 377) | func (m MatcherFunc) Match(r *http.Request, match *RouteMatch) bool {
  type methodMatcher (line 389) | type methodMatcher
    method Match (line 391) | func (m methodMatcher) Match(r *http.Request, match *RouteMatch) bool {
  type schemeMatcher (line 485) | type schemeMatcher
    method Match (line 487) | func (m schemeMatcher) Match(r *http.Request, match *RouteMatch) bool {
  type BuildVarsFunc (line 526) | type BuildVarsFunc

FILE: route_test.go
  function BenchmarkNewRouter (line 15) | func BenchmarkNewRouter(b *testing.B) {
  function BenchmarkNewRouterRegexpFunc (line 30) | func BenchmarkNewRouterRegexpFunc(b *testing.B) {
  function testNewRouter (line 62) | func testNewRouter(_ testing.TB, handler http.Handler) {
  function TestRouteMetadata (line 70) | func TestRouteMetadata(t *testing.T) {

FILE: test_helpers.go
  function SetURLVars (line 17) | func SetURLVars(r *http.Request, val map[string]string) *http.Request {
Condensed preview — 27 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (284K chars).
[
  {
    "path": ".editorconfig",
    "chars": 324,
    "preview": "; https://editorconfig.org/\n\nroot = true\n\n[*]\ninsert_final_newline = true\ncharset = utf-8\ntrim_trailing_whitespace = tru"
  },
  {
    "path": ".github/workflows/issues.yml",
    "chars": 477,
    "preview": "# Add all the issues created to the project.\nname: Add issue or pull request to Project\n\non:\n  issues:\n    types:\n      "
  },
  {
    "path": ".github/workflows/security.yml",
    "chars": 750,
    "preview": "name: Security\non:\n  push:\n    branches:\n      - main\n  pull_request:\n    branches:\n      - main\npermissions:\n  contents"
  },
  {
    "path": ".github/workflows/test.yml",
    "chars": 760,
    "preview": "name: Test\non:\n  push:\n    branches:\n      - main\n  pull_request:\n    branches:\n      - main\npermissions:\n  contents: re"
  },
  {
    "path": ".github/workflows/verify.yml",
    "chars": 616,
    "preview": "name: Verify\non:\n  push:\n    branches:\n      - main\n  pull_request:\n    branches:\n      - main\npermissions:\n  contents: "
  },
  {
    "path": ".gitignore",
    "chars": 22,
    "preview": "coverage.coverprofile\n"
  },
  {
    "path": "LICENSE",
    "chars": 1481,
    "preview": "Copyright (c) 2023 The Gorilla Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or"
  },
  {
    "path": "Makefile",
    "chars": 950,
    "preview": "GO_LINT=$(shell which golangci-lint 2> /dev/null || echo '')\nGO_LINT_URI=github.com/golangci/golangci-lint/cmd/golangci-"
  },
  {
    "path": "README.md",
    "chars": 25670,
    "preview": "# gorilla/mux\n\n![testing](https://github.com/gorilla/mux/actions/workflows/test.yml/badge.svg)\n[![codecov](https://codec"
  },
  {
    "path": "bench_test.go",
    "chars": 2119,
    "preview": "// Copyright 2012 The Gorilla Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// lic"
  },
  {
    "path": "doc.go",
    "chars": 11238,
    "preview": "// Copyright 2012 The Gorilla Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// lic"
  },
  {
    "path": "example_authentication_middleware_test.go",
    "chars": 1154,
    "preview": "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 authenticationMidd"
  },
  {
    "path": "example_cors_method_middleware_test.go",
    "chars": 1179,
    "preview": "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 ExampleCORSMethodM"
  },
  {
    "path": "example_route_test.go",
    "chars": 1752,
    "preview": "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 regul"
  },
  {
    "path": "example_route_vars_test.go",
    "chars": 744,
    "preview": "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"
  },
  {
    "path": "go.mod",
    "chars": 39,
    "preview": "module github.com/gorilla/mux\n\ngo 1.20\n"
  },
  {
    "path": "middleware.go",
    "chars": 3507,
    "preview": "package mux\n\nimport (\n\t\"net/http\"\n\t\"strings\"\n)\n\n// MiddlewareFunc is a function which receives an http.Handler and retur"
  },
  {
    "path": "middleware_test.go",
    "chars": 19923,
    "preview": "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 ("
  },
  {
    "path": "mux.go",
    "chars": 19917,
    "preview": "// Copyright 2012 The Gorilla Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// lic"
  },
  {
    "path": "mux_httpserver_test.go",
    "chars": 1460,
    "preview": "//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\nfu"
  },
  {
    "path": "mux_test.go",
    "chars": 100196,
    "preview": "// Copyright 2012 The Gorilla Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// lic"
  },
  {
    "path": "old_test.go",
    "chars": 18009,
    "preview": "// Old tests ported to Go1. This is a mess. Want to drop it one day.\n\n// Copyright 2011 Gorilla Authors. All rights rese"
  },
  {
    "path": "regexp.go",
    "chars": 10933,
    "preview": "// Copyright 2012 The Gorilla Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// lic"
  },
  {
    "path": "regexp_test.go",
    "chars": 3164,
    "preview": "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 *testi"
  },
  {
    "path": "route.go",
    "chars": 24397,
    "preview": "// Copyright 2012 The Gorilla Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// lic"
  },
  {
    "path": "route_test.go",
    "chars": 3899,
    "preview": "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\nva"
  },
  {
    "path": "test_helpers.go",
    "chars": 766,
    "preview": "// Copyright 2012 The Gorilla Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// lic"
  }
]

About this extraction

This page contains the full source code of the gorilla/mux GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 27 files (249.5 KB), approximately 71.1k tokens, and a symbol index with 267 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!