blob: fb69196dbd2094009ad0b9d5de99f9e813b29960 [file] [log] [blame]
David K. Bainbridge215e0242017-09-05 23:18:24 -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
5package mux
6
7import (
8 "errors"
9 "fmt"
10 "net/http"
11 "path"
12 "regexp"
13 "strings"
14)
15
16var (
17 ErrMethodMismatch = errors.New("method is not allowed")
18)
19
20// NewRouter returns a new router instance.
21func NewRouter() *Router {
22 return &Router{namedRoutes: make(map[string]*Route), KeepContext: false}
23}
24
25// Router registers routes to be matched and dispatches a handler.
26//
27// It implements the http.Handler interface, so it can be registered to serve
28// requests:
29//
30// var router = mux.NewRouter()
31//
32// func main() {
33// http.Handle("/", router)
34// }
35//
36// Or, for Google App Engine, register it in a init() function:
37//
38// func init() {
39// http.Handle("/", router)
40// }
41//
42// This will send all incoming requests to the router.
43type Router struct {
44 // Configurable Handler to be used when no route matches.
45 NotFoundHandler http.Handler
46
47 // Configurable Handler to be used when the request method does not match the route.
48 MethodNotAllowedHandler http.Handler
49
50 // Parent route, if this is a subrouter.
51 parent parentRoute
52 // Routes to be matched, in order.
53 routes []*Route
54 // Routes by name for URL building.
55 namedRoutes map[string]*Route
56 // See Router.StrictSlash(). This defines the flag for new routes.
57 strictSlash bool
58 // See Router.SkipClean(). This defines the flag for new routes.
59 skipClean bool
60 // If true, do not clear the request context after handling the request.
61 // This has no effect when go1.7+ is used, since the context is stored
62 // on the request itself.
63 KeepContext bool
64 // see Router.UseEncodedPath(). This defines a flag for all routes.
65 useEncodedPath bool
66}
67
68// Match matches registered routes against the request.
69func (r *Router) Match(req *http.Request, match *RouteMatch) bool {
70 for _, route := range r.routes {
71 if route.Match(req, match) {
72 return true
73 }
74 }
75
76 if match.MatchErr == ErrMethodMismatch && r.MethodNotAllowedHandler != nil {
77 match.Handler = r.MethodNotAllowedHandler
78 return true
79 }
80
81 // Closest match for a router (includes sub-routers)
82 if r.NotFoundHandler != nil {
83 match.Handler = r.NotFoundHandler
84 return true
85 }
86 return false
87}
88
89// ServeHTTP dispatches the handler registered in the matched route.
90//
91// When there is a match, the route variables can be retrieved calling
92// mux.Vars(request).
93func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
94 if !r.skipClean {
95 path := req.URL.Path
96 if r.useEncodedPath {
97 path = getPath(req)
98 }
99 // Clean path to canonical form and redirect.
100 if p := cleanPath(path); p != path {
101
102 // Added 3 lines (Philip Schlump) - It was dropping the query string and #whatever from query.
103 // This matches with fix in go 1.2 r.c. 4 for same problem. Go Issue:
104 // http://code.google.com/p/go/issues/detail?id=5252
105 url := *req.URL
106 url.Path = p
107 p = url.String()
108
109 w.Header().Set("Location", p)
110 w.WriteHeader(http.StatusMovedPermanently)
111 return
112 }
113 }
114 var match RouteMatch
115 var handler http.Handler
116 if r.Match(req, &match) {
117 handler = match.Handler
118 req = setVars(req, match.Vars)
119 req = setCurrentRoute(req, match.Route)
120 }
121
122 if handler == nil && match.MatchErr == ErrMethodMismatch {
123 handler = methodNotAllowedHandler()
124 }
125
126 if handler == nil {
127 handler = http.NotFoundHandler()
128 }
129
130 if !r.KeepContext {
131 defer contextClear(req)
132 }
133 handler.ServeHTTP(w, req)
134}
135
136// Get returns a route registered with the given name.
137func (r *Router) Get(name string) *Route {
138 return r.getNamedRoutes()[name]
139}
140
141// GetRoute returns a route registered with the given name. This method
142// was renamed to Get() and remains here for backwards compatibility.
143func (r *Router) GetRoute(name string) *Route {
144 return r.getNamedRoutes()[name]
145}
146
147// StrictSlash defines the trailing slash behavior for new routes. The initial
148// value is false.
149//
150// When true, if the route path is "/path/", accessing "/path" will redirect
151// to the former and vice versa. In other words, your application will always
152// see the path as specified in the route.
153//
154// When false, if the route path is "/path", accessing "/path/" will not match
155// this route and vice versa.
156//
157// Special case: when a route sets a path prefix using the PathPrefix() method,
158// strict slash is ignored for that route because the redirect behavior can't
159// be determined from a prefix alone. However, any subrouters created from that
160// route inherit the original StrictSlash setting.
161func (r *Router) StrictSlash(value bool) *Router {
162 r.strictSlash = value
163 return r
164}
165
166// SkipClean defines the path cleaning behaviour for new routes. The initial
167// value is false. Users should be careful about which routes are not cleaned
168//
169// When true, if the route path is "/path//to", it will remain with the double
170// slash. This is helpful if you have a route like: /fetch/http://xkcd.com/534/
171//
172// When false, the path will be cleaned, so /fetch/http://xkcd.com/534/ will
173// become /fetch/http/xkcd.com/534
174func (r *Router) SkipClean(value bool) *Router {
175 r.skipClean = value
176 return r
177}
178
179// UseEncodedPath tells the router to match the encoded original path
180// to the routes.
181// For eg. "/path/foo%2Fbar/to" will match the path "/path/{var}/to".
182// This behavior has the drawback of needing to match routes against
183// r.RequestURI instead of r.URL.Path. Any modifications (such as http.StripPrefix)
184// to r.URL.Path will not affect routing when this flag is on and thus may
185// induce unintended behavior.
186//
187// If not called, the router will match the unencoded path to the routes.
188// For eg. "/path/foo%2Fbar/to" will match the path "/path/foo/bar/to"
189func (r *Router) UseEncodedPath() *Router {
190 r.useEncodedPath = true
191 return r
192}
193
194// ----------------------------------------------------------------------------
195// parentRoute
196// ----------------------------------------------------------------------------
197
198func (r *Router) getBuildScheme() string {
199 if r.parent != nil {
200 return r.parent.getBuildScheme()
201 }
202 return ""
203}
204
205// getNamedRoutes returns the map where named routes are registered.
206func (r *Router) getNamedRoutes() map[string]*Route {
207 if r.namedRoutes == nil {
208 if r.parent != nil {
209 r.namedRoutes = r.parent.getNamedRoutes()
210 } else {
211 r.namedRoutes = make(map[string]*Route)
212 }
213 }
214 return r.namedRoutes
215}
216
217// getRegexpGroup returns regexp definitions from the parent route, if any.
218func (r *Router) getRegexpGroup() *routeRegexpGroup {
219 if r.parent != nil {
220 return r.parent.getRegexpGroup()
221 }
222 return nil
223}
224
225func (r *Router) buildVars(m map[string]string) map[string]string {
226 if r.parent != nil {
227 m = r.parent.buildVars(m)
228 }
229 return m
230}
231
232// ----------------------------------------------------------------------------
233// Route factories
234// ----------------------------------------------------------------------------
235
236// NewRoute registers an empty route.
237func (r *Router) NewRoute() *Route {
238 route := &Route{parent: r, strictSlash: r.strictSlash, skipClean: r.skipClean, useEncodedPath: r.useEncodedPath}
239 r.routes = append(r.routes, route)
240 return route
241}
242
243// Handle registers a new route with a matcher for the URL path.
244// See Route.Path() and Route.Handler().
245func (r *Router) Handle(path string, handler http.Handler) *Route {
246 return r.NewRoute().Path(path).Handler(handler)
247}
248
249// HandleFunc registers a new route with a matcher for the URL path.
250// See Route.Path() and Route.HandlerFunc().
251func (r *Router) HandleFunc(path string, f func(http.ResponseWriter,
252 *http.Request)) *Route {
253 return r.NewRoute().Path(path).HandlerFunc(f)
254}
255
256// Headers registers a new route with a matcher for request header values.
257// See Route.Headers().
258func (r *Router) Headers(pairs ...string) *Route {
259 return r.NewRoute().Headers(pairs...)
260}
261
262// Host registers a new route with a matcher for the URL host.
263// See Route.Host().
264func (r *Router) Host(tpl string) *Route {
265 return r.NewRoute().Host(tpl)
266}
267
268// MatcherFunc registers a new route with a custom matcher function.
269// See Route.MatcherFunc().
270func (r *Router) MatcherFunc(f MatcherFunc) *Route {
271 return r.NewRoute().MatcherFunc(f)
272}
273
274// Methods registers a new route with a matcher for HTTP methods.
275// See Route.Methods().
276func (r *Router) Methods(methods ...string) *Route {
277 return r.NewRoute().Methods(methods...)
278}
279
280// Path registers a new route with a matcher for the URL path.
281// See Route.Path().
282func (r *Router) Path(tpl string) *Route {
283 return r.NewRoute().Path(tpl)
284}
285
286// PathPrefix registers a new route with a matcher for the URL path prefix.
287// See Route.PathPrefix().
288func (r *Router) PathPrefix(tpl string) *Route {
289 return r.NewRoute().PathPrefix(tpl)
290}
291
292// Queries registers a new route with a matcher for URL query values.
293// See Route.Queries().
294func (r *Router) Queries(pairs ...string) *Route {
295 return r.NewRoute().Queries(pairs...)
296}
297
298// Schemes registers a new route with a matcher for URL schemes.
299// See Route.Schemes().
300func (r *Router) Schemes(schemes ...string) *Route {
301 return r.NewRoute().Schemes(schemes...)
302}
303
304// BuildVarsFunc registers a new route with a custom function for modifying
305// route variables before building a URL.
306func (r *Router) BuildVarsFunc(f BuildVarsFunc) *Route {
307 return r.NewRoute().BuildVarsFunc(f)
308}
309
310// Walk walks the router and all its sub-routers, calling walkFn for each route
311// in the tree. The routes are walked in the order they were added. Sub-routers
312// are explored depth-first.
313func (r *Router) Walk(walkFn WalkFunc) error {
314 return r.walk(walkFn, []*Route{})
315}
316
317// SkipRouter is used as a return value from WalkFuncs to indicate that the
318// router that walk is about to descend down to should be skipped.
319var SkipRouter = errors.New("skip this router")
320
321// WalkFunc is the type of the function called for each route visited by Walk.
322// At every invocation, it is given the current route, and the current router,
323// and a list of ancestor routes that lead to the current route.
324type WalkFunc func(route *Route, router *Router, ancestors []*Route) error
325
326func (r *Router) walk(walkFn WalkFunc, ancestors []*Route) error {
327 for _, t := range r.routes {
328 err := walkFn(t, r, ancestors)
329 if err == SkipRouter {
330 continue
331 }
332 if err != nil {
333 return err
334 }
335 for _, sr := range t.matchers {
336 if h, ok := sr.(*Router); ok {
337 ancestors = append(ancestors, t)
338 err := h.walk(walkFn, ancestors)
339 if err != nil {
340 return err
341 }
342 ancestors = ancestors[:len(ancestors)-1]
343 }
344 }
345 if h, ok := t.handler.(*Router); ok {
346 ancestors = append(ancestors, t)
347 err := h.walk(walkFn, ancestors)
348 if err != nil {
349 return err
350 }
351 ancestors = ancestors[:len(ancestors)-1]
352 }
353 }
354 return nil
355}
356
357// ----------------------------------------------------------------------------
358// Context
359// ----------------------------------------------------------------------------
360
361// RouteMatch stores information about a matched route.
362type RouteMatch struct {
363 Route *Route
364 Handler http.Handler
365 Vars map[string]string
366
367 // MatchErr is set to appropriate matching error
368 // It is set to ErrMethodMismatch if there is a mismatch in
369 // the request method and route method
370 MatchErr error
371}
372
373type contextKey int
374
375const (
376 varsKey contextKey = iota
377 routeKey
378)
379
380// Vars returns the route variables for the current request, if any.
381func Vars(r *http.Request) map[string]string {
382 if rv := contextGet(r, varsKey); rv != nil {
383 return rv.(map[string]string)
384 }
385 return nil
386}
387
388// CurrentRoute returns the matched route for the current request, if any.
389// This only works when called inside the handler of the matched route
390// because the matched route is stored in the request context which is cleared
391// after the handler returns, unless the KeepContext option is set on the
392// Router.
393func CurrentRoute(r *http.Request) *Route {
394 if rv := contextGet(r, routeKey); rv != nil {
395 return rv.(*Route)
396 }
397 return nil
398}
399
400func setVars(r *http.Request, val interface{}) *http.Request {
401 return contextSet(r, varsKey, val)
402}
403
404func setCurrentRoute(r *http.Request, val interface{}) *http.Request {
405 return contextSet(r, routeKey, val)
406}
407
408// ----------------------------------------------------------------------------
409// Helpers
410// ----------------------------------------------------------------------------
411
412// getPath returns the escaped path if possible; doing what URL.EscapedPath()
413// which was added in go1.5 does
414func getPath(req *http.Request) string {
415 if req.RequestURI != "" {
416 // Extract the path from RequestURI (which is escaped unlike URL.Path)
417 // as detailed here as detailed in https://golang.org/pkg/net/url/#URL
418 // for < 1.5 server side workaround
419 // http://localhost/path/here?v=1 -> /path/here
420 path := req.RequestURI
421 path = strings.TrimPrefix(path, req.URL.Scheme+`://`)
422 path = strings.TrimPrefix(path, req.URL.Host)
423 if i := strings.LastIndex(path, "?"); i > -1 {
424 path = path[:i]
425 }
426 if i := strings.LastIndex(path, "#"); i > -1 {
427 path = path[:i]
428 }
429 return path
430 }
431 return req.URL.Path
432}
433
434// cleanPath returns the canonical path for p, eliminating . and .. elements.
435// Borrowed from the net/http package.
436func cleanPath(p string) string {
437 if p == "" {
438 return "/"
439 }
440 if p[0] != '/' {
441 p = "/" + p
442 }
443 np := path.Clean(p)
444 // path.Clean removes trailing slash except for root;
445 // put the trailing slash back if necessary.
446 if p[len(p)-1] == '/' && np != "/" {
447 np += "/"
448 }
449
450 return np
451}
452
453// uniqueVars returns an error if two slices contain duplicated strings.
454func uniqueVars(s1, s2 []string) error {
455 for _, v1 := range s1 {
456 for _, v2 := range s2 {
457 if v1 == v2 {
458 return fmt.Errorf("mux: duplicated route variable %q", v2)
459 }
460 }
461 }
462 return nil
463}
464
465// checkPairs returns the count of strings passed in, and an error if
466// the count is not an even number.
467func checkPairs(pairs ...string) (int, error) {
468 length := len(pairs)
469 if length%2 != 0 {
470 return length, fmt.Errorf(
471 "mux: number of parameters must be multiple of 2, got %v", pairs)
472 }
473 return length, nil
474}
475
476// mapFromPairsToString converts variadic string parameters to a
477// string to string map.
478func mapFromPairsToString(pairs ...string) (map[string]string, error) {
479 length, err := checkPairs(pairs...)
480 if err != nil {
481 return nil, err
482 }
483 m := make(map[string]string, length/2)
484 for i := 0; i < length; i += 2 {
485 m[pairs[i]] = pairs[i+1]
486 }
487 return m, nil
488}
489
490// mapFromPairsToRegex converts variadic string parameters to a
491// string to regex map.
492func mapFromPairsToRegex(pairs ...string) (map[string]*regexp.Regexp, error) {
493 length, err := checkPairs(pairs...)
494 if err != nil {
495 return nil, err
496 }
497 m := make(map[string]*regexp.Regexp, length/2)
498 for i := 0; i < length; i += 2 {
499 regex, err := regexp.Compile(pairs[i+1])
500 if err != nil {
501 return nil, err
502 }
503 m[pairs[i]] = regex
504 }
505 return m, nil
506}
507
508// matchInArray returns true if the given string value is in the array.
509func matchInArray(arr []string, value string) bool {
510 for _, v := range arr {
511 if v == value {
512 return true
513 }
514 }
515 return false
516}
517
518// matchMapWithString returns true if the given key/value pairs exist in a given map.
519func matchMapWithString(toCheck map[string]string, toMatch map[string][]string, canonicalKey bool) bool {
520 for k, v := range toCheck {
521 // Check if key exists.
522 if canonicalKey {
523 k = http.CanonicalHeaderKey(k)
524 }
525 if values := toMatch[k]; values == nil {
526 return false
527 } else if v != "" {
528 // If value was defined as an empty string we only check that the
529 // key exists. Otherwise we also check for equality.
530 valueExists := false
531 for _, value := range values {
532 if v == value {
533 valueExists = true
534 break
535 }
536 }
537 if !valueExists {
538 return false
539 }
540 }
541 }
542 return true
543}
544
545// matchMapWithRegex returns true if the given key/value pairs exist in a given map compiled against
546// the given regex
547func matchMapWithRegex(toCheck map[string]*regexp.Regexp, toMatch map[string][]string, canonicalKey bool) bool {
548 for k, v := range toCheck {
549 // Check if key exists.
550 if canonicalKey {
551 k = http.CanonicalHeaderKey(k)
552 }
553 if values := toMatch[k]; values == nil {
554 return false
555 } else if v != nil {
556 // If value was defined as an empty string we only check that the
557 // key exists. Otherwise we also check for equality.
558 valueExists := false
559 for _, value := range values {
560 if v.MatchString(value) {
561 valueExists = true
562 break
563 }
564 }
565 if !valueExists {
566 return false
567 }
568 }
569 }
570 return true
571}
572
573// methodNotAllowed replies to the request with an HTTP status code 405.
574func methodNotAllowed(w http.ResponseWriter, r *http.Request) {
575 w.WriteHeader(http.StatusMethodNotAllowed)
576}
577
578// methodNotAllowedHandler returns a simple request handler
579// that replies to each request with a status code 405.
580func methodNotAllowedHandler() http.Handler { return http.HandlerFunc(methodNotAllowed) }