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