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