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