{"id":13430912,"url":"https://github.com/gorilla/mux","last_synced_at":"2025-09-09T20:45:21.998Z","repository":{"id":4896181,"uuid":"6051812","full_name":"gorilla/mux","owner":"gorilla","description":"Package gorilla/mux is a powerful HTTP router and URL matcher for building Go web servers with 🦍","archived":false,"fork":false,"pushed_at":"2024-08-15T03:10:55.000Z","size":543,"stargazers_count":21560,"open_issues_count":33,"forks_count":1874,"subscribers_count":308,"default_branch":"main","last_synced_at":"2025-09-05T05:01:59.590Z","etag":null,"topics":["go","golang","gorilla","gorilla-web-toolkit","http","middleware","mux","router"],"latest_commit_sha":null,"homepage":"https://gorilla.github.io","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":"kennethreitz/envoy","license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gorilla.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2012-10-02T21:32:24.000Z","updated_at":"2025-09-04T23:54:36.000Z","dependencies_parsed_at":"2023-11-13T05:28:53.364Z","dependency_job_id":"8344c3ab-2047-4c9c-8007-69fff69af020","html_url":"https://github.com/gorilla/mux","commit_stats":{"total_commits":272,"total_committers":120,"mean_commits":"2.2666666666666666","dds":0.8676470588235294,"last_synced_commit":"db9d1d0073d27a0a2d9a8c1bc52aa0af4374d265"},"previous_names":[],"tags_count":15,"template":false,"template_full_name":null,"purl":"pkg:github/gorilla/mux","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gorilla%2Fmux","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gorilla%2Fmux/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gorilla%2Fmux/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gorilla%2Fmux/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gorilla","download_url":"https://codeload.github.com/gorilla/mux/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gorilla%2Fmux/sbom","scorecard":{"id":441607,"data":{"date":"2025-08-11","repo":{"name":"github.com/gorilla/mux","commit":"db9d1d0073d27a0a2d9a8c1bc52aa0af4374d265"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":5.4,"checks":[{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Code-Review","score":9,"reason":"Found 21/23 approved changesets -- score normalized to 9","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/issues.yml:18: update your workflow using https://app.stepsecurity.io/secureworkflow/gorilla/mux/issues.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/security.yml:20: update your workflow using https://app.stepsecurity.io/secureworkflow/gorilla/mux/security.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/security.yml:23: update your workflow using https://app.stepsecurity.io/secureworkflow/gorilla/mux/security.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/security.yml:29: update your workflow using https://app.stepsecurity.io/secureworkflow/gorilla/mux/security.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/security.yml:34: update your workflow using https://app.stepsecurity.io/secureworkflow/gorilla/mux/security.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yml:21: update your workflow using https://app.stepsecurity.io/secureworkflow/gorilla/mux/test.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yml:24: update your workflow using https://app.stepsecurity.io/secureworkflow/gorilla/mux/test.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/test.yml:33: update your workflow using https://app.stepsecurity.io/secureworkflow/gorilla/mux/test.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/verify.yml:20: update your workflow using https://app.stepsecurity.io/secureworkflow/gorilla/mux/verify.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/verify.yml:23: update your workflow using https://app.stepsecurity.io/secureworkflow/gorilla/mux/verify.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/verify.yml:29: update your workflow using https://app.stepsecurity.io/secureworkflow/gorilla/mux/verify.yml/main?enable=pin","Info:   0 out of   7 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   4 third-party GitHubAction dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/issues.yml:1","Info: topLevel 'contents' permission set to 'read': .github/workflows/security.yml:10","Info: topLevel 'contents' permission set to 'read': .github/workflows/test.yml:10","Info: topLevel 'contents' permission set to 'read': .github/workflows/verify.yml:10","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: BSD 3-Clause \"New\" or \"Revised\" License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Security-Policy","score":10,"reason":"security policy file detected","details":["Info: security policy file detected: github.com/gorilla/.github/SECURITY.md:1","Info: Found linked content: github.com/gorilla/.github/SECURITY.md:1","Info: Found disclosure, vulnerability, and/or timelines in security policy: github.com/gorilla/.github/SECURITY.md:1","Info: Found text in security policy: github.com/gorilla/.github/SECURITY.md:1"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 30 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-19T05:42:39.505Z","repository_id":4896181,"created_at":"2025-08-19T05:42:39.505Z","updated_at":"2025-08-19T05:42:39.505Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273740850,"owners_count":25159434,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-05T02:00:09.113Z","response_time":402,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["go","golang","gorilla","gorilla-web-toolkit","http","middleware","mux","router"],"created_at":"2024-07-31T02:00:58.917Z","updated_at":"2025-09-09T20:45:21.968Z","avatar_url":"https://github.com/gorilla.png","language":"Go","readme":"# gorilla/mux\n\n![testing](https://github.com/gorilla/mux/actions/workflows/test.yml/badge.svg)\n[![codecov](https://codecov.io/github/gorilla/mux/branch/main/graph/badge.svg)](https://codecov.io/github/gorilla/mux)\n[![godoc](https://godoc.org/github.com/gorilla/mux?status.svg)](https://godoc.org/github.com/gorilla/mux)\n[![sourcegraph](https://sourcegraph.com/github.com/gorilla/mux/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/mux?badge)\n\n\n![Gorilla Logo](https://github.com/gorilla/.github/assets/53367916/d92caabf-98e0-473e-bfbf-ab554ba435e5)\n\nPackage `gorilla/mux` implements a request router and dispatcher for matching incoming requests to\ntheir respective handler.\n\nThe name mux stands for \"HTTP request multiplexer\". Like the standard `http.ServeMux`, `mux.Router` matches incoming requests against a list of registered routes and calls a handler for the route that matches the URL or other conditions. The main features are:\n\n* It implements the `http.Handler` interface so it is compatible with the standard `http.ServeMux`.\n* Requests can be matched based on URL host, path, path prefix, schemes, header and query values, HTTP methods or using custom matchers.\n* URL hosts, paths and query values can have variables with an optional regular expression.\n* Registered URLs can be built, or \"reversed\", which helps maintaining references to resources.\n* Routes can be used as subrouters: nested routes are only tested if the parent route matches. This is useful to define groups of routes that share common conditions like a host, a path prefix or other repeated attributes. As a bonus, this optimizes request matching.\n\n---\n\n* [Install](#install)\n* [Examples](#examples)\n* [Matching Routes](#matching-routes)\n* [Static Files](#static-files)\n* [Serving Single Page Applications](#serving-single-page-applications) (e.g. React, Vue, Ember.js, etc.)\n* [Registered URLs](#registered-urls)\n* [Walking Routes](#walking-routes)\n* [Graceful Shutdown](#graceful-shutdown)\n* [Middleware](#middleware)\n* [Handling CORS Requests](#handling-cors-requests)\n* [Testing Handlers](#testing-handlers)\n* [Full Example](#full-example)\n\n---\n\n## Install\n\nWith a [correctly configured](https://golang.org/doc/install#testing) Go toolchain:\n\n```sh\ngo get -u github.com/gorilla/mux\n```\n\n## Examples\n\nLet's start registering a couple of URL paths and handlers:\n\n```go\nfunc main() {\n    r := mux.NewRouter()\n    r.HandleFunc(\"/\", HomeHandler)\n    r.HandleFunc(\"/products\", ProductsHandler)\n    r.HandleFunc(\"/articles\", ArticlesHandler)\n    http.Handle(\"/\", r)\n}\n```\n\nHere we register three routes mapping URL paths to handlers. This is equivalent to how `http.HandleFunc()` works: if an incoming request URL matches one of the paths, the corresponding handler is called passing (`http.ResponseWriter`, `*http.Request`) as parameters.\n\nPaths can have variables. They are defined using the format `{name}` or `{name:pattern}`. If a regular expression pattern is not defined, the matched variable will be anything until the next slash. For example:\n\n```go\nr := mux.NewRouter()\nr.HandleFunc(\"/products/{key}\", ProductHandler)\nr.HandleFunc(\"/articles/{category}/\", ArticlesCategoryHandler)\nr.HandleFunc(\"/articles/{category}/{id:[0-9]+}\", ArticleHandler)\n```\n\nThe names are used to create a map of route variables which can be retrieved calling `mux.Vars()`:\n\n```go\nfunc ArticlesCategoryHandler(w http.ResponseWriter, r *http.Request) {\n    vars := mux.Vars(r)\n    w.WriteHeader(http.StatusOK)\n    fmt.Fprintf(w, \"Category: %v\\n\", vars[\"category\"])\n}\n```\n\nAnd this is all you need to know about the basic usage. More advanced options are explained below.\n\n### Matching Routes\n\nRoutes can also be restricted to a domain or subdomain. Just define a host pattern to be matched. They can also have variables:\n\n```go\nr := mux.NewRouter()\n// Only matches if domain is \"www.example.com\".\nr.Host(\"www.example.com\")\n// Matches a dynamic subdomain.\nr.Host(\"{subdomain:[a-z]+}.example.com\")\n```\n\nThere are several other matchers that can be added. To match path prefixes:\n\n```go\nr.PathPrefix(\"/products/\")\n```\n\n...or HTTP methods:\n\n```go\nr.Methods(\"GET\", \"POST\")\n```\n\n...or URL schemes:\n\n```go\nr.Schemes(\"https\")\n```\n\n...or header values:\n\n```go\nr.Headers(\"X-Requested-With\", \"XMLHttpRequest\")\n```\n\n...or query values:\n\n```go\nr.Queries(\"key\", \"value\")\n```\n\n...or to use a custom matcher function:\n\n```go\nr.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool {\n    return r.ProtoMajor == 0\n})\n```\n\n...and finally, it is possible to combine several matchers in a single route:\n\n```go\nr.HandleFunc(\"/products\", ProductsHandler).\n  Host(\"www.example.com\").\n  Methods(\"GET\").\n  Schemes(\"http\")\n```\n\nRoutes are tested in the order they were added to the router. If two routes match, the first one wins:\n\n```go\nr := mux.NewRouter()\nr.HandleFunc(\"/specific\", specificHandler)\nr.PathPrefix(\"/\").Handler(catchAllHandler)\n```\n\nSetting the same matching conditions again and again can be boring, so we have a way to group several routes that share the same requirements. We call it \"subrouting\".\n\nFor example, let's say we have several URLs that should only match when the host is `www.example.com`. Create a route for that host and get a \"subrouter\" from it:\n\n```go\nr := mux.NewRouter()\ns := r.Host(\"www.example.com\").Subrouter()\n```\n\nThen register routes in the subrouter:\n\n```go\ns.HandleFunc(\"/products/\", ProductsHandler)\ns.HandleFunc(\"/products/{key}\", ProductHandler)\ns.HandleFunc(\"/articles/{category}/{id:[0-9]+}\", ArticleHandler)\n```\n\nThe three URL paths we registered above will only be tested if the domain is `www.example.com`, because the subrouter is tested first. This is not only convenient, but also optimizes request matching. You can create subrouters combining any attribute matchers accepted by a route.\n\nSubrouters can be used to create domain or path \"namespaces\": you define subrouters in a central place and then parts of the app can register its paths relatively to a given subrouter.\n\nThere's one more thing about subroutes. When a subrouter has a path prefix, the inner routes use it as base for their paths:\n\n```go\nr := mux.NewRouter()\ns := r.PathPrefix(\"/products\").Subrouter()\n// \"/products/\"\ns.HandleFunc(\"/\", ProductsHandler)\n// \"/products/{key}/\"\ns.HandleFunc(\"/{key}/\", ProductHandler)\n// \"/products/{key}/details\"\ns.HandleFunc(\"/{key}/details\", ProductDetailsHandler)\n```\n\n\n### Static Files\n\nNote that the path provided to `PathPrefix()` represents a \"wildcard\": calling\n`PathPrefix(\"/static/\").Handler(...)` means that the handler will be passed any\nrequest that matches \"/static/\\*\". This makes it easy to serve static files with mux:\n\n```go\nfunc main() {\n    var dir string\n\n    flag.StringVar(\u0026dir, \"dir\", \".\", \"the directory to serve files from. Defaults to the current dir\")\n    flag.Parse()\n    r := mux.NewRouter()\n\n    // This will serve files under http://localhost:8000/static/\u003cfilename\u003e\n    r.PathPrefix(\"/static/\").Handler(http.StripPrefix(\"/static/\", http.FileServer(http.Dir(dir))))\n\n    srv := \u0026http.Server{\n        Handler:      r,\n        Addr:         \"127.0.0.1:8000\",\n        // Good practice: enforce timeouts for servers you create!\n        WriteTimeout: 15 * time.Second,\n        ReadTimeout:  15 * time.Second,\n    }\n\n    log.Fatal(srv.ListenAndServe())\n}\n```\n\n### Serving Single Page Applications\n\nMost of the time it makes sense to serve your SPA on a separate web server from your API,\nbut sometimes it's desirable to serve them both from one place. It's possible to write a simple\nhandler for serving your SPA (for use with React Router's [BrowserRouter](https://reacttraining.com/react-router/web/api/BrowserRouter) for example), and leverage\nmux's powerful routing for your API endpoints.\n\n```go\npackage main\n\nimport (\n\t\"encoding/json\"\n\t\"log\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"time\"\n\n\t\"github.com/gorilla/mux\"\n)\n\n// spaHandler implements the http.Handler interface, so we can use it\n// to respond to HTTP requests. The path to the static directory and\n// path to the index file within that static directory are used to\n// serve the SPA in the given static directory.\ntype spaHandler struct {\n\tstaticPath string\n\tindexPath  string\n}\n\n// ServeHTTP inspects the URL path to locate a file within the static dir\n// on the SPA handler. If a file is found, it will be served. If not, the\n// file located at the index path on the SPA handler will be served. This\n// is suitable behavior for serving an SPA (single page application).\nfunc (h spaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n\t// Join internally call path.Clean to prevent directory traversal\n\tpath := filepath.Join(h.staticPath, r.URL.Path)\n\n\t// check whether a file exists or is a directory at the given path\n\tfi, err := os.Stat(path)\n\tif os.IsNotExist(err) || fi.IsDir() {\n\t\t// file does not exist or path is a directory, serve index.html\n\t\thttp.ServeFile(w, r, filepath.Join(h.staticPath, h.indexPath))\n\t\treturn\n\t}\n\n\tif err != nil {\n\t\t// if we got an error (that wasn't that the file doesn't exist) stating the\n\t\t// file, return a 500 internal server error and stop\n\t\thttp.Error(w, err.Error(), http.StatusInternalServerError)\n        return\n\t}\n\n\t// otherwise, use http.FileServer to serve the static file\n\thttp.FileServer(http.Dir(h.staticPath)).ServeHTTP(w, r)\n}\n\nfunc main() {\n\trouter := mux.NewRouter()\n\n\trouter.HandleFunc(\"/api/health\", func(w http.ResponseWriter, r *http.Request) {\n\t\t// an example API handler\n\t\tjson.NewEncoder(w).Encode(map[string]bool{\"ok\": true})\n\t})\n\n\tspa := spaHandler{staticPath: \"build\", indexPath: \"index.html\"}\n\trouter.PathPrefix(\"/\").Handler(spa)\n\n\tsrv := \u0026http.Server{\n\t\tHandler: router,\n\t\tAddr:    \"127.0.0.1:8000\",\n\t\t// Good practice: enforce timeouts for servers you create!\n\t\tWriteTimeout: 15 * time.Second,\n\t\tReadTimeout:  15 * time.Second,\n\t}\n\n\tlog.Fatal(srv.ListenAndServe())\n}\n```\n\n### Registered URLs\n\nNow let's see how to build registered URLs.\n\nRoutes can be named. All routes that define a name can have their URLs built, or \"reversed\". We define a name calling `Name()` on a route. For example:\n\n```go\nr := mux.NewRouter()\nr.HandleFunc(\"/articles/{category}/{id:[0-9]+}\", ArticleHandler).\n  Name(\"article\")\n```\n\nTo build a URL, get the route and call the `URL()` method, passing a sequence of key/value pairs for the route variables. For the previous route, we would do:\n\n```go\nurl, err := r.Get(\"article\").URL(\"category\", \"technology\", \"id\", \"42\")\n```\n\n...and the result will be a `url.URL` with the following path:\n\n```\n\"/articles/technology/42\"\n```\n\nThis also works for host and query value variables:\n\n```go\nr := mux.NewRouter()\nr.Host(\"{subdomain}.example.com\").\n  Path(\"/articles/{category}/{id:[0-9]+}\").\n  Queries(\"filter\", \"{filter}\").\n  HandlerFunc(ArticleHandler).\n  Name(\"article\")\n\n// url.String() will be \"http://news.example.com/articles/technology/42?filter=gorilla\"\nurl, err := r.Get(\"article\").URL(\"subdomain\", \"news\",\n                                 \"category\", \"technology\",\n                                 \"id\", \"42\",\n                                 \"filter\", \"gorilla\")\n```\n\nAll variables defined in the route are required, and their values must conform to the corresponding patterns. These requirements guarantee that a generated URL will always match a registered route -- the only exception is for explicitly defined \"build-only\" routes which never match.\n\nRegex support also exists for matching Headers within a route. For example, we could do:\n\n```go\nr.HeadersRegexp(\"Content-Type\", \"application/(text|json)\")\n```\n\n...and the route will match both requests with a Content-Type of `application/json` as well as `application/text`\n\nThere's also a way to build only the URL host or path for a route: use the methods `URLHost()` or `URLPath()` instead. For the previous route, we would do:\n\n```go\n// \"http://news.example.com/\"\nhost, err := r.Get(\"article\").URLHost(\"subdomain\", \"news\")\n\n// \"/articles/technology/42\"\npath, err := r.Get(\"article\").URLPath(\"category\", \"technology\", \"id\", \"42\")\n```\n\nAnd if you use subrouters, host and path defined separately can be built as well:\n\n```go\nr := mux.NewRouter()\ns := r.Host(\"{subdomain}.example.com\").Subrouter()\ns.Path(\"/articles/{category}/{id:[0-9]+}\").\n  HandlerFunc(ArticleHandler).\n  Name(\"article\")\n\n// \"http://news.example.com/articles/technology/42\"\nurl, err := r.Get(\"article\").URL(\"subdomain\", \"news\",\n                                 \"category\", \"technology\",\n                                 \"id\", \"42\")\n```\n\nTo find all the required variables for a given route when calling `URL()`, the method `GetVarNames()` is available:\n```go\nr := mux.NewRouter()\nr.Host(\"{domain}\").\n    Path(\"/{group}/{item_id}\").\n    Queries(\"some_data1\", \"{some_data1}\").\n    Queries(\"some_data2\", \"{some_data2}\").\n    Name(\"article\")\n\n// Will print [domain group item_id some_data1 some_data2] \u003cnil\u003e\nfmt.Println(r.Get(\"article\").GetVarNames())\n\n```\n### Walking Routes\n\nThe `Walk` function on `mux.Router` can be used to visit all of the routes that are registered on a router. For example,\nthe following prints all of the registered routes:\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/gorilla/mux\"\n)\n\nfunc handler(w http.ResponseWriter, r *http.Request) {\n\treturn\n}\n\nfunc main() {\n\tr := mux.NewRouter()\n\tr.HandleFunc(\"/\", handler)\n\tr.HandleFunc(\"/products\", handler).Methods(\"POST\")\n\tr.HandleFunc(\"/articles\", handler).Methods(\"GET\")\n\tr.HandleFunc(\"/articles/{id}\", handler).Methods(\"GET\", \"PUT\")\n\tr.HandleFunc(\"/authors\", handler).Queries(\"surname\", \"{surname}\")\n\terr := r.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {\n\t\tpathTemplate, err := route.GetPathTemplate()\n\t\tif err == nil {\n\t\t\tfmt.Println(\"ROUTE:\", pathTemplate)\n\t\t}\n\t\tpathRegexp, err := route.GetPathRegexp()\n\t\tif err == nil {\n\t\t\tfmt.Println(\"Path regexp:\", pathRegexp)\n\t\t}\n\t\tqueriesTemplates, err := route.GetQueriesTemplates()\n\t\tif err == nil {\n\t\t\tfmt.Println(\"Queries templates:\", strings.Join(queriesTemplates, \",\"))\n\t\t}\n\t\tqueriesRegexps, err := route.GetQueriesRegexp()\n\t\tif err == nil {\n\t\t\tfmt.Println(\"Queries regexps:\", strings.Join(queriesRegexps, \",\"))\n\t\t}\n\t\tmethods, err := route.GetMethods()\n\t\tif err == nil {\n\t\t\tfmt.Println(\"Methods:\", strings.Join(methods, \",\"))\n\t\t}\n\t\tfmt.Println()\n\t\treturn nil\n\t})\n\n\tif err != nil {\n\t\tfmt.Println(err)\n\t}\n\n\thttp.Handle(\"/\", r)\n}\n```\n\n### Graceful Shutdown\n\nGo 1.8 introduced the ability to [gracefully shutdown](https://golang.org/doc/go1.8#http_shutdown) a `*http.Server`. Here's how to do that alongside `mux`:\n\n```go\npackage main\n\nimport (\n    \"context\"\n    \"flag\"\n    \"log\"\n    \"net/http\"\n    \"os\"\n    \"os/signal\"\n    \"time\"\n\n    \"github.com/gorilla/mux\"\n)\n\nfunc main() {\n    var wait time.Duration\n    flag.DurationVar(\u0026wait, \"graceful-timeout\", time.Second * 15, \"the duration for which the server gracefully wait for existing connections to finish - e.g. 15s or 1m\")\n    flag.Parse()\n\n    r := mux.NewRouter()\n    // Add your routes as needed\n\n    srv := \u0026http.Server{\n        Addr:         \"0.0.0.0:8080\",\n        // Good practice to set timeouts to avoid Slowloris attacks.\n        WriteTimeout: time.Second * 15,\n        ReadTimeout:  time.Second * 15,\n        IdleTimeout:  time.Second * 60,\n        Handler: r, // Pass our instance of gorilla/mux in.\n    }\n\n    // Run our server in a goroutine so that it doesn't block.\n    go func() {\n        if err := srv.ListenAndServe(); err != nil {\n            log.Println(err)\n        }\n    }()\n\n    c := make(chan os.Signal, 1)\n    // We'll accept graceful shutdowns when quit via SIGINT (Ctrl+C)\n    // SIGKILL, SIGQUIT or SIGTERM (Ctrl+/) will not be caught.\n    signal.Notify(c, os.Interrupt)\n\n    // Block until we receive our signal.\n    \u003c-c\n\n    // Create a deadline to wait for.\n    ctx, cancel := context.WithTimeout(context.Background(), wait)\n    defer cancel()\n    // Doesn't block if no connections, but will otherwise wait\n    // until the timeout deadline.\n    srv.Shutdown(ctx)\n    // Optionally, you could run srv.Shutdown in a goroutine and block on\n    // \u003c-ctx.Done() if your application should wait for other services\n    // to finalize based on context cancellation.\n    log.Println(\"shutting down\")\n    os.Exit(0)\n}\n```\n\n### Middleware\n\nMux supports the addition of middlewares to a [Router](https://godoc.org/github.com/gorilla/mux#Router), which are executed in the order they are added if a match is found, including its subrouters.\nMiddlewares are (typically) small pieces of code which take one request, do something with it, and pass it down to another middleware or the final handler. Some common use cases for middleware are request logging, header manipulation, or `ResponseWriter` hijacking.\n\nMux middlewares are defined using the de facto standard type:\n\n```go\ntype MiddlewareFunc func(http.Handler) http.Handler\n```\n\nTypically, the returned handler is a closure which does something with the http.ResponseWriter and http.Request passed to it, and then calls the handler passed as parameter to the MiddlewareFunc. This takes advantage of closures being able access variables from the context where they are created, while retaining the signature enforced by the receivers.\n\nA very basic middleware which logs the URI of the request being handled could be written as:\n\n```go\nfunc loggingMiddleware(next http.Handler) http.Handler {\n    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n        // Do stuff here\n        log.Println(r.RequestURI)\n        // Call the next handler, which can be another middleware in the chain, or the final handler.\n        next.ServeHTTP(w, r)\n    })\n}\n```\n\nMiddlewares can be added to a router using `Router.Use()`:\n\n```go\nr := mux.NewRouter()\nr.HandleFunc(\"/\", handler)\nr.Use(loggingMiddleware)\n```\n\nA more complex authentication middleware, which maps session token to users, could be written as:\n\n```go\n// Define our struct\ntype authenticationMiddleware struct {\n\ttokenUsers map[string]string\n}\n\n// Initialize it somewhere\nfunc (amw *authenticationMiddleware) Populate() {\n\tamw.tokenUsers[\"00000000\"] = \"user0\"\n\tamw.tokenUsers[\"aaaaaaaa\"] = \"userA\"\n\tamw.tokenUsers[\"05f717e5\"] = \"randomUser\"\n\tamw.tokenUsers[\"deadbeef\"] = \"user0\"\n}\n\n// Middleware function, which will be called for each request\nfunc (amw *authenticationMiddleware) Middleware(next http.Handler) http.Handler {\n    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n        token := r.Header.Get(\"X-Session-Token\")\n\n        if user, found := amw.tokenUsers[token]; found {\n        \t// We found the token in our map\n        \tlog.Printf(\"Authenticated user %s\\n\", user)\n        \t// Pass down the request to the next middleware (or final handler)\n        \tnext.ServeHTTP(w, r)\n        } else {\n        \t// Write an error and stop the handler chain\n        \thttp.Error(w, \"Forbidden\", http.StatusForbidden)\n        }\n    })\n}\n```\n\n```go\nr := mux.NewRouter()\nr.HandleFunc(\"/\", handler)\n\namw := authenticationMiddleware{tokenUsers: make(map[string]string)}\namw.Populate()\n\nr.Use(amw.Middleware)\n```\n\nNote: The handler chain will be stopped if your middleware doesn't call `next.ServeHTTP()` with the corresponding parameters. This can be used to abort a request if the middleware writer wants to. Middlewares _should_ write to `ResponseWriter` if they _are_ going to terminate the request, and they _should not_ write to `ResponseWriter` if they _are not_ going to terminate it.\n\n### Handling CORS Requests\n\n[CORSMethodMiddleware](https://godoc.org/github.com/gorilla/mux#CORSMethodMiddleware) intends to make it easier to strictly set the `Access-Control-Allow-Methods` response header.\n\n* You will still need to use your own CORS handler to set the other CORS headers such as `Access-Control-Allow-Origin`\n* The middleware will set the `Access-Control-Allow-Methods` header to all the method matchers (e.g. `r.Methods(http.MethodGet, http.MethodPut, http.MethodOptions)` -\u003e `Access-Control-Allow-Methods: GET,PUT,OPTIONS`) on a route\n* If you do not specify any methods, then:\n\u003e _Important_: there must be an `OPTIONS` method matcher for the middleware to set the headers.\n\nHere is an example of using `CORSMethodMiddleware` along with a custom `OPTIONS` handler to set all the required CORS headers:\n\n```go\npackage main\n\nimport (\n\t\"net/http\"\n\t\"github.com/gorilla/mux\"\n)\n\nfunc main() {\n    r := mux.NewRouter()\n\n    // IMPORTANT: you must specify an OPTIONS method matcher for the middleware to set CORS headers\n    r.HandleFunc(\"/foo\", fooHandler).Methods(http.MethodGet, http.MethodPut, http.MethodPatch, http.MethodOptions)\n    r.Use(mux.CORSMethodMiddleware(r))\n    \n    http.ListenAndServe(\":8080\", r)\n}\n\nfunc fooHandler(w http.ResponseWriter, r *http.Request) {\n    w.Header().Set(\"Access-Control-Allow-Origin\", \"*\")\n    if r.Method == http.MethodOptions {\n        return\n    }\n\n    w.Write([]byte(\"foo\"))\n}\n```\n\nAnd an request to `/foo` using something like:\n\n```bash\ncurl localhost:8080/foo -v\n```\n\nWould look like:\n\n```bash\n*   Trying ::1...\n* TCP_NODELAY set\n* Connected to localhost (::1) port 8080 (#0)\n\u003e GET /foo HTTP/1.1\n\u003e Host: localhost:8080\n\u003e User-Agent: curl/7.59.0\n\u003e Accept: */*\n\u003e \n\u003c HTTP/1.1 200 OK\n\u003c Access-Control-Allow-Methods: GET,PUT,PATCH,OPTIONS\n\u003c Access-Control-Allow-Origin: *\n\u003c Date: Fri, 28 Jun 2019 20:13:30 GMT\n\u003c Content-Length: 3\n\u003c Content-Type: text/plain; charset=utf-8\n\u003c \n* Connection #0 to host localhost left intact\nfoo\n```\n\n### Testing Handlers\n\nTesting handlers in a Go web application is straightforward, and _mux_ doesn't complicate this any further. Given two files: `endpoints.go` and `endpoints_test.go`, here's how we'd test an application using _mux_.\n\nFirst, our simple HTTP handler:\n\n```go\n// endpoints.go\npackage main\n\nfunc HealthCheckHandler(w http.ResponseWriter, r *http.Request) {\n    // A very simple health check.\n    w.Header().Set(\"Content-Type\", \"application/json\")\n    w.WriteHeader(http.StatusOK)\n\n    // In the future we could report back on the status of our DB, or our cache\n    // (e.g. Redis) by performing a simple PING, and include them in the response.\n    io.WriteString(w, `{\"alive\": true}`)\n}\n\nfunc main() {\n    r := mux.NewRouter()\n    r.HandleFunc(\"/health\", HealthCheckHandler)\n\n    log.Fatal(http.ListenAndServe(\"localhost:8080\", r))\n}\n```\n\nOur test code:\n\n```go\n// endpoints_test.go\npackage main\n\nimport (\n    \"net/http\"\n    \"net/http/httptest\"\n    \"testing\"\n)\n\nfunc TestHealthCheckHandler(t *testing.T) {\n    // Create a request to pass to our handler. We don't have any query parameters for now, so we'll\n    // pass 'nil' as the third parameter.\n    req, err := http.NewRequest(\"GET\", \"/health\", nil)\n    if err != nil {\n        t.Fatal(err)\n    }\n\n    // We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response.\n    rr := httptest.NewRecorder()\n    handler := http.HandlerFunc(HealthCheckHandler)\n\n    // Our handlers satisfy http.Handler, so we can call their ServeHTTP method\n    // directly and pass in our Request and ResponseRecorder.\n    handler.ServeHTTP(rr, req)\n\n    // Check the status code is what we expect.\n    if status := rr.Code; status != http.StatusOK {\n        t.Errorf(\"handler returned wrong status code: got %v want %v\",\n            status, http.StatusOK)\n    }\n\n    // Check the response body is what we expect.\n    expected := `{\"alive\": true}`\n    if rr.Body.String() != expected {\n        t.Errorf(\"handler returned unexpected body: got %v want %v\",\n            rr.Body.String(), expected)\n    }\n}\n```\n\nIn the case that our routes have [variables](#examples), we can pass those in the request. We could write\n[table-driven tests](https://dave.cheney.net/2013/06/09/writing-table-driven-tests-in-go) to test multiple\npossible route variables as needed.\n\n```go\n// endpoints.go\nfunc main() {\n    r := mux.NewRouter()\n    // A route with a route variable:\n    r.HandleFunc(\"/metrics/{type}\", MetricsHandler)\n\n    log.Fatal(http.ListenAndServe(\"localhost:8080\", r))\n}\n```\n\nOur test file, with a table-driven test of `routeVariables`:\n\n```go\n// endpoints_test.go\nfunc TestMetricsHandler(t *testing.T) {\n    tt := []struct{\n        routeVariable string\n        shouldPass bool\n    }{\n        {\"goroutines\", true},\n        {\"heap\", true},\n        {\"counters\", true},\n        {\"queries\", true},\n        {\"adhadaeqm3k\", false},\n    }\n\n    for _, tc := range tt {\n        path := fmt.Sprintf(\"/metrics/%s\", tc.routeVariable)\n        req, err := http.NewRequest(\"GET\", path, nil)\n        if err != nil {\n            t.Fatal(err)\n        }\n\n        rr := httptest.NewRecorder()\n\t\n\t// To add the vars to the context, \n\t// we need to create a router through which we can pass the request.\n\trouter := mux.NewRouter()\n        router.HandleFunc(\"/metrics/{type}\", MetricsHandler)\n        router.ServeHTTP(rr, req)\n\n        // In this case, our MetricsHandler returns a non-200 response\n        // for a route variable it doesn't know about.\n        if rr.Code == http.StatusOK \u0026\u0026 !tc.shouldPass {\n            t.Errorf(\"handler should have failed on routeVariable %s: got %v want %v\",\n                tc.routeVariable, rr.Code, http.StatusOK)\n        }\n    }\n}\n```\n\n## Full Example\n\nHere's a complete, runnable example of a small `mux` based server:\n\n```go\npackage main\n\nimport (\n    \"net/http\"\n    \"log\"\n    \"github.com/gorilla/mux\"\n)\n\nfunc YourHandler(w http.ResponseWriter, r *http.Request) {\n    w.Write([]byte(\"Gorilla!\\n\"))\n}\n\nfunc main() {\n    r := mux.NewRouter()\n    // Routes consist of a path and a handler function.\n    r.HandleFunc(\"/\", YourHandler)\n\n    // Bind to a port and pass our router in\n    log.Fatal(http.ListenAndServe(\":8000\", r))\n}\n```\n\n## License\n\nBSD licensed. See the LICENSE file for details.\n","funding_links":[],"categories":["Go","HarmonyOS","开源类库","Misc","Uncategorized","Web Frameworks","Routers","Backend Go","Multiplexers","Open source library","Networking","后端开发框架及项目","Web 框架","golang","Web框架","路由"],"sub_categories":["Windows Manager","路由","Uncategorized","Routers","HTTP Clients","Web Framework","Redirect","管理面板","路由库","Advanced Console UIs","路由器","Mock","创建http中间件的代码库"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgorilla%2Fmux","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgorilla%2Fmux","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgorilla%2Fmux/lists"}