route.go

  1// 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}