blob: 38957deead3d73b4cbf24fca337bbf8a6dfa03cc [file] [log] [blame]
Jonathan Hartf86817b2018-08-17 10:35:54 -07001// 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/*
6Package mux implements a request router and dispatcher.
7
8The name mux stands for "HTTP request multiplexer". Like the standard
9http.ServeMux, mux.Router matches incoming requests against a list of
10registered routes and calls a handler for the route that matches the URL
11or 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.
Matteo Scandolo1f49bf52018-11-20 13:56:45 -080015 * URL hosts, paths and query values can have variables with an optional
16 regular expression.
Jonathan Hartf86817b2018-08-17 10:35:54 -070017 * 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
26Let'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
36Here we register three routes mapping URL paths to handlers. This is
37equivalent to how http.HandleFunc() works: if an incoming request URL matches
38one of the paths, the corresponding handler is called passing
39(http.ResponseWriter, *http.Request) as parameters.
40
41Paths can have variables. They are defined using the format {name} or
42{name:pattern}. If a regular expression pattern is not defined, the matched
43variable 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
50Groups 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
54The names are used to create a map of route variables which can be retrieved
55calling mux.Vars():
56
57 vars := mux.Vars(request)
58 category := vars["category"]
59
60Note that if any capturing groups are present, mux will panic() during parsing. To prevent
61this, 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
63when capturing groups were present.
64
65And this is all you need to know about the basic usage. More advanced options
66are explained below.
67
68Routes can also be restricted to a domain or subdomain. Just define a host
69pattern 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
77There 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
110Setting the same matching conditions again and again can be boring, so we have
111a way to group several routes that share the same requirements.
112We call it "subrouting".
113
114For example, let's say we have several URLs that should only match when the
115host is "www.example.com". Create a route for that host and get a "subrouter"
116from it:
117
118 r := mux.NewRouter()
119 s := r.Host("www.example.com").Subrouter()
120
121Then 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
127The 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
129only convenient, but also optimizes request matching. You can create
130subrouters combining any attribute matchers accepted by a route.
131
132Subrouters can be used to create domain or path "namespaces": you define
133subrouters in a central place and then parts of the app can register its
134paths relatively to a given subrouter.
135
136There's one more thing about subroutes. When a subrouter has a path prefix,
137the 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
148Note that the path provided to PathPrefix() represents a "wildcard": calling
149PathPrefix("/static/").Handler(...) means that the handler will be passed any
150request 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
173Now let's see how to build registered URLs.
174
175Routes can be named. All routes that define a name can have their URLs built,
176or "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
182To build a URL, get the route and call the URL() method, passing a sequence of
183key/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
Matteo Scandolo1f49bf52018-11-20 13:56:45 -0800191This also works for host and query value variables:
Jonathan Hartf86817b2018-08-17 10:35:54 -0700192
193 r := mux.NewRouter()
194 r.Host("{subdomain}.domain.com").
195 Path("/articles/{category}/{id:[0-9]+}").
Matteo Scandolo1f49bf52018-11-20 13:56:45 -0800196 Queries("filter", "{filter}").
Jonathan Hartf86817b2018-08-17 10:35:54 -0700197 HandlerFunc(ArticleHandler).
198 Name("article")
199
Matteo Scandolo1f49bf52018-11-20 13:56:45 -0800200 // url.String() will be "http://news.domain.com/articles/technology/42?filter=gorilla"
Jonathan Hartf86817b2018-08-17 10:35:54 -0700201 url, err := r.Get("article").URL("subdomain", "news",
202 "category", "technology",
Matteo Scandolo1f49bf52018-11-20 13:56:45 -0800203 "id", "42",
204 "filter", "gorilla")
Jonathan Hartf86817b2018-08-17 10:35:54 -0700205
206All variables defined in the route are required, and their values must
207conform to the corresponding patterns. These requirements guarantee that a
208generated URL will always match a registered route -- the only exception is
209for explicitly defined "build-only" routes which never match.
210
211Regex support also exists for matching Headers within a route. For example, we could do:
212
213 r.HeadersRegexp("Content-Type", "application/(text|json)")
214
215...and the route will match both requests with a Content-Type of `application/json` as well as
216`application/text`
217
218There's also a way to build only the URL host or path for a route:
219use the methods URLHost() or URLPath() instead. For the previous route,
220we would do:
221
222 // "http://news.domain.com/"
223 host, err := r.Get("article").URLHost("subdomain", "news")
224
225 // "/articles/technology/42"
226 path, err := r.Get("article").URLPath("category", "technology", "id", "42")
227
228And if you use subrouters, host and path defined separately can be built
229as well:
230
231 r := mux.NewRouter()
232 s := r.Host("{subdomain}.domain.com").Subrouter()
233 s.Path("/articles/{category}/{id:[0-9]+}").
234 HandlerFunc(ArticleHandler).
235 Name("article")
236
237 // "http://news.domain.com/articles/technology/42"
238 url, err := r.Get("article").URL("subdomain", "news",
239 "category", "technology",
240 "id", "42")
Matteo Scandolo1f49bf52018-11-20 13:56:45 -0800241
242Mux supports the addition of middlewares to a Router, which are executed in the order they are added if a match is found, including its subrouters. Middlewares are (typically) small pieces of code which take one request, do something with it, and pass it down to another middleware or the final handler. Some common use cases for middleware are request logging, header manipulation, or ResponseWriter hijacking.
243
244 type MiddlewareFunc func(http.Handler) http.Handler
245
246Typically, the returned handler is a closure which does something with the http.ResponseWriter and http.Request passed to it, and then calls the handler passed as parameter to the MiddlewareFunc (closures can access variables from the context where they are created).
247
248A very basic middleware which logs the URI of the request being handled could be written as:
249
250 func simpleMw(next http.Handler) http.Handler {
251 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
252 // Do stuff here
253 log.Println(r.RequestURI)
254 // Call the next handler, which can be another middleware in the chain, or the final handler.
255 next.ServeHTTP(w, r)
256 })
257 }
258
259Middlewares can be added to a router using `Router.Use()`:
260
261 r := mux.NewRouter()
262 r.HandleFunc("/", handler)
263 r.Use(simpleMw)
264
265A more complex authentication middleware, which maps session token to users, could be written as:
266
267 // Define our struct
268 type authenticationMiddleware struct {
269 tokenUsers map[string]string
270 }
271
272 // Initialize it somewhere
273 func (amw *authenticationMiddleware) Populate() {
274 amw.tokenUsers["00000000"] = "user0"
275 amw.tokenUsers["aaaaaaaa"] = "userA"
276 amw.tokenUsers["05f717e5"] = "randomUser"
277 amw.tokenUsers["deadbeef"] = "user0"
278 }
279
280 // Middleware function, which will be called for each request
281 func (amw *authenticationMiddleware) Middleware(next http.Handler) http.Handler {
282 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
283 token := r.Header.Get("X-Session-Token")
284
285 if user, found := amw.tokenUsers[token]; found {
286 // We found the token in our map
287 log.Printf("Authenticated user %s\n", user)
288 next.ServeHTTP(w, r)
289 } else {
290 http.Error(w, "Forbidden", http.StatusForbidden)
291 }
292 })
293 }
294
295 r := mux.NewRouter()
296 r.HandleFunc("/", handler)
297
298 amw := authenticationMiddleware{}
299 amw.Populate()
300
301 r.Use(amw.Middleware)
302
303Note: The handler chain will be stopped if your middleware doesn't call `next.ServeHTTP()` with the corresponding parameters. This can be used to abort a request if the middleware writer wants to.
304
Jonathan Hartf86817b2018-08-17 10:35:54 -0700305*/
306package mux