Jonathan Hart | f86817b | 2018-08-17 10:35:54 -0700 | [diff] [blame] | 1 | // Copyright 2012 The Gorilla Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style |
| 3 | // license that can be found in the LICENSE file. |
| 4 | |
| 5 | /* |
| 6 | Package mux implements a request router and dispatcher. |
| 7 | |
| 8 | The name mux stands for "HTTP request multiplexer". Like the standard |
| 9 | http.ServeMux, mux.Router matches incoming requests against a list of |
| 10 | registered routes and calls a handler for the route that matches the URL |
| 11 | or other conditions. The main features are: |
| 12 | |
| 13 | * Requests can be matched based on URL host, path, path prefix, schemes, |
| 14 | header and query values, HTTP methods or using custom matchers. |
| 15 | * URL hosts and paths can have variables with an optional regular |
| 16 | expression. |
| 17 | * Registered URLs can be built, or "reversed", which helps maintaining |
| 18 | references to resources. |
| 19 | * Routes can be used as subrouters: nested routes are only tested if the |
| 20 | parent route matches. This is useful to define groups of routes that |
| 21 | share common conditions like a host, a path prefix or other repeated |
| 22 | attributes. As a bonus, this optimizes request matching. |
| 23 | * It implements the http.Handler interface so it is compatible with the |
| 24 | standard http.ServeMux. |
| 25 | |
| 26 | Let's start registering a couple of URL paths and handlers: |
| 27 | |
| 28 | func main() { |
| 29 | r := mux.NewRouter() |
| 30 | r.HandleFunc("/", HomeHandler) |
| 31 | r.HandleFunc("/products", ProductsHandler) |
| 32 | r.HandleFunc("/articles", ArticlesHandler) |
| 33 | http.Handle("/", r) |
| 34 | } |
| 35 | |
| 36 | Here we register three routes mapping URL paths to handlers. This is |
| 37 | equivalent to how http.HandleFunc() works: if an incoming request URL matches |
| 38 | one of the paths, the corresponding handler is called passing |
| 39 | (http.ResponseWriter, *http.Request) as parameters. |
| 40 | |
| 41 | Paths can have variables. They are defined using the format {name} or |
| 42 | {name:pattern}. If a regular expression pattern is not defined, the matched |
| 43 | variable will be anything until the next slash. For example: |
| 44 | |
| 45 | r := mux.NewRouter() |
| 46 | r.HandleFunc("/products/{key}", ProductHandler) |
| 47 | r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler) |
| 48 | r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler) |
| 49 | |
| 50 | Groups can be used inside patterns, as long as they are non-capturing (?:re). For example: |
| 51 | |
| 52 | r.HandleFunc("/articles/{category}/{sort:(?:asc|desc|new)}", ArticlesCategoryHandler) |
| 53 | |
| 54 | The names are used to create a map of route variables which can be retrieved |
| 55 | calling mux.Vars(): |
| 56 | |
| 57 | vars := mux.Vars(request) |
| 58 | category := vars["category"] |
| 59 | |
| 60 | Note that if any capturing groups are present, mux will panic() during parsing. To prevent |
| 61 | this, convert any capturing groups to non-capturing, e.g. change "/{sort:(asc|desc)}" to |
| 62 | "/{sort:(?:asc|desc)}". This is a change from prior versions which behaved unpredictably |
| 63 | when capturing groups were present. |
| 64 | |
| 65 | And this is all you need to know about the basic usage. More advanced options |
| 66 | are explained below. |
| 67 | |
| 68 | Routes can also be restricted to a domain or subdomain. Just define a host |
| 69 | pattern to be matched. They can also have variables: |
| 70 | |
| 71 | r := mux.NewRouter() |
| 72 | // Only matches if domain is "www.example.com". |
| 73 | r.Host("www.example.com") |
| 74 | // Matches a dynamic subdomain. |
| 75 | r.Host("{subdomain:[a-z]+}.domain.com") |
| 76 | |
| 77 | There are several other matchers that can be added. To match path prefixes: |
| 78 | |
| 79 | r.PathPrefix("/products/") |
| 80 | |
| 81 | ...or HTTP methods: |
| 82 | |
| 83 | r.Methods("GET", "POST") |
| 84 | |
| 85 | ...or URL schemes: |
| 86 | |
| 87 | r.Schemes("https") |
| 88 | |
| 89 | ...or header values: |
| 90 | |
| 91 | r.Headers("X-Requested-With", "XMLHttpRequest") |
| 92 | |
| 93 | ...or query values: |
| 94 | |
| 95 | r.Queries("key", "value") |
| 96 | |
| 97 | ...or to use a custom matcher function: |
| 98 | |
| 99 | r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool { |
| 100 | return r.ProtoMajor == 0 |
| 101 | }) |
| 102 | |
| 103 | ...and finally, it is possible to combine several matchers in a single route: |
| 104 | |
| 105 | r.HandleFunc("/products", ProductsHandler). |
| 106 | Host("www.example.com"). |
| 107 | Methods("GET"). |
| 108 | Schemes("http") |
| 109 | |
| 110 | Setting the same matching conditions again and again can be boring, so we have |
| 111 | a way to group several routes that share the same requirements. |
| 112 | We call it "subrouting". |
| 113 | |
| 114 | For example, let's say we have several URLs that should only match when the |
| 115 | host is "www.example.com". Create a route for that host and get a "subrouter" |
| 116 | from it: |
| 117 | |
| 118 | r := mux.NewRouter() |
| 119 | s := r.Host("www.example.com").Subrouter() |
| 120 | |
| 121 | Then register routes in the subrouter: |
| 122 | |
| 123 | s.HandleFunc("/products/", ProductsHandler) |
| 124 | s.HandleFunc("/products/{key}", ProductHandler) |
| 125 | s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler) |
| 126 | |
| 127 | The three URL paths we registered above will only be tested if the domain is |
| 128 | "www.example.com", because the subrouter is tested first. This is not |
| 129 | only convenient, but also optimizes request matching. You can create |
| 130 | subrouters combining any attribute matchers accepted by a route. |
| 131 | |
| 132 | Subrouters can be used to create domain or path "namespaces": you define |
| 133 | subrouters in a central place and then parts of the app can register its |
| 134 | paths relatively to a given subrouter. |
| 135 | |
| 136 | There's one more thing about subroutes. When a subrouter has a path prefix, |
| 137 | the inner routes use it as base for their paths: |
| 138 | |
| 139 | r := mux.NewRouter() |
| 140 | s := r.PathPrefix("/products").Subrouter() |
| 141 | // "/products/" |
| 142 | s.HandleFunc("/", ProductsHandler) |
| 143 | // "/products/{key}/" |
| 144 | s.HandleFunc("/{key}/", ProductHandler) |
| 145 | // "/products/{key}/details" |
| 146 | s.HandleFunc("/{key}/details", ProductDetailsHandler) |
| 147 | |
| 148 | Note that the path provided to PathPrefix() represents a "wildcard": calling |
| 149 | PathPrefix("/static/").Handler(...) means that the handler will be passed any |
| 150 | request that matches "/static/*". This makes it easy to serve static files with mux: |
| 151 | |
| 152 | func main() { |
| 153 | var dir string |
| 154 | |
| 155 | flag.StringVar(&dir, "dir", ".", "the directory to serve files from. Defaults to the current dir") |
| 156 | flag.Parse() |
| 157 | r := mux.NewRouter() |
| 158 | |
| 159 | // This will serve files under http://localhost:8000/static/<filename> |
| 160 | r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(dir)))) |
| 161 | |
| 162 | srv := &http.Server{ |
| 163 | Handler: r, |
| 164 | Addr: "127.0.0.1:8000", |
| 165 | // Good practice: enforce timeouts for servers you create! |
| 166 | WriteTimeout: 15 * time.Second, |
| 167 | ReadTimeout: 15 * time.Second, |
| 168 | } |
| 169 | |
| 170 | log.Fatal(srv.ListenAndServe()) |
| 171 | } |
| 172 | |
| 173 | Now let's see how to build registered URLs. |
| 174 | |
| 175 | Routes can be named. All routes that define a name can have their URLs built, |
| 176 | or "reversed". We define a name calling Name() on a route. For example: |
| 177 | |
| 178 | r := mux.NewRouter() |
| 179 | r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler). |
| 180 | Name("article") |
| 181 | |
| 182 | To build a URL, get the route and call the URL() method, passing a sequence of |
| 183 | key/value pairs for the route variables. For the previous route, we would do: |
| 184 | |
| 185 | url, err := r.Get("article").URL("category", "technology", "id", "42") |
| 186 | |
| 187 | ...and the result will be a url.URL with the following path: |
| 188 | |
| 189 | "/articles/technology/42" |
| 190 | |
| 191 | This also works for host variables: |
| 192 | |
| 193 | r := mux.NewRouter() |
| 194 | r.Host("{subdomain}.domain.com"). |
| 195 | Path("/articles/{category}/{id:[0-9]+}"). |
| 196 | HandlerFunc(ArticleHandler). |
| 197 | Name("article") |
| 198 | |
| 199 | // url.String() will be "http://news.domain.com/articles/technology/42" |
| 200 | url, err := r.Get("article").URL("subdomain", "news", |
| 201 | "category", "technology", |
| 202 | "id", "42") |
| 203 | |
| 204 | All variables defined in the route are required, and their values must |
| 205 | conform to the corresponding patterns. These requirements guarantee that a |
| 206 | generated URL will always match a registered route -- the only exception is |
| 207 | for explicitly defined "build-only" routes which never match. |
| 208 | |
| 209 | Regex support also exists for matching Headers within a route. For example, we could do: |
| 210 | |
| 211 | r.HeadersRegexp("Content-Type", "application/(text|json)") |
| 212 | |
| 213 | ...and the route will match both requests with a Content-Type of `application/json` as well as |
| 214 | `application/text` |
| 215 | |
| 216 | There's also a way to build only the URL host or path for a route: |
| 217 | use the methods URLHost() or URLPath() instead. For the previous route, |
| 218 | we would do: |
| 219 | |
| 220 | // "http://news.domain.com/" |
| 221 | host, err := r.Get("article").URLHost("subdomain", "news") |
| 222 | |
| 223 | // "/articles/technology/42" |
| 224 | path, err := r.Get("article").URLPath("category", "technology", "id", "42") |
| 225 | |
| 226 | And if you use subrouters, host and path defined separately can be built |
| 227 | as well: |
| 228 | |
| 229 | r := mux.NewRouter() |
| 230 | s := r.Host("{subdomain}.domain.com").Subrouter() |
| 231 | s.Path("/articles/{category}/{id:[0-9]+}"). |
| 232 | HandlerFunc(ArticleHandler). |
| 233 | Name("article") |
| 234 | |
| 235 | // "http://news.domain.com/articles/technology/42" |
| 236 | url, err := r.Get("article").URL("subdomain", "news", |
| 237 | "category", "technology", |
| 238 | "id", "42") |
| 239 | */ |
| 240 | package mux |