blob: 750afe570d05396cda6b4ce037f6ff2f97cafe30 [file] [log] [blame]
Naveen Sampath04696f72022-06-13 15:19:14 +05301// 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 && r.handler != nil {
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 scheme := r.URL.Scheme
416 // https://golang.org/pkg/net/http/#Request
417 // "For [most] server requests, fields other than Path and RawQuery will be
418 // empty."
419 // Since we're an http muxer, the scheme is either going to be http or https
420 // though, so we can just set it based on the tls termination state.
421 if scheme == "" {
422 if r.TLS == nil {
423 scheme = "http"
424 } else {
425 scheme = "https"
426 }
427 }
428 return matchInArray(m, scheme)
429}
430
431// Schemes adds a matcher for URL schemes.
432// It accepts a sequence of schemes to be matched, e.g.: "http", "https".
433// If the request's URL has a scheme set, it will be matched against.
434// Generally, the URL scheme will only be set if a previous handler set it,
435// such as the ProxyHeaders handler from gorilla/handlers.
436// If unset, the scheme will be determined based on the request's TLS
437// termination state.
438// The first argument to Schemes will be used when constructing a route URL.
439func (r *Route) Schemes(schemes ...string) *Route {
440 for k, v := range schemes {
441 schemes[k] = strings.ToLower(v)
442 }
443 if len(schemes) > 0 {
444 r.buildScheme = schemes[0]
445 }
446 return r.addMatcher(schemeMatcher(schemes))
447}
448
449// BuildVarsFunc --------------------------------------------------------------
450
451// BuildVarsFunc is the function signature used by custom build variable
452// functions (which can modify route variables before a route's URL is built).
453type BuildVarsFunc func(map[string]string) map[string]string
454
455// BuildVarsFunc adds a custom function to be used to modify build variables
456// before a route's URL is built.
457func (r *Route) BuildVarsFunc(f BuildVarsFunc) *Route {
458 if r.buildVarsFunc != nil {
459 // compose the old and new functions
460 old := r.buildVarsFunc
461 r.buildVarsFunc = func(m map[string]string) map[string]string {
462 return f(old(m))
463 }
464 } else {
465 r.buildVarsFunc = f
466 }
467 return r
468}
469
470// Subrouter ------------------------------------------------------------------
471
472// Subrouter creates a subrouter for the route.
473//
474// It will test the inner routes only if the parent route matched. For example:
475//
476// r := mux.NewRouter()
477// s := r.Host("www.example.com").Subrouter()
478// s.HandleFunc("/products/", ProductsHandler)
479// s.HandleFunc("/products/{key}", ProductHandler)
480// s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler)
481//
482// Here, the routes registered in the subrouter won't be tested if the host
483// doesn't match.
484func (r *Route) Subrouter() *Router {
485 // initialize a subrouter with a copy of the parent route's configuration
486 router := &Router{routeConf: copyRouteConf(r.routeConf), namedRoutes: r.namedRoutes}
487 r.addMatcher(router)
488 return router
489}
490
491// ----------------------------------------------------------------------------
492// URL building
493// ----------------------------------------------------------------------------
494
495// URL builds a URL for the route.
496//
497// It accepts a sequence of key/value pairs for the route variables. For
498// example, given this route:
499//
500// r := mux.NewRouter()
501// r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
502// Name("article")
503//
504// ...a URL for it can be built using:
505//
506// url, err := r.Get("article").URL("category", "technology", "id", "42")
507//
508// ...which will return an url.URL with the following path:
509//
510// "/articles/technology/42"
511//
512// This also works for host variables:
513//
514// r := mux.NewRouter()
515// r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
516// Host("{subdomain}.domain.com").
517// Name("article")
518//
519// // url.String() will be "http://news.domain.com/articles/technology/42"
520// url, err := r.Get("article").URL("subdomain", "news",
521// "category", "technology",
522// "id", "42")
523//
524// The scheme of the resulting url will be the first argument that was passed to Schemes:
525//
526// // url.String() will be "https://example.com"
527// r := mux.NewRouter()
528// url, err := r.Host("example.com")
529// .Schemes("https", "http").URL()
530//
531// All variables defined in the route are required, and their values must
532// conform to the corresponding patterns.
533func (r *Route) URL(pairs ...string) (*url.URL, error) {
534 if r.err != nil {
535 return nil, r.err
536 }
537 values, err := r.prepareVars(pairs...)
538 if err != nil {
539 return nil, err
540 }
541 var scheme, host, path string
542 queries := make([]string, 0, len(r.regexp.queries))
543 if r.regexp.host != nil {
544 if host, err = r.regexp.host.url(values); err != nil {
545 return nil, err
546 }
547 scheme = "http"
548 if r.buildScheme != "" {
549 scheme = r.buildScheme
550 }
551 }
552 if r.regexp.path != nil {
553 if path, err = r.regexp.path.url(values); err != nil {
554 return nil, err
555 }
556 }
557 for _, q := range r.regexp.queries {
558 var query string
559 if query, err = q.url(values); err != nil {
560 return nil, err
561 }
562 queries = append(queries, query)
563 }
564 return &url.URL{
565 Scheme: scheme,
566 Host: host,
567 Path: path,
568 RawQuery: strings.Join(queries, "&"),
569 }, nil
570}
571
572// URLHost builds the host part of the URL for a route. See Route.URL().
573//
574// The route must have a host defined.
575func (r *Route) URLHost(pairs ...string) (*url.URL, error) {
576 if r.err != nil {
577 return nil, r.err
578 }
579 if r.regexp.host == nil {
580 return nil, errors.New("mux: route doesn't have a host")
581 }
582 values, err := r.prepareVars(pairs...)
583 if err != nil {
584 return nil, err
585 }
586 host, err := r.regexp.host.url(values)
587 if err != nil {
588 return nil, err
589 }
590 u := &url.URL{
591 Scheme: "http",
592 Host: host,
593 }
594 if r.buildScheme != "" {
595 u.Scheme = r.buildScheme
596 }
597 return u, nil
598}
599
600// URLPath builds the path part of the URL for a route. See Route.URL().
601//
602// The route must have a path defined.
603func (r *Route) URLPath(pairs ...string) (*url.URL, error) {
604 if r.err != nil {
605 return nil, r.err
606 }
607 if r.regexp.path == nil {
608 return nil, errors.New("mux: route doesn't have a path")
609 }
610 values, err := r.prepareVars(pairs...)
611 if err != nil {
612 return nil, err
613 }
614 path, err := r.regexp.path.url(values)
615 if err != nil {
616 return nil, err
617 }
618 return &url.URL{
619 Path: path,
620 }, nil
621}
622
623// GetPathTemplate returns the template used to build the
624// route match.
625// This is useful for building simple REST API documentation and for instrumentation
626// against third-party services.
627// An error will be returned if the route does not define a path.
628func (r *Route) GetPathTemplate() (string, error) {
629 if r.err != nil {
630 return "", r.err
631 }
632 if r.regexp.path == nil {
633 return "", errors.New("mux: route doesn't have a path")
634 }
635 return r.regexp.path.template, nil
636}
637
638// GetPathRegexp returns the expanded regular expression used to match route path.
639// This is useful for building simple REST API documentation and for instrumentation
640// against third-party services.
641// An error will be returned if the route does not define a path.
642func (r *Route) GetPathRegexp() (string, error) {
643 if r.err != nil {
644 return "", r.err
645 }
646 if r.regexp.path == nil {
647 return "", errors.New("mux: route does not have a path")
648 }
649 return r.regexp.path.regexp.String(), nil
650}
651
652// GetQueriesRegexp returns the expanded regular expressions used to match the
653// route queries.
654// This is useful for building simple REST API documentation and for instrumentation
655// against third-party services.
656// An error will be returned if the route does not have queries.
657func (r *Route) GetQueriesRegexp() ([]string, error) {
658 if r.err != nil {
659 return nil, r.err
660 }
661 if r.regexp.queries == nil {
662 return nil, errors.New("mux: route doesn't have queries")
663 }
664 queries := make([]string, 0, len(r.regexp.queries))
665 for _, query := range r.regexp.queries {
666 queries = append(queries, query.regexp.String())
667 }
668 return queries, nil
669}
670
671// GetQueriesTemplates returns the templates used to build the
672// query matching.
673// This is useful for building simple REST API documentation and for instrumentation
674// against third-party services.
675// An error will be returned if the route does not define queries.
676func (r *Route) GetQueriesTemplates() ([]string, error) {
677 if r.err != nil {
678 return nil, r.err
679 }
680 if r.regexp.queries == nil {
681 return nil, errors.New("mux: route doesn't have queries")
682 }
683 queries := make([]string, 0, len(r.regexp.queries))
684 for _, query := range r.regexp.queries {
685 queries = append(queries, query.template)
686 }
687 return queries, nil
688}
689
690// GetMethods returns the methods the route matches against
691// This is useful for building simple REST API documentation and for instrumentation
692// against third-party services.
693// An error will be returned if route does not have methods.
694func (r *Route) GetMethods() ([]string, error) {
695 if r.err != nil {
696 return nil, r.err
697 }
698 for _, m := range r.matchers {
699 if methods, ok := m.(methodMatcher); ok {
700 return []string(methods), nil
701 }
702 }
703 return nil, errors.New("mux: route doesn't have methods")
704}
705
706// GetHostTemplate returns the template used to build the
707// route match.
708// This is useful for building simple REST API documentation and for instrumentation
709// against third-party services.
710// An error will be returned if the route does not define a host.
711func (r *Route) GetHostTemplate() (string, error) {
712 if r.err != nil {
713 return "", r.err
714 }
715 if r.regexp.host == nil {
716 return "", errors.New("mux: route doesn't have a host")
717 }
718 return r.regexp.host.template, nil
719}
720
721// prepareVars converts the route variable pairs into a map. If the route has a
722// BuildVarsFunc, it is invoked.
723func (r *Route) prepareVars(pairs ...string) (map[string]string, error) {
724 m, err := mapFromPairsToString(pairs...)
725 if err != nil {
726 return nil, err
727 }
728 return r.buildVars(m), nil
729}
730
731func (r *Route) buildVars(m map[string]string) map[string]string {
732 if r.buildVarsFunc != nil {
733 m = r.buildVarsFunc(m)
734 }
735 return m
736}