|
@@ -9,6 +9,7 @@ import (
|
|
|
"fmt"
|
|
|
"net/http"
|
|
|
"net/url"
|
|
|
+ "regexp"
|
|
|
"strings"
|
|
|
)
|
|
|
|
|
@@ -31,6 +32,8 @@ type Route struct {
|
|
|
name string
|
|
|
// Error resulted from building a route.
|
|
|
err error
|
|
|
+
|
|
|
+ buildVarsFunc BuildVarsFunc
|
|
|
}
|
|
|
|
|
|
// Match matches the route against the request.
|
|
@@ -186,7 +189,7 @@ func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix, matchQuery
|
|
|
type headerMatcher map[string]string
|
|
|
|
|
|
func (m headerMatcher) Match(r *http.Request, match *RouteMatch) bool {
|
|
|
- return matchMap(m, r.Header, true)
|
|
|
+ return matchMapWithString(m, r.Header, true)
|
|
|
}
|
|
|
|
|
|
// Headers adds a matcher for request header values.
|
|
@@ -197,22 +200,46 @@ func (m headerMatcher) Match(r *http.Request, match *RouteMatch) bool {
|
|
|
// "X-Requested-With", "XMLHttpRequest")
|
|
|
//
|
|
|
// The above route will only match if both request header values match.
|
|
|
-//
|
|
|
-// It the value is an empty string, it will match any value if the key is set.
|
|
|
+// If the value is an empty string, it will match any value if the key is set.
|
|
|
func (r *Route) Headers(pairs ...string) *Route {
|
|
|
if r.err == nil {
|
|
|
var headers map[string]string
|
|
|
- headers, r.err = mapFromPairs(pairs...)
|
|
|
+ headers, r.err = mapFromPairsToString(pairs...)
|
|
|
return r.addMatcher(headerMatcher(headers))
|
|
|
}
|
|
|
return r
|
|
|
}
|
|
|
|
|
|
+// headerRegexMatcher matches the request against the route given a regex for the header
|
|
|
+type headerRegexMatcher map[string]*regexp.Regexp
|
|
|
+
|
|
|
+func (m headerRegexMatcher) Match(r *http.Request, match *RouteMatch) bool {
|
|
|
+ return matchMapWithRegex(m, r.Header, true)
|
|
|
+}
|
|
|
+
|
|
|
+// HeadersRegexp accepts a sequence of key/value pairs, where the value has regex
|
|
|
+// support. For example:
|
|
|
+//
|
|
|
+// r := mux.NewRouter()
|
|
|
+// r.HeadersRegexp("Content-Type", "application/(text|json)",
|
|
|
+// "X-Requested-With", "XMLHttpRequest")
|
|
|
+//
|
|
|
+// The above route will only match if both the request header matches both regular expressions.
|
|
|
+// It the value is an empty string, it will match any value if the key is set.
|
|
|
+func (r *Route) HeadersRegexp(pairs ...string) *Route {
|
|
|
+ if r.err == nil {
|
|
|
+ var headers map[string]*regexp.Regexp
|
|
|
+ headers, r.err = mapFromPairsToRegex(pairs...)
|
|
|
+ return r.addMatcher(headerRegexMatcher(headers))
|
|
|
+ }
|
|
|
+ return r
|
|
|
+}
|
|
|
+
|
|
|
// Host -----------------------------------------------------------------------
|
|
|
|
|
|
// Host adds a matcher for the URL host.
|
|
|
// It accepts a template with zero or more URL variables enclosed by {}.
|
|
|
-// Variables can define an optional regexp pattern to me matched:
|
|
|
+// Variables can define an optional regexp pattern to be matched:
|
|
|
//
|
|
|
// - {name} matches anything until the next dot.
|
|
|
//
|
|
@@ -221,7 +248,7 @@ func (r *Route) Headers(pairs ...string) *Route {
|
|
|
// For example:
|
|
|
//
|
|
|
// r := mux.NewRouter()
|
|
|
-// r.Host("www.domain.com")
|
|
|
+// r.Host("www.example.com")
|
|
|
// r.Host("{subdomain}.domain.com")
|
|
|
// r.Host("{subdomain:[a-z]+}.domain.com")
|
|
|
//
|
|
@@ -237,6 +264,7 @@ func (r *Route) Host(tpl string) *Route {
|
|
|
// MatcherFunc is the function signature used by custom matchers.
|
|
|
type MatcherFunc func(*http.Request, *RouteMatch) bool
|
|
|
|
|
|
+// Match returns the match for a given request.
|
|
|
func (m MatcherFunc) Match(r *http.Request, match *RouteMatch) bool {
|
|
|
return m(r, match)
|
|
|
}
|
|
@@ -270,7 +298,7 @@ func (r *Route) Methods(methods ...string) *Route {
|
|
|
// Path adds a matcher for the URL path.
|
|
|
// It accepts a template with zero or more URL variables enclosed by {}. The
|
|
|
// template must start with a "/".
|
|
|
-// Variables can define an optional regexp pattern to me matched:
|
|
|
+// Variables can define an optional regexp pattern to be matched:
|
|
|
//
|
|
|
// - {name} matches anything until the next slash.
|
|
|
//
|
|
@@ -321,7 +349,7 @@ func (r *Route) PathPrefix(tpl string) *Route {
|
|
|
//
|
|
|
// It the value is an empty string, it will match any value if the key is set.
|
|
|
//
|
|
|
-// Variables can define an optional regexp pattern to me matched:
|
|
|
+// Variables can define an optional regexp pattern to be matched:
|
|
|
//
|
|
|
// - {name} matches anything until the next slash.
|
|
|
//
|
|
@@ -334,7 +362,7 @@ func (r *Route) Queries(pairs ...string) *Route {
|
|
|
return nil
|
|
|
}
|
|
|
for i := 0; i < length; i += 2 {
|
|
|
- if r.err = r.addRegexpMatcher(pairs[i]+"="+pairs[i+1], false, true, true); r.err != nil {
|
|
|
+ if r.err = r.addRegexpMatcher(pairs[i]+"="+pairs[i+1], false, false, true); r.err != nil {
|
|
|
return r
|
|
|
}
|
|
|
}
|
|
@@ -360,6 +388,19 @@ func (r *Route) Schemes(schemes ...string) *Route {
|
|
|
return r.addMatcher(schemeMatcher(schemes))
|
|
|
}
|
|
|
|
|
|
+// BuildVarsFunc --------------------------------------------------------------
|
|
|
+
|
|
|
+// BuildVarsFunc is the function signature used by custom build variable
|
|
|
+// functions (which can modify route variables before a route's URL is built).
|
|
|
+type BuildVarsFunc func(map[string]string) map[string]string
|
|
|
+
|
|
|
+// BuildVarsFunc adds a custom function to be used to modify build variables
|
|
|
+// before a route's URL is built.
|
|
|
+func (r *Route) BuildVarsFunc(f BuildVarsFunc) *Route {
|
|
|
+ r.buildVarsFunc = f
|
|
|
+ return r
|
|
|
+}
|
|
|
+
|
|
|
// Subrouter ------------------------------------------------------------------
|
|
|
|
|
|
// Subrouter creates a subrouter for the route.
|
|
@@ -367,7 +408,7 @@ func (r *Route) Schemes(schemes ...string) *Route {
|
|
|
// It will test the inner routes only if the parent route matched. For example:
|
|
|
//
|
|
|
// r := mux.NewRouter()
|
|
|
-// s := r.Host("www.domain.com").Subrouter()
|
|
|
+// s := r.Host("www.example.com").Subrouter()
|
|
|
// s.HandleFunc("/products/", ProductsHandler)
|
|
|
// s.HandleFunc("/products/{key}", ProductHandler)
|
|
|
// s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler)
|
|
@@ -422,17 +463,20 @@ func (r *Route) URL(pairs ...string) (*url.URL, error) {
|
|
|
if r.regexp == nil {
|
|
|
return nil, errors.New("mux: route doesn't have a host or path")
|
|
|
}
|
|
|
+ values, err := r.prepareVars(pairs...)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
var scheme, host, path string
|
|
|
- var err error
|
|
|
if r.regexp.host != nil {
|
|
|
// Set a default scheme.
|
|
|
scheme = "http"
|
|
|
- if host, err = r.regexp.host.url(pairs...); err != nil {
|
|
|
+ if host, err = r.regexp.host.url(values); err != nil {
|
|
|
return nil, err
|
|
|
}
|
|
|
}
|
|
|
if r.regexp.path != nil {
|
|
|
- if path, err = r.regexp.path.url(pairs...); err != nil {
|
|
|
+ if path, err = r.regexp.path.url(values); err != nil {
|
|
|
return nil, err
|
|
|
}
|
|
|
}
|
|
@@ -453,7 +497,11 @@ func (r *Route) URLHost(pairs ...string) (*url.URL, error) {
|
|
|
if r.regexp == nil || r.regexp.host == nil {
|
|
|
return nil, errors.New("mux: route doesn't have a host")
|
|
|
}
|
|
|
- host, err := r.regexp.host.url(pairs...)
|
|
|
+ values, err := r.prepareVars(pairs...)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ host, err := r.regexp.host.url(values)
|
|
|
if err != nil {
|
|
|
return nil, err
|
|
|
}
|
|
@@ -473,7 +521,11 @@ func (r *Route) URLPath(pairs ...string) (*url.URL, error) {
|
|
|
if r.regexp == nil || r.regexp.path == nil {
|
|
|
return nil, errors.New("mux: route doesn't have a path")
|
|
|
}
|
|
|
- path, err := r.regexp.path.url(pairs...)
|
|
|
+ values, err := r.prepareVars(pairs...)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ path, err := r.regexp.path.url(values)
|
|
|
if err != nil {
|
|
|
return nil, err
|
|
|
}
|
|
@@ -482,6 +534,56 @@ func (r *Route) URLPath(pairs ...string) (*url.URL, error) {
|
|
|
}, nil
|
|
|
}
|
|
|
|
|
|
+// GetPathTemplate returns the template used to build the
|
|
|
+// route match.
|
|
|
+// This is useful for building simple REST API documentation and for instrumentation
|
|
|
+// against third-party services.
|
|
|
+// An error will be returned if the route does not define a path.
|
|
|
+func (r *Route) GetPathTemplate() (string, error) {
|
|
|
+ if r.err != nil {
|
|
|
+ return "", r.err
|
|
|
+ }
|
|
|
+ if r.regexp == nil || r.regexp.path == nil {
|
|
|
+ return "", errors.New("mux: route doesn't have a path")
|
|
|
+ }
|
|
|
+ return r.regexp.path.template, nil
|
|
|
+}
|
|
|
+
|
|
|
+// GetHostTemplate returns the template used to build the
|
|
|
+// route match.
|
|
|
+// This is useful for building simple REST API documentation and for instrumentation
|
|
|
+// against third-party services.
|
|
|
+// An error will be returned if the route does not define a host.
|
|
|
+func (r *Route) GetHostTemplate() (string, error) {
|
|
|
+ if r.err != nil {
|
|
|
+ return "", r.err
|
|
|
+ }
|
|
|
+ if r.regexp == nil || r.regexp.host == nil {
|
|
|
+ return "", errors.New("mux: route doesn't have a host")
|
|
|
+ }
|
|
|
+ return r.regexp.host.template, nil
|
|
|
+}
|
|
|
+
|
|
|
+// prepareVars converts the route variable pairs into a map. If the route has a
|
|
|
+// BuildVarsFunc, it is invoked.
|
|
|
+func (r *Route) prepareVars(pairs ...string) (map[string]string, error) {
|
|
|
+ m, err := mapFromPairsToString(pairs...)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ return r.buildVars(m), nil
|
|
|
+}
|
|
|
+
|
|
|
+func (r *Route) buildVars(m map[string]string) map[string]string {
|
|
|
+ if r.parent != nil {
|
|
|
+ m = r.parent.buildVars(m)
|
|
|
+ }
|
|
|
+ if r.buildVarsFunc != nil {
|
|
|
+ m = r.buildVarsFunc(m)
|
|
|
+ }
|
|
|
+ return m
|
|
|
+}
|
|
|
+
|
|
|
// ----------------------------------------------------------------------------
|
|
|
// parentRoute
|
|
|
// ----------------------------------------------------------------------------
|
|
@@ -490,6 +592,7 @@ func (r *Route) URLPath(pairs ...string) (*url.URL, error) {
|
|
|
type parentRoute interface {
|
|
|
getNamedRoutes() map[string]*Route
|
|
|
getRegexpGroup() *routeRegexpGroup
|
|
|
+ buildVars(map[string]string) map[string]string
|
|
|
}
|
|
|
|
|
|
// getNamedRoutes returns the map where named routes are registered.
|