New rule syntax
Co-authored-by: jbdoumenjou <jb.doumenjou@gmail.com>
This commit is contained in:
parent
7155f0d50d
commit
9ebe3c38b2
89 changed files with 1111 additions and 1357 deletions
|
@ -6,13 +6,13 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/containous/alice"
|
||||
"github.com/containous/mux"
|
||||
"github.com/containous/traefik/config"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/middlewares/accesslog"
|
||||
"github.com/containous/traefik/middlewares/recovery"
|
||||
"github.com/containous/traefik/middlewares/tracing"
|
||||
"github.com/containous/traefik/responsemodifiers"
|
||||
"github.com/containous/traefik/rules"
|
||||
"github.com/containous/traefik/server/internal"
|
||||
"github.com/containous/traefik/server/middleware"
|
||||
"github.com/containous/traefik/server/service"
|
||||
|
@ -50,6 +50,7 @@ func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string) m
|
|||
|
||||
entryPointHandlers := make(map[string]http.Handler)
|
||||
for entryPointName, routers := range entryPointsRouters {
|
||||
entryPointName := entryPointName
|
||||
ctx := log.With(rootCtx, log.Str(log.EntryPointName, entryPointName))
|
||||
|
||||
handler, err := m.buildEntryPointHandler(ctx, routers)
|
||||
|
@ -110,22 +111,24 @@ func (m *Manager) filteredRouters(ctx context.Context, entryPoints []string) map
|
|||
}
|
||||
|
||||
func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string]*config.Router) (http.Handler, error) {
|
||||
router := mux.NewRouter().
|
||||
SkipClean(true)
|
||||
router, err := rules.NewRouter()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for routerName, routerConfig := range configs {
|
||||
ctx := log.With(ctx, log.Str(log.RouterName, routerName))
|
||||
logger := log.FromContext(ctx)
|
||||
ctxRouter := log.With(ctx, log.Str(log.RouterName, routerName))
|
||||
logger := log.FromContext(ctxRouter)
|
||||
|
||||
ctx = internal.AddProviderInContext(ctx, routerName)
|
||||
ctxRouter = internal.AddProviderInContext(ctxRouter, routerName)
|
||||
|
||||
handler, err := m.buildRouterHandler(ctx, routerName)
|
||||
handler, err := m.buildRouterHandler(ctxRouter, routerName)
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
err = addRoute(ctx, router, routerConfig.Rule, routerConfig.Priority, handler)
|
||||
err = router.AddRoute(routerConfig.Rule, routerConfig.Priority, handler)
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
continue
|
||||
|
|
|
@ -40,7 +40,7 @@ func TestRouterManager_Get(t *testing.T) {
|
|||
"foo": {
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service",
|
||||
Rule: "Host:foo.bar",
|
||||
Rule: "Host(`foo.bar`)",
|
||||
},
|
||||
},
|
||||
serviceConfig: map[string]*config.Service{
|
||||
|
@ -65,7 +65,7 @@ func TestRouterManager_Get(t *testing.T) {
|
|||
"foo": {
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service",
|
||||
Rule: "Host:foo.bar",
|
||||
Rule: "Host(`foo.bar`)",
|
||||
},
|
||||
},
|
||||
serviceConfig: map[string]*config.Service{
|
||||
|
@ -79,7 +79,7 @@ func TestRouterManager_Get(t *testing.T) {
|
|||
routersConfig: map[string]*config.Router{
|
||||
"foo": {
|
||||
Service: "foo-service",
|
||||
Rule: "Host:foo.bar",
|
||||
Rule: "Host(`foo.bar`)",
|
||||
},
|
||||
},
|
||||
serviceConfig: map[string]*config.Service{
|
||||
|
@ -104,7 +104,7 @@ func TestRouterManager_Get(t *testing.T) {
|
|||
"foo": {
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service",
|
||||
Rule: "Host:bar.bar",
|
||||
Rule: "Host(`bar.bar`)",
|
||||
},
|
||||
},
|
||||
serviceConfig: map[string]*config.Service{
|
||||
|
@ -130,7 +130,7 @@ func TestRouterManager_Get(t *testing.T) {
|
|||
EntryPoints: []string{"web"},
|
||||
Middlewares: []string{"headers-middle", "auth-middle"},
|
||||
Service: "foo-service",
|
||||
Rule: "Host:foo.bar",
|
||||
Rule: "Host(`foo.bar`)",
|
||||
},
|
||||
},
|
||||
serviceConfig: map[string]*config.Service{
|
||||
|
@ -173,7 +173,7 @@ func TestRouterManager_Get(t *testing.T) {
|
|||
EntryPoints: []string{"web"},
|
||||
Middlewares: []string{"auth-middle", "headers-middle"},
|
||||
Service: "foo-service",
|
||||
Rule: "Host:foo.bar",
|
||||
Rule: "Host(`foo.bar`)",
|
||||
},
|
||||
},
|
||||
serviceConfig: map[string]*config.Service{
|
||||
|
@ -215,7 +215,7 @@ func TestRouterManager_Get(t *testing.T) {
|
|||
"provider-1.foo": {
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service",
|
||||
Rule: "Host:foo.bar",
|
||||
Rule: "Host(`foo.bar`)",
|
||||
},
|
||||
},
|
||||
serviceConfig: map[string]*config.Service{
|
||||
|
@ -240,7 +240,7 @@ func TestRouterManager_Get(t *testing.T) {
|
|||
"provider-1.foo": {
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "provider-2.foo-service",
|
||||
Rule: "Host:foo.bar",
|
||||
Rule: "Host(`foo.bar`)",
|
||||
},
|
||||
},
|
||||
serviceConfig: map[string]*config.Service{
|
||||
|
@ -266,7 +266,7 @@ func TestRouterManager_Get(t *testing.T) {
|
|||
EntryPoints: []string{"web"},
|
||||
Middlewares: []string{"provider-2.chain-middle", "headers-middle"},
|
||||
Service: "foo-service",
|
||||
Rule: "Host:foo.bar",
|
||||
Rule: "Host(`foo.bar`)",
|
||||
},
|
||||
},
|
||||
serviceConfig: map[string]*config.Service{
|
||||
|
@ -339,13 +339,12 @@ func TestAccessLog(t *testing.T) {
|
|||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
routersConfig map[string]*config.Router
|
||||
serviceConfig map[string]*config.Service
|
||||
middlewaresConfig map[string]*config.Middleware
|
||||
entryPoints []string
|
||||
defaultEntryPoints []string
|
||||
expected string
|
||||
desc string
|
||||
routersConfig map[string]*config.Router
|
||||
serviceConfig map[string]*config.Service
|
||||
middlewaresConfig map[string]*config.Middleware
|
||||
entryPoints []string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
desc: "apply routerName in accesslog (first match)",
|
||||
|
@ -353,12 +352,12 @@ func TestAccessLog(t *testing.T) {
|
|||
"foo": {
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service",
|
||||
Rule: "Host:foo.bar",
|
||||
Rule: "Host(`foo.bar`)",
|
||||
},
|
||||
"bar": {
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service",
|
||||
Rule: "Host:bar.foo",
|
||||
Rule: "Host(`bar.foo`)",
|
||||
},
|
||||
},
|
||||
serviceConfig: map[string]*config.Service{
|
||||
|
@ -383,12 +382,12 @@ func TestAccessLog(t *testing.T) {
|
|||
"foo": {
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service",
|
||||
Rule: "Host:bar.foo",
|
||||
Rule: "Host(`bar.foo`)",
|
||||
},
|
||||
"bar": {
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service",
|
||||
Rule: "Host:foo.bar",
|
||||
Rule: "Host(`foo.bar`)",
|
||||
},
|
||||
},
|
||||
serviceConfig: map[string]*config.Service{
|
||||
|
|
|
@ -1,167 +0,0 @@
|
|||
package router
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/containous/mux"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/middlewares/requestdecorator"
|
||||
)
|
||||
|
||||
func addRoute(ctx context.Context, router *mux.Router, rule string, priority int, handler http.Handler) error {
|
||||
matchers, err := parseRule(rule)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(matchers) == 0 {
|
||||
return fmt.Errorf("invalid rule: %s", rule)
|
||||
}
|
||||
|
||||
if priority == 0 {
|
||||
priority = len(rule)
|
||||
}
|
||||
|
||||
route := router.NewRoute().Handler(handler).Priority(priority)
|
||||
for _, matcher := range matchers {
|
||||
matcher(route)
|
||||
if route.GetError() != nil {
|
||||
log.FromContext(ctx).Error(route.GetError())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseRule(rule string) ([]func(*mux.Route), error) {
|
||||
funcs := map[string]func(*mux.Route, ...string){
|
||||
"Host": host,
|
||||
"HostRegexp": hostRegexp,
|
||||
"Path": path,
|
||||
"PathPrefix": pathPrefix,
|
||||
"Method": methods,
|
||||
"Headers": headers,
|
||||
"HeadersRegexp": headersRegexp,
|
||||
"Query": query,
|
||||
}
|
||||
|
||||
splitRule := func(c rune) bool {
|
||||
return c == ';'
|
||||
}
|
||||
parsedRules := strings.FieldsFunc(rule, splitRule)
|
||||
|
||||
var matchers []func(*mux.Route)
|
||||
|
||||
for _, expression := range parsedRules {
|
||||
expParts := strings.Split(expression, ":")
|
||||
if len(expParts) > 1 && len(expParts[1]) > 0 {
|
||||
if fn, ok := funcs[expParts[0]]; ok {
|
||||
|
||||
parseOr := func(c rune) bool {
|
||||
return c == ','
|
||||
}
|
||||
|
||||
exp := strings.FieldsFunc(strings.Join(expParts[1:], ":"), parseOr)
|
||||
|
||||
var trimmedExp []string
|
||||
for _, value := range exp {
|
||||
trimmedExp = append(trimmedExp, strings.TrimSpace(value))
|
||||
}
|
||||
|
||||
// FIXME struct for onhostrule ?
|
||||
matcher := func(rt *mux.Route) {
|
||||
fn(rt, trimmedExp...)
|
||||
}
|
||||
|
||||
matchers = append(matchers, matcher)
|
||||
} else {
|
||||
return nil, fmt.Errorf("invalid matcher: %s", expression)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return matchers, nil
|
||||
}
|
||||
|
||||
func path(route *mux.Route, paths ...string) {
|
||||
rt := route.Subrouter()
|
||||
for _, path := range paths {
|
||||
tmpRt := rt.Path(path)
|
||||
if tmpRt.GetError() != nil {
|
||||
log.WithoutContext().WithField("paths", strings.Join(paths, ",")).Error(tmpRt.GetError())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func pathPrefix(route *mux.Route, paths ...string) {
|
||||
rt := route.Subrouter()
|
||||
for _, path := range paths {
|
||||
tmpRt := rt.PathPrefix(path)
|
||||
if tmpRt.GetError() != nil {
|
||||
log.WithoutContext().WithField("paths", strings.Join(paths, ",")).Error(tmpRt.GetError())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func host(route *mux.Route, hosts ...string) {
|
||||
for i, host := range hosts {
|
||||
hosts[i] = strings.ToLower(host)
|
||||
}
|
||||
|
||||
route.MatcherFunc(func(req *http.Request, route *mux.RouteMatch) bool {
|
||||
reqHost := requestdecorator.GetCanonizedHost(req.Context())
|
||||
if len(reqHost) == 0 {
|
||||
log.FromContext(req.Context()).Warnf("Could not retrieve CanonizedHost, rejecting %s", req.Host)
|
||||
return false
|
||||
}
|
||||
|
||||
flatH := requestdecorator.GetCNAMEFlatten(req.Context())
|
||||
if len(flatH) > 0 {
|
||||
for _, host := range hosts {
|
||||
if strings.EqualFold(reqHost, host) || strings.EqualFold(flatH, host) {
|
||||
return true
|
||||
}
|
||||
log.FromContext(req.Context()).Debugf("CNAMEFlattening: request %s which resolved to %s, is not matched to route %s", reqHost, flatH, host)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
for _, host := range hosts {
|
||||
if reqHost == host {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
func hostRegexp(route *mux.Route, hosts ...string) {
|
||||
router := route.Subrouter()
|
||||
for _, host := range hosts {
|
||||
router.Host(host)
|
||||
}
|
||||
}
|
||||
|
||||
func methods(route *mux.Route, methods ...string) {
|
||||
route.Methods(methods...)
|
||||
}
|
||||
|
||||
func headers(route *mux.Route, headers ...string) {
|
||||
route.Headers(headers...)
|
||||
}
|
||||
|
||||
func headersRegexp(route *mux.Route, headers ...string) {
|
||||
route.HeadersRegexp(headers...)
|
||||
}
|
||||
|
||||
func query(route *mux.Route, query ...string) {
|
||||
var queries []string
|
||||
for _, elem := range query {
|
||||
queries = append(queries, strings.Split(elem, "=")...)
|
||||
}
|
||||
|
||||
route.Queries(queries...)
|
||||
}
|
|
@ -1,449 +0,0 @@
|
|||
package router
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/containous/mux"
|
||||
"github.com/containous/traefik/middlewares/requestdecorator"
|
||||
"github.com/containous/traefik/testhelpers"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_addRoute(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
rule string
|
||||
headers map[string]string
|
||||
expected map[string]int
|
||||
expectedError bool
|
||||
}{
|
||||
{
|
||||
desc: "no rule",
|
||||
expectedError: true,
|
||||
},
|
||||
{
|
||||
desc: "Rule with no matcher",
|
||||
rule: "rulewithnotmatcher",
|
||||
expectedError: true,
|
||||
},
|
||||
{
|
||||
desc: "PathPrefix",
|
||||
rule: "PathPrefix:/foo",
|
||||
expected: map[string]int{
|
||||
"http://localhost/foo": http.StatusOK,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "wrong PathPrefix",
|
||||
rule: "PathPrefix:/bar",
|
||||
expected: map[string]int{
|
||||
"http://localhost/foo": http.StatusNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Host",
|
||||
rule: "Host:localhost",
|
||||
expected: map[string]int{
|
||||
"http://localhost/foo": http.StatusOK,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "wrong Host",
|
||||
rule: "Host:nope",
|
||||
expected: map[string]int{
|
||||
"http://localhost/foo": http.StatusNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Host and PathPrefix",
|
||||
rule: "Host:localhost;PathPrefix:/foo",
|
||||
expected: map[string]int{
|
||||
"http://localhost/foo": http.StatusOK,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Host and PathPrefix: wrong PathPrefix",
|
||||
rule: "Host:localhost;PathPrefix:/bar",
|
||||
expected: map[string]int{
|
||||
"http://localhost/foo": http.StatusNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Host and PathPrefix: wrong Host",
|
||||
rule: "Host:nope;PathPrefix:/bar",
|
||||
expected: map[string]int{
|
||||
"http://localhost/foo": http.StatusNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Host and PathPrefix: Host OR, first host",
|
||||
rule: "Host:nope,localhost;PathPrefix:/foo",
|
||||
expected: map[string]int{
|
||||
"http://localhost/foo": http.StatusOK,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Host and PathPrefix: Host OR, second host",
|
||||
rule: "Host:nope,localhost;PathPrefix:/foo",
|
||||
expected: map[string]int{
|
||||
"http://nope/foo": http.StatusOK,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Host and PathPrefix: Host OR, first host and wrong PathPrefix",
|
||||
rule: "Host:nope,localhost;PathPrefix:/bar",
|
||||
expected: map[string]int{
|
||||
"http://localhost/foo": http.StatusNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "HostRegexp with capturing group",
|
||||
rule: "HostRegexp: {subdomain:(foo\\.)?bar\\.com}",
|
||||
expected: map[string]int{
|
||||
"http://foo.bar.com": http.StatusOK,
|
||||
"http://bar.com": http.StatusOK,
|
||||
"http://fooubar.com": http.StatusNotFound,
|
||||
"http://barucom": http.StatusNotFound,
|
||||
"http://barcom": http.StatusNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "HostRegexp with non capturing group",
|
||||
rule: "HostRegexp: {subdomain:(?:foo\\.)?bar\\.com}",
|
||||
expected: map[string]int{
|
||||
"http://foo.bar.com": http.StatusOK,
|
||||
"http://bar.com": http.StatusOK,
|
||||
"http://fooubar.com": http.StatusNotFound,
|
||||
"http://barucom": http.StatusNotFound,
|
||||
"http://barcom": http.StatusNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Methods with GET",
|
||||
rule: "Method: GET",
|
||||
expected: map[string]int{
|
||||
"http://localhost/foo": http.StatusOK,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Methods with GET and POST",
|
||||
rule: "Method: GET,POST",
|
||||
expected: map[string]int{
|
||||
"http://localhost/foo": http.StatusOK,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Methods with POST",
|
||||
rule: "Method: POST",
|
||||
expected: map[string]int{
|
||||
"http://localhost/foo": http.StatusMethodNotAllowed,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Header with matching header",
|
||||
rule: "Headers: Content-Type,application/json",
|
||||
headers: map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
expected: map[string]int{
|
||||
"http://localhost/foo": http.StatusOK,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Header without matching header",
|
||||
rule: "Headers: Content-Type,application/foo",
|
||||
headers: map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
expected: map[string]int{
|
||||
"http://localhost/foo": http.StatusNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "HeaderRegExp with matching header",
|
||||
rule: "HeadersRegexp: Content-Type, application/(text|json)",
|
||||
headers: map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
expected: map[string]int{
|
||||
"http://localhost/foo": http.StatusOK,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "HeaderRegExp without matching header",
|
||||
rule: "HeadersRegexp: Content-Type, application/(text|json)",
|
||||
headers: map[string]string{
|
||||
"Content-Type": "application/foo",
|
||||
},
|
||||
expected: map[string]int{
|
||||
"http://localhost/foo": http.StatusNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "HeaderRegExp with matching second header",
|
||||
rule: "HeadersRegexp: Content-Type, application/(text|json)",
|
||||
headers: map[string]string{
|
||||
"Content-Type": "application/text",
|
||||
},
|
||||
expected: map[string]int{
|
||||
"http://localhost/foo": http.StatusOK,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Query with multiple params",
|
||||
rule: "Query: foo=bar, bar=baz",
|
||||
expected: map[string]int{
|
||||
"http://localhost/foo?foo=bar&bar=baz": http.StatusOK,
|
||||
"http://localhost/foo?bar=baz": http.StatusNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Invalid rule syntax",
|
||||
rule: "Query:param_one=true, /path2;Path: /path1",
|
||||
expected: map[string]int{
|
||||
"http://localhost/foo?bar=baz": http.StatusNotFound,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
|
||||
router := mux.NewRouter()
|
||||
router.SkipClean(true)
|
||||
|
||||
err := addRoute(context.Background(), router, test.rule, 0, handler)
|
||||
if test.expectedError {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
|
||||
// RequestDecorator is necessary for the host rule
|
||||
reqHost := requestdecorator.New(nil)
|
||||
|
||||
results := make(map[string]int)
|
||||
for calledURL := range test.expected {
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
req := testhelpers.MustNewRequest(http.MethodGet, calledURL, nil)
|
||||
for key, value := range test.headers {
|
||||
req.Header.Set(key, value)
|
||||
}
|
||||
reqHost.ServeHTTP(w, req, router.ServeHTTP)
|
||||
results[calledURL] = w.Code
|
||||
}
|
||||
assert.Equal(t, test.expected, results)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_addRoutePriority(t *testing.T) {
|
||||
type Case struct {
|
||||
xFrom string
|
||||
rule string
|
||||
priority int
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
path string
|
||||
cases []Case
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
desc: "Higher priority on second rule",
|
||||
path: "/my",
|
||||
cases: []Case{
|
||||
{
|
||||
xFrom: "header1",
|
||||
rule: "PathPrefix:/my",
|
||||
priority: 10,
|
||||
},
|
||||
{
|
||||
xFrom: "header2",
|
||||
rule: "PathPrefix:/my",
|
||||
priority: 20,
|
||||
},
|
||||
},
|
||||
expected: "header2",
|
||||
},
|
||||
{
|
||||
desc: "Higher priority on first rule",
|
||||
path: "/my",
|
||||
cases: []Case{
|
||||
{
|
||||
xFrom: "header1",
|
||||
rule: "PathPrefix:/my",
|
||||
priority: 20,
|
||||
},
|
||||
{
|
||||
xFrom: "header2",
|
||||
rule: "PathPrefix:/my",
|
||||
priority: 10,
|
||||
},
|
||||
},
|
||||
expected: "header1",
|
||||
},
|
||||
{
|
||||
desc: "Higher priority on second rule with different rule",
|
||||
path: "/mypath",
|
||||
cases: []Case{
|
||||
{
|
||||
xFrom: "header1",
|
||||
rule: "PathPrefix:/mypath",
|
||||
priority: 10,
|
||||
},
|
||||
{
|
||||
xFrom: "header2",
|
||||
rule: "PathPrefix:/my",
|
||||
priority: 20,
|
||||
},
|
||||
},
|
||||
expected: "header2",
|
||||
},
|
||||
{
|
||||
desc: "Higher priority on longest rule (longest first)",
|
||||
path: "/mypath",
|
||||
cases: []Case{
|
||||
{
|
||||
xFrom: "header1",
|
||||
rule: "PathPrefix:/mypath",
|
||||
},
|
||||
{
|
||||
xFrom: "header2",
|
||||
rule: "PathPrefix:/my",
|
||||
},
|
||||
},
|
||||
expected: "header1",
|
||||
},
|
||||
{
|
||||
desc: "Higher priority on longest rule (longest second)",
|
||||
path: "/mypath",
|
||||
cases: []Case{
|
||||
{
|
||||
xFrom: "header1",
|
||||
rule: "PathPrefix:/my",
|
||||
},
|
||||
{
|
||||
xFrom: "header2",
|
||||
rule: "PathPrefix:/mypath",
|
||||
},
|
||||
},
|
||||
expected: "header2",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
router := mux.NewRouter()
|
||||
|
||||
for _, route := range test.cases {
|
||||
route := route
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("X-From", route.xFrom)
|
||||
})
|
||||
err := addRoute(context.Background(), router, route.rule, route.priority, handler)
|
||||
require.NoError(t, err, route)
|
||||
}
|
||||
|
||||
router.SortRoutes()
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req := testhelpers.MustNewRequest(http.MethodGet, test.path, nil)
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, test.expected, w.Header().Get("X-From"))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHostRegexp(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
hostExp string
|
||||
urls map[string]bool
|
||||
}{
|
||||
{
|
||||
desc: "capturing group",
|
||||
hostExp: "{subdomain:(foo\\.)?bar\\.com}",
|
||||
urls: map[string]bool{
|
||||
"http://foo.bar.com": true,
|
||||
"http://bar.com": true,
|
||||
"http://fooubar.com": false,
|
||||
"http://barucom": false,
|
||||
"http://barcom": false,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "non capturing group",
|
||||
hostExp: "{subdomain:(?:foo\\.)?bar\\.com}",
|
||||
urls: map[string]bool{
|
||||
"http://foo.bar.com": true,
|
||||
"http://bar.com": true,
|
||||
"http://fooubar.com": false,
|
||||
"http://barucom": false,
|
||||
"http://barcom": false,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "regex insensitive",
|
||||
hostExp: "{dummy:[A-Za-z-]+\\.bar\\.com}",
|
||||
urls: map[string]bool{
|
||||
"http://FOO.bar.com": true,
|
||||
"http://foo.bar.com": true,
|
||||
"http://fooubar.com": false,
|
||||
"http://barucom": false,
|
||||
"http://barcom": false,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "insensitive host",
|
||||
hostExp: "{dummy:[a-z-]+\\.bar\\.com}",
|
||||
urls: map[string]bool{
|
||||
"http://FOO.bar.com": true,
|
||||
"http://foo.bar.com": true,
|
||||
"http://fooubar.com": false,
|
||||
"http://barucom": false,
|
||||
"http://barcom": false,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "insensitive host simple",
|
||||
hostExp: "foo.bar.com",
|
||||
urls: map[string]bool{
|
||||
"http://FOO.bar.com": true,
|
||||
"http://foo.bar.com": true,
|
||||
"http://fooubar.com": false,
|
||||
"http://barucom": false,
|
||||
"http://barcom": false,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
rt := &mux.Route{}
|
||||
hostRegexp(rt, test.hostExp)
|
||||
|
||||
for testURL, match := range test.urls {
|
||||
req := testhelpers.MustNewRequest(http.MethodGet, testURL, nil)
|
||||
assert.Equal(t, match, rt.Match(req, &mux.RouteMatch{}), testURL)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue