blob: 6863adba50e60f895a32f949c6225892f9e62673 [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 "net/url"
12 "regexp"
13 "strings"
14)
15
16// Route stores information to match a request and build URLs.
17type Route struct {
18 // Parent where the route was registered (a Router).
19 parent parentRoute
20 // Request handler for the route.
21 handler http.Handler
22 // List of matchers.
23 matchers []matcher
24 // Manager for the variables from host and path.
25 regexp *routeRegexpGroup
26 // If true, when the path pattern is "/path/", accessing "/path" will
27 // redirect to the former and vice versa.
28 strictSlash bool
29 // If true, when the path pattern is "/path//to", accessing "/path//to"
30 // will not redirect
31 skipClean bool
32 // If true, "/path/foo%2Fbar/to" will match the path "/path/{var}/to"
33 useEncodedPath bool
34 // The scheme used when building URLs.
35 buildScheme string
36 // If true, this route never matches: it is only used to build URLs.
37 buildOnly bool
38 // The name used to build URLs.
39 name string
40 // Error resulted from building a route.
41 err error
42
43 buildVarsFunc BuildVarsFunc
44}
45
46func (r *Route) SkipClean() bool {
47 return r.skipClean
48}
49
50// Match matches the route against the request.
51func (r *Route) Match(req *http.Request, match *RouteMatch) bool {
52 if r.buildOnly || r.err != nil {
53 return false
54 }
55
56 var matchErr error
57
58 // Match everything.
59 for _, m := range r.matchers {
60 if matched := m.Match(req, match); !matched {
61 if _, ok := m.(methodMatcher); ok {
62 matchErr = ErrMethodMismatch
63 continue
64 }
65 matchErr = nil
66 return false
67 }
68 }
69
70 if matchErr != nil {
71 match.MatchErr = matchErr
72 return false
73 }
74
75 match.MatchErr = nil
76 // Yay, we have a match. Let's collect some info about it.
77 if match.Route == nil {
78 match.Route = r
79 }
80 if match.Handler == nil {
81 match.Handler = r.handler
82 }
83 if match.Vars == nil {
84 match.Vars = make(map[string]string)
85 }
86
87 // Set variables.
88 if r.regexp != nil {
89 r.regexp.setMatch(req, match, r)
90 }
91 return true
92}
93
94// ----------------------------------------------------------------------------
95// Route attributes
96// ----------------------------------------------------------------------------
97
98// GetError returns an error resulted from building the route, if any.
99func (r *Route) GetError() error {
100 return r.err
101}
102
103// BuildOnly sets the route to never match: it is only used to build URLs.
104func (r *Route) BuildOnly() *Route {
105 r.buildOnly = true
106 return r
107}
108
109// Handler --------------------------------------------------------------------
110
111// Handler sets a handler for the route.
112func (r *Route) Handler(handler http.Handler) *Route {
113 if r.err == nil {
114 r.handler = handler
115 }
116 return r
117}
118
119// HandlerFunc sets a handler function for the route.
120func (r *Route) HandlerFunc(f func(http.ResponseWriter, *http.Request)) *Route {
121 return r.Handler(http.HandlerFunc(f))
122}
123
124// GetHandler returns the handler for the route, if any.
125func (r *Route) GetHandler() http.Handler {
126 return r.handler
127}
128
129// Name -----------------------------------------------------------------------
130
131// Name sets the name for the route, used to build URLs.
132// If the name was registered already it will be overwritten.
133func (r *Route) Name(name string) *Route {
134 if r.name != "" {
135 r.err = fmt.Errorf("mux: route already has name %q, can't set %q",
136 r.name, name)
137 }
138 if r.err == nil {
139 r.name = name
140 r.getNamedRoutes()[name] = r
141 }
142 return r
143}
144
145// GetName returns the name for the route, if any.
146func (r *Route) GetName() string {
147 return r.name
148}
149
150// ----------------------------------------------------------------------------
151// Matchers
152// ----------------------------------------------------------------------------
153
154// matcher types try to match a request.
155type matcher interface {
156 Match(*http.Request, *RouteMatch) bool
157}
158
159// addMatcher adds a matcher to the route.
160func (r *Route) addMatcher(m matcher) *Route {
161 if r.err == nil {
162 r.matchers = append(r.matchers, m)
163 }
164 return r
165}
166
167// addRegexpMatcher adds a host or path matcher and builder to a route.
168func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix, matchQuery bool) error {
169 if r.err != nil {
170 return r.err
171 }
172 r.regexp = r.getRegexpGroup()
173 if !matchHost && !matchQuery {
174 if len(tpl) > 0 && tpl[0] != '/' {
175 return fmt.Errorf("mux: path must start with a slash, got %q", tpl)
176 }
177 if r.regexp.path != nil {
178 tpl = strings.TrimRight(r.regexp.path.template, "/") + tpl
179 }
180 }
181 rr, err := newRouteRegexp(tpl, matchHost, matchPrefix, matchQuery, r.strictSlash, r.useEncodedPath)
182 if err != nil {
183 return err
184 }
185 for _, q := range r.regexp.queries {
186 if err = uniqueVars(rr.varsN, q.varsN); err != nil {
187 return err
188 }
189 }
190 if matchHost {
191 if r.regexp.path != nil {
192 if err = uniqueVars(rr.varsN, r.regexp.path.varsN); err != nil {
193 return err
194 }
195 }
196 r.regexp.host = rr
197 } else {
198 if r.regexp.host != nil {
199 if err = uniqueVars(rr.varsN, r.regexp.host.varsN); err != nil {
200 return err
201 }
202 }
203 if matchQuery {
204 r.regexp.queries = append(r.regexp.queries, rr)
205 } else {
206 r.regexp.path = rr
207 }
208 }
209 r.addMatcher(rr)
210 return nil
211}
212
213// Headers --------------------------------------------------------------------
214
215// headerMatcher matches the request against header values.
216type headerMatcher map[string]string
217
218func (m headerMatcher) Match(r *http.Request, match *RouteMatch) bool {
219 return matchMapWithString(m, r.Header, true)
220}
221
222// Headers adds a matcher for request header values.
223// It accepts a sequence of key/value pairs to be matched. For example:
224//
225// r := mux.NewRouter()
226// r.Headers("Content-Type", "application/json",
227// "X-Requested-With", "XMLHttpRequest")
228//
229// The above route will only match if both request header values match.
230// If the value is an empty string, it will match any value if the key is set.
231func (r *Route) Headers(pairs ...string) *Route {
232 if r.err == nil {
233 var headers map[string]string
234 headers, r.err = mapFromPairsToString(pairs...)
235 return r.addMatcher(headerMatcher(headers))
236 }
237 return r
238}
239
240// headerRegexMatcher matches the request against the route given a regex for the header
241type headerRegexMatcher map[string]*regexp.Regexp
242
243func (m headerRegexMatcher) Match(r *http.Request, match *RouteMatch) bool {
244 return matchMapWithRegex(m, r.Header, true)
245}
246
247// HeadersRegexp accepts a sequence of key/value pairs, where the value has regex
248// support. For example:
249//
250// r := mux.NewRouter()
251// r.HeadersRegexp("Content-Type", "application/(text|json)",
252// "X-Requested-With", "XMLHttpRequest")
253//
254// The above route will only match if both the request header matches both regular expressions.
255// It the value is an empty string, it will match any value if the key is set.
256func (r *Route) HeadersRegexp(pairs ...string) *Route {
257 if r.err == nil {
258 var headers map[string]*regexp.Regexp
259 headers, r.err = mapFromPairsToRegex(pairs...)
260 return r.addMatcher(headerRegexMatcher(headers))
261 }
262 return r
263}
264
265// Host -----------------------------------------------------------------------
266
267// Host adds a matcher for the URL host.
268// It accepts a template with zero or more URL variables enclosed by {}.
269// Variables can define an optional regexp pattern to be matched:
270//
271// - {name} matches anything until the next dot.
272//
273// - {name:pattern} matches the given regexp pattern.
274//
275// For example:
276//
277// r := mux.NewRouter()
278// r.Host("www.example.com")
279// r.Host("{subdomain}.domain.com")
280// r.Host("{subdomain:[a-z]+}.domain.com")
281//
282// Variable names must be unique in a given route. They can be retrieved
283// calling mux.Vars(request).
284func (r *Route) Host(tpl string) *Route {
285 r.err = r.addRegexpMatcher(tpl, true, false, false)
286 return r
287}
288
289// MatcherFunc ----------------------------------------------------------------
290
291// MatcherFunc is the function signature used by custom matchers.
292type MatcherFunc func(*http.Request, *RouteMatch) bool
293
294// Match returns the match for a given request.
295func (m MatcherFunc) Match(r *http.Request, match *RouteMatch) bool {
296 return m(r, match)
297}
298
299// MatcherFunc adds a custom function to be used as request matcher.
300func (r *Route) MatcherFunc(f MatcherFunc) *Route {
301 return r.addMatcher(f)
302}
303
304// Methods --------------------------------------------------------------------
305
306// methodMatcher matches the request against HTTP methods.
307type methodMatcher []string
308
309func (m methodMatcher) Match(r *http.Request, match *RouteMatch) bool {
310 return matchInArray(m, r.Method)
311}
312
313// Methods adds a matcher for HTTP methods.
314// It accepts a sequence of one or more methods to be matched, e.g.:
315// "GET", "POST", "PUT".
316func (r *Route) Methods(methods ...string) *Route {
317 for k, v := range methods {
318 methods[k] = strings.ToUpper(v)
319 }
320 return r.addMatcher(methodMatcher(methods))
321}
322
323// Path -----------------------------------------------------------------------
324
325// Path adds a matcher for the URL path.
326// It accepts a template with zero or more URL variables enclosed by {}. The
327// template must start with a "/".
328// Variables can define an optional regexp pattern to be matched:
329//
330// - {name} matches anything until the next slash.
331//
332// - {name:pattern} matches the given regexp pattern.
333//
334// For example:
335//
336// r := mux.NewRouter()
337// r.Path("/products/").Handler(ProductsHandler)
338// r.Path("/products/{key}").Handler(ProductsHandler)
339// r.Path("/articles/{category}/{id:[0-9]+}").
340// Handler(ArticleHandler)
341//
342// Variable names must be unique in a given route. They can be retrieved
343// calling mux.Vars(request).
344func (r *Route) Path(tpl string) *Route {
345 r.err = r.addRegexpMatcher(tpl, false, false, false)
346 return r
347}
348
349// PathPrefix -----------------------------------------------------------------
350
351// PathPrefix adds a matcher for the URL path prefix. This matches if the given
352// template is a prefix of the full URL path. See Route.Path() for details on
353// the tpl argument.
354//
355// Note that it does not treat slashes specially ("/foobar/" will be matched by
356// the prefix "/foo") so you may want to use a trailing slash here.
357//
358// Also note that the setting of Router.StrictSlash() has no effect on routes
359// with a PathPrefix matcher.
360func (r *Route) PathPrefix(tpl string) *Route {
361 r.err = r.addRegexpMatcher(tpl, false, true, false)
362 return r
363}
364
365// Query ----------------------------------------------------------------------
366
367// Queries adds a matcher for URL query values.
368// It accepts a sequence of key/value pairs. Values may define variables.
369// For example:
370//
371// r := mux.NewRouter()
372// r.Queries("foo", "bar", "id", "{id:[0-9]+}")
373//
374// The above route will only match if the URL contains the defined queries
375// values, e.g.: ?foo=bar&id=42.
376//
377// It the value is an empty string, it will match any value if the key is set.
378//
379// Variables can define an optional regexp pattern to be matched:
380//
381// - {name} matches anything until the next slash.
382//
383// - {name:pattern} matches the given regexp pattern.
384func (r *Route) Queries(pairs ...string) *Route {
385 length := len(pairs)
386 if length%2 != 0 {
387 r.err = fmt.Errorf(
388 "mux: number of parameters must be multiple of 2, got %v", pairs)
389 return nil
390 }
391 for i := 0; i < length; i += 2 {
392 if r.err = r.addRegexpMatcher(pairs[i]+"="+pairs[i+1], false, false, true); r.err != nil {
393 return r
394 }
395 }
396
397 return r
398}
399
400// Schemes --------------------------------------------------------------------
401
402// schemeMatcher matches the request against URL schemes.
403type schemeMatcher []string
404
405func (m schemeMatcher) Match(r *http.Request, match *RouteMatch) bool {
406 return matchInArray(m, r.URL.Scheme)
407}
408
409// Schemes adds a matcher for URL schemes.
410// It accepts a sequence of schemes to be matched, e.g.: "http", "https".
411func (r *Route) Schemes(schemes ...string) *Route {
412 for k, v := range schemes {
413 schemes[k] = strings.ToLower(v)
414 }
415 if r.buildScheme == "" && len(schemes) > 0 {
416 r.buildScheme = schemes[0]
417 }
418 return r.addMatcher(schemeMatcher(schemes))
419}
420
421// BuildVarsFunc --------------------------------------------------------------
422
423// BuildVarsFunc is the function signature used by custom build variable
424// functions (which can modify route variables before a route's URL is built).
425type BuildVarsFunc func(map[string]string) map[string]string
426
427// BuildVarsFunc adds a custom function to be used to modify build variables
428// before a route's URL is built.
429func (r *Route) BuildVarsFunc(f BuildVarsFunc) *Route {
430 r.buildVarsFunc = f
431 return r
432}
433
434// Subrouter ------------------------------------------------------------------
435
436// Subrouter creates a subrouter for the route.
437//
438// It will test the inner routes only if the parent route matched. For example:
439//
440// r := mux.NewRouter()
441// s := r.Host("www.example.com").Subrouter()
442// s.HandleFunc("/products/", ProductsHandler)
443// s.HandleFunc("/products/{key}", ProductHandler)
444// s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler)
445//
446// Here, the routes registered in the subrouter won't be tested if the host
447// doesn't match.
448func (r *Route) Subrouter() *Router {
449 router := &Router{parent: r, strictSlash: r.strictSlash}
450 r.addMatcher(router)
451 return router
452}
453
454// ----------------------------------------------------------------------------
455// URL building
456// ----------------------------------------------------------------------------
457
458// URL builds a URL for the route.
459//
460// It accepts a sequence of key/value pairs for the route variables. For
461// example, given this route:
462//
463// r := mux.NewRouter()
464// r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
465// Name("article")
466//
467// ...a URL for it can be built using:
468//
469// url, err := r.Get("article").URL("category", "technology", "id", "42")
470//
471// ...which will return an url.URL with the following path:
472//
473// "/articles/technology/42"
474//
475// This also works for host variables:
476//
477// r := mux.NewRouter()
478// r.Host("{subdomain}.domain.com").
479// HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
480// Name("article")
481//
482// // url.String() will be "http://news.domain.com/articles/technology/42"
483// url, err := r.Get("article").URL("subdomain", "news",
484// "category", "technology",
485// "id", "42")
486//
487// All variables defined in the route are required, and their values must
488// conform to the corresponding patterns.
489func (r *Route) URL(pairs ...string) (*url.URL, error) {
490 if r.err != nil {
491 return nil, r.err
492 }
493 if r.regexp == nil {
494 return nil, errors.New("mux: route doesn't have a host or path")
495 }
496 values, err := r.prepareVars(pairs...)
497 if err != nil {
498 return nil, err
499 }
500 var scheme, host, path string
501 queries := make([]string, 0, len(r.regexp.queries))
502 if r.regexp.host != nil {
503 if host, err = r.regexp.host.url(values); err != nil {
504 return nil, err
505 }
506 scheme = "http"
507 if s := r.getBuildScheme(); s != "" {
508 scheme = s
509 }
510 }
511 if r.regexp.path != nil {
512 if path, err = r.regexp.path.url(values); err != nil {
513 return nil, err
514 }
515 }
516 for _, q := range r.regexp.queries {
517 var query string
518 if query, err = q.url(values); err != nil {
519 return nil, err
520 }
521 queries = append(queries, query)
522 }
523 return &url.URL{
524 Scheme: scheme,
525 Host: host,
526 Path: path,
527 RawQuery: strings.Join(queries, "&"),
528 }, nil
529}
530
531// URLHost builds the host part of the URL for a route. See Route.URL().
532//
533// The route must have a host defined.
534func (r *Route) URLHost(pairs ...string) (*url.URL, error) {
535 if r.err != nil {
536 return nil, r.err
537 }
538 if r.regexp == nil || r.regexp.host == nil {
539 return nil, errors.New("mux: route doesn't have a host")
540 }
541 values, err := r.prepareVars(pairs...)
542 if err != nil {
543 return nil, err
544 }
545 host, err := r.regexp.host.url(values)
546 if err != nil {
547 return nil, err
548 }
549 u := &url.URL{
550 Scheme: "http",
551 Host: host,
552 }
553 if s := r.getBuildScheme(); s != "" {
554 u.Scheme = s
555 }
556 return u, nil
557}
558
559// URLPath builds the path part of the URL for a route. See Route.URL().
560//
561// The route must have a path defined.
562func (r *Route) URLPath(pairs ...string) (*url.URL, error) {
563 if r.err != nil {
564 return nil, r.err
565 }
566 if r.regexp == nil || r.regexp.path == nil {
567 return nil, errors.New("mux: route doesn't have a path")
568 }
569 values, err := r.prepareVars(pairs...)
570 if err != nil {
571 return nil, err
572 }
573 path, err := r.regexp.path.url(values)
574 if err != nil {
575 return nil, err
576 }
577 return &url.URL{
578 Path: path,
579 }, nil
580}
581
582// GetPathTemplate returns the template used to build the
583// route match.
584// This is useful for building simple REST API documentation and for instrumentation
585// against third-party services.
586// An error will be returned if the route does not define a path.
587func (r *Route) GetPathTemplate() (string, error) {
588 if r.err != nil {
589 return "", r.err
590 }
591 if r.regexp == nil || r.regexp.path == nil {
592 return "", errors.New("mux: route doesn't have a path")
593 }
594 return r.regexp.path.template, nil
595}
596
597// GetPathRegexp returns the expanded regular expression used to match route path.
598// This is useful for building simple REST API documentation and for instrumentation
599// against third-party services.
600// An error will be returned if the route does not define a path.
601func (r *Route) GetPathRegexp() (string, error) {
602 if r.err != nil {
603 return "", r.err
604 }
605 if r.regexp == nil || r.regexp.path == nil {
606 return "", errors.New("mux: route does not have a path")
607 }
608 return r.regexp.path.regexp.String(), nil
609}
610
611// GetMethods returns the methods the route matches against
612// This is useful for building simple REST API documentation and for instrumentation
613// against third-party services.
614// An empty list will be returned if route does not have methods.
615func (r *Route) GetMethods() ([]string, error) {
616 if r.err != nil {
617 return nil, r.err
618 }
619 for _, m := range r.matchers {
620 if methods, ok := m.(methodMatcher); ok {
621 return []string(methods), nil
622 }
623 }
624 return nil, nil
625}
626
627// GetHostTemplate returns the template used to build the
628// route match.
629// This is useful for building simple REST API documentation and for instrumentation
630// against third-party services.
631// An error will be returned if the route does not define a host.
632func (r *Route) GetHostTemplate() (string, error) {
633 if r.err != nil {
634 return "", r.err
635 }
636 if r.regexp == nil || r.regexp.host == nil {
637 return "", errors.New("mux: route doesn't have a host")
638 }
639 return r.regexp.host.template, nil
640}
641
642// prepareVars converts the route variable pairs into a map. If the route has a
643// BuildVarsFunc, it is invoked.
644func (r *Route) prepareVars(pairs ...string) (map[string]string, error) {
645 m, err := mapFromPairsToString(pairs...)
646 if err != nil {
647 return nil, err
648 }
649 return r.buildVars(m), nil
650}
651
652func (r *Route) buildVars(m map[string]string) map[string]string {
653 if r.parent != nil {
654 m = r.parent.buildVars(m)
655 }
656 if r.buildVarsFunc != nil {
657 m = r.buildVarsFunc(m)
658 }
659 return m
660}
661
662// ----------------------------------------------------------------------------
663// parentRoute
664// ----------------------------------------------------------------------------
665
666// parentRoute allows routes to know about parent host and path definitions.
667type parentRoute interface {
668 getBuildScheme() string
669 getNamedRoutes() map[string]*Route
670 getRegexpGroup() *routeRegexpGroup
671 buildVars(map[string]string) map[string]string
672}
673
674func (r *Route) getBuildScheme() string {
675 if r.buildScheme != "" {
676 return r.buildScheme
677 }
678 if r.parent != nil {
679 return r.parent.getBuildScheme()
680 }
681 return ""
682}
683
684// getNamedRoutes returns the map where named routes are registered.
685func (r *Route) getNamedRoutes() map[string]*Route {
686 if r.parent == nil {
687 // During tests router is not always set.
688 r.parent = NewRouter()
689 }
690 return r.parent.getNamedRoutes()
691}
692
693// getRegexpGroup returns regexp definitions from this route.
694func (r *Route) getRegexpGroup() *routeRegexpGroup {
695 if r.regexp == nil {
696 if r.parent == nil {
697 // During tests router is not always set.
698 r.parent = NewRouter()
699 }
700 regexp := r.parent.getRegexpGroup()
701 if regexp == nil {
702 r.regexp = new(routeRegexpGroup)
703 } else {
704 // Copy.
705 r.regexp = &routeRegexpGroup{
706 host: regexp.host,
707 path: regexp.path,
708 queries: regexp.queries,
709 }
710 }
711 }
712 return r.regexp
713}