Browse Source

Fix typo

Issue #16: Added regex support for matching headers

Issue #16 : Addressed code review and refactored support for regex into
a separate function

Added compiled regex to route matcher
pull/100/head
Craig Jellick 11 years ago committed by Clint Ryan
parent
commit
a063f14812
  1. 7
      doc.go
  2. 60
      mux.go
  3. 18
      mux_test.go
  4. 44
      route.go

7
doc.go

@ -172,6 +172,13 @@ conform to the corresponding patterns. These requirements guarantee that a
generated URL will always match a registered route -- the only exception is generated URL will always match a registered route -- the only exception is
for explicitly defined "build-only" routes which never match. for explicitly defined "build-only" routes which never match.
Regex support also exists for matching Headers within a route. For example, we could do:
r.HeadersRegexp("Content-Type", "application/(text|json)")
...and the route will match both requests with a Content-Type of `application/json` as well as
`application/text`
There's also a way to build only the URL host or path for a route: There's also a way to build only the URL host or path for a route:
use the methods URLHost() or URLPath() instead. For the previous route, use the methods URLHost() or URLPath() instead. For the previous route,
we would do: we would do:

60
mux.go

@ -8,6 +8,7 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"path" "path"
"regexp"
"github.com/gorilla/context" "github.com/gorilla/context"
) )
@ -313,13 +314,21 @@ func uniqueVars(s1, s2 []string) error {
return nil return nil
} }
// mapFromPairs converts variadic string parameters to a string map. func checkPairs(pairs ...string) (int, error) {
func mapFromPairs(pairs ...string) (map[string]string, error) {
length := len(pairs) length := len(pairs)
if length%2 != 0 { if length%2 != 0 {
return nil, fmt.Errorf( return length, fmt.Errorf(
"mux: number of parameters must be multiple of 2, got %v", pairs) "mux: number of parameters must be multiple of 2, got %v", pairs)
} }
return length, nil
}
// mapFromPairs converts variadic string parameters to a string map.
func mapFromPairsToString(pairs ...string) (map[string]string, error) {
length, err := checkPairs(pairs...)
if err != nil {
return nil, err
}
m := make(map[string]string, length/2) m := make(map[string]string, length/2)
for i := 0; i < length; i += 2 { for i := 0; i < length; i += 2 {
m[pairs[i]] = pairs[i+1] m[pairs[i]] = pairs[i+1]
@ -327,6 +336,19 @@ func mapFromPairs(pairs ...string) (map[string]string, error) {
return m, nil return m, nil
} }
func mapFromPairsToRegex(pairs ...string) (map[string]*regexp.Regexp, error) {
length, err := checkPairs(pairs...)
if err != nil {
return nil, err
}
m := make(map[string]*regexp.Regexp, length/2)
for i := 0; i < length; i += 2 {
regex, _ := regexp.Compile(pairs[i+1])
m[pairs[i]] = regex
}
return m, nil
}
// matchInArray returns true if the given string value is in the array. // matchInArray returns true if the given string value is in the array.
func matchInArray(arr []string, value string) bool { func matchInArray(arr []string, value string) bool {
for _, v := range arr { for _, v := range arr {
@ -337,9 +359,10 @@ func matchInArray(arr []string, value string) bool {
return false return false
} }
type equals func(interface{}, interface{}) bool
// matchMap returns true if the given key/value pairs exist in a given map. // matchMap returns true if the given key/value pairs exist in a given map.
func matchMap(toCheck map[string]string, toMatch map[string][]string, func matchMapWithString(toCheck map[string]string, toMatch map[string][]string, canonicalKey bool) bool {
canonicalKey bool) bool {
for k, v := range toCheck { for k, v := range toCheck {
// Check if key exists. // Check if key exists.
if canonicalKey { if canonicalKey {
@ -364,3 +387,30 @@ func matchMap(toCheck map[string]string, toMatch map[string][]string,
} }
return true return true
} }
// matchMap returns true if the given key/value pairs exist in a given map.
func matchMapWithRegex(toCheck map[string]*regexp.Regexp, toMatch map[string][]string, canonicalKey bool) bool {
for k, v := range toCheck {
// Check if key exists.
if canonicalKey {
k = http.CanonicalHeaderKey(k)
}
if values := toMatch[k]; values == nil {
return false
} else if v != nil {
// If value was defined as an empty string we only check that the
// key exists. Otherwise we also check for equality.
valueExists := false
for _, value := range values {
if v.MatchString(value) {
valueExists = true
break
}
}
if !valueExists {
return false
}
}
}
return true
}

18
mux_test.go

@ -434,6 +434,24 @@ func TestHeaders(t *testing.T) {
path: "", path: "",
shouldMatch: false, shouldMatch: false,
}, },
{
title: "Headers route, regex header values to match",
route: new(Route).Headers("foo", "ba[zr]"),
request: newRequestHeaders("GET", "http://localhost", map[string]string{"foo": "bar"}),
vars: map[string]string{},
host: "",
path: "",
shouldMatch: false,
},
{
title: "Headers route, regex header values to match",
route: new(Route).HeadersRegexp("foo", "ba[zr]"),
request: newRequestHeaders("GET", "http://localhost", map[string]string{"foo": "baz"}),
vars: map[string]string{},
host: "",
path: "",
shouldMatch: true,
},
} }
for _, test := range tests { for _, test := range tests {

44
route.go

@ -9,6 +9,7 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"net/url" "net/url"
"regexp"
"strings" "strings"
) )
@ -188,7 +189,7 @@ func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix, matchQuery
type headerMatcher map[string]string type headerMatcher map[string]string
func (m headerMatcher) Match(r *http.Request, match *RouteMatch) bool { 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. // Headers adds a matcher for request header values.
@ -199,22 +200,53 @@ func (m headerMatcher) Match(r *http.Request, match *RouteMatch) bool {
// "X-Requested-With", "XMLHttpRequest") // "X-Requested-With", "XMLHttpRequest")
// //
// The above route will only match if both request header values match. // The above route will only match if both request header values match.
// Alternatively, you can provide a regular expression and match the header as follows:
//
// r.Headers("Content-Type", "application/(text|json)",
// "X-Requested-With", "XMLHttpRequest")
//
// The above route will the same as the previous example, with the addition of matching
// application/text as well.
// //
// It the value is an empty string, it will match any value if the key is set. // It the value is an empty string, it will match any value if the key is set.
func (r *Route) Headers(pairs ...string) *Route { func (r *Route) Headers(pairs ...string) *Route {
if r.err == nil { if r.err == nil {
var headers map[string]string var headers map[string]string
headers, r.err = mapFromPairs(pairs...) headers, r.err = mapFromPairsToString(pairs...)
return r.addMatcher(headerMatcher(headers)) return r.addMatcher(headerMatcher(headers))
} }
return r 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)
}
// Regular expressions can be used with headers as well.
// It 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 -----------------------------------------------------------------------
// Host adds a matcher for the URL host. // Host adds a matcher for the URL host.
// It accepts a template with zero or more URL variables enclosed by {}. // 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. // - {name} matches anything until the next dot.
// //
@ -272,7 +304,7 @@ func (r *Route) Methods(methods ...string) *Route {
// Path adds a matcher for the URL path. // Path adds a matcher for the URL path.
// It accepts a template with zero or more URL variables enclosed by {}. The // It accepts a template with zero or more URL variables enclosed by {}. The
// template must start with a "/". // 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. // - {name} matches anything until the next slash.
// //
@ -323,7 +355,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. // 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. // - {name} matches anything until the next slash.
// //
@ -511,7 +543,7 @@ func (r *Route) URLPath(pairs ...string) (*url.URL, error) {
// prepareVars converts the route variable pairs into a map. If the route has a // prepareVars converts the route variable pairs into a map. If the route has a
// BuildVarsFunc, it is invoked. // BuildVarsFunc, it is invoked.
func (r *Route) prepareVars(pairs ...string) (map[string]string, error) { func (r *Route) prepareVars(pairs ...string) (map[string]string, error) {
m, err := mapFromPairs(pairs...) m, err := mapFromPairsToString(pairs...)
if err != nil { if err != nil {
return nil, err return nil, err
} }

Loading…
Cancel
Save