diff --git a/api/dashboard.go b/api/dashboard.go index 059a9eaf2..b5529c7cd 100644 --- a/api/dashboard.go +++ b/api/dashboard.go @@ -24,13 +24,13 @@ func (g DashboardHandler) Append(router *mux.Router) { router.Methods(http.MethodGet). Path("/"). HandlerFunc(func(response http.ResponseWriter, request *http.Request) { - http.Redirect(response, request, request.Header.Get("X-Forwarded-Prefix")+"/dashboard/", 302) + http.Redirect(response, request, request.Header.Get("X-Forwarded-Prefix")+"/dashboard/", http.StatusFound) }) router.Methods(http.MethodGet). Path("/dashboard/status"). HandlerFunc(func(response http.ResponseWriter, request *http.Request) { - http.Redirect(response, request, "/dashboard/", 302) + http.Redirect(response, request, "/dashboard/", http.StatusFound) }) router.Methods(http.MethodGet). diff --git a/cmd/configuration.go b/cmd/configuration.go index e7fa001e6..81f02d2ed 100644 --- a/cmd/configuration.go +++ b/cmd/configuration.go @@ -144,7 +144,7 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration { }, } - defaultResolver := static.HostResolverConfig{ + defaultResolver := types.HostResolverConfig{ CnameFlattening: false, ResolvConfig: "/etc/resolv.conf", ResolvDepth: 5, diff --git a/config/static/static_config.go b/config/static/static_config.go index 01a3ad746..f23431ee0 100644 --- a/config/static/static_config.go +++ b/config/static/static_config.go @@ -66,7 +66,7 @@ type Configuration struct { AccessLog *types.AccessLog `description:"Access log settings" export:"true"` Tracing *Tracing `description:"OpenTracing configuration" export:"true"` - HostResolver *HostResolverConfig `description:"Enable CNAME Flattening" export:"true"` + HostResolver *types.HostResolverConfig `description:"Enable CNAME Flattening" export:"true"` ACME *acme.ACME `description:"Enable ACME (Let's Encrypt): automatic SSL" export:"true"` } @@ -124,13 +124,6 @@ type Tracing struct { DataDog *datadog.Config `description:"Settings for DataDog"` } -// HostResolverConfig contain configuration for CNAME Flattening. -type HostResolverConfig struct { - CnameFlattening bool `description:"A flag to enable/disable CNAME flattening" export:"true"` - ResolvConfig string `description:"resolv.conf used for DNS resolving" export:"true"` - ResolvDepth int `description:"The maximal depth of DNS recursive resolving" export:"true"` -} - // Providers contains providers configuration type Providers struct { ProvidersThrottleDuration parse.Duration `description:"Backends throttle duration: minimum duration between 2 events from providers before applying a new configuration. It avoids unnecessary reloads if multiples events are sent in a short amount of time." export:"true"` diff --git a/integration/docker_compose_test.go b/integration/docker_compose_test.go index e6dfd3e90..1fef054bd 100644 --- a/integration/docker_compose_test.go +++ b/integration/docker_compose_test.go @@ -45,7 +45,7 @@ func (s *DockerComposeSuite) TestComposeScale(c *check.C) { DefaultRule string }{ DockerHost: s.getDockerHost(), - DefaultRule: "Host:{{ normalize .Name }}.docker.localhost", + DefaultRule: "Host(`{{ normalize .Name }}.docker.localhost`)", } file := s.adaptFile(c, "fixtures/docker/minimal.toml", tempObjects) defer os.Remove(file) diff --git a/integration/docker_test.go b/integration/docker_test.go index ac421eb82..36a0f224f 100644 --- a/integration/docker_test.go +++ b/integration/docker_test.go @@ -88,7 +88,7 @@ func (s *DockerSuite) TestSimpleConfiguration(c *check.C) { DefaultRule string }{ DockerHost: s.getDockerHost(), - DefaultRule: "Host:{{ normalize .Name }}.docker.localhost", + DefaultRule: "Host(`{{ normalize .Name }}.docker.localhost`)", } file := s.adaptFile(c, "fixtures/docker/simple.toml", tempObjects) @@ -112,7 +112,7 @@ func (s *DockerSuite) TestDefaultDockerContainers(c *check.C) { DefaultRule string }{ DockerHost: s.getDockerHost(), - DefaultRule: "Host:{{ normalize .Name }}.docker.localhost", + DefaultRule: "Host(`{{ normalize .Name }}.docker.localhost`)", } file := s.adaptFile(c, "fixtures/docker/simple.toml", tempObjects) @@ -150,7 +150,7 @@ func (s *DockerSuite) TestDockerContainersWithLabels(c *check.C) { DefaultRule string }{ DockerHost: s.getDockerHost(), - DefaultRule: "Host:{{ normalize .Name }}.docker.localhost", + DefaultRule: "Host(`{{ normalize .Name }}.docker.localhost`)", } file := s.adaptFile(c, "fixtures/docker/simple.toml", tempObjects) @@ -158,13 +158,13 @@ func (s *DockerSuite) TestDockerContainersWithLabels(c *check.C) { // Start a container with some labels labels := map[string]string{ - "traefik.Routers.Super.Rule": "Host:my.super.host", + "traefik.Routers.Super.Rule": "Host(`my.super.host`)", } s.startContainerWithLabels(c, "swarm:1.0.0", labels, "manage", "token://blabla") // Start another container by replacing a '.' by a '-' labels = map[string]string{ - "traefik.Routers.SuperHost.Rule": "Host:my-super.host", + "traefik.Routers.SuperHost.Rule": "Host(`my-super.host`)", } s.startContainerWithLabels(c, "swarm:1.0.0", labels, "manage", "token://blablabla") @@ -206,7 +206,7 @@ func (s *DockerSuite) TestDockerContainersWithOneMissingLabels(c *check.C) { DefaultRule string }{ DockerHost: s.getDockerHost(), - DefaultRule: "Host:{{ normalize .Name }}.docker.localhost", + DefaultRule: "Host(`{{ normalize .Name }}.docker.localhost`)", } file := s.adaptFile(c, "fixtures/docker/simple.toml", tempObjects) @@ -242,7 +242,7 @@ func (s *DockerSuite) TestRestartDockerContainers(c *check.C) { DefaultRule string }{ DockerHost: s.getDockerHost(), - DefaultRule: "Host:{{ normalize .Name }}.docker.localhost", + DefaultRule: "Host(`{{ normalize .Name }}.docker.localhost`)", } file := s.adaptFile(c, "fixtures/docker/simple.toml", tempObjects) @@ -250,7 +250,7 @@ func (s *DockerSuite) TestRestartDockerContainers(c *check.C) { // Start a container with some labels labels := map[string]string{ - "traefik.Routers.Super.Rule": "Host:my.super.host", + "traefik.Routers.Super.Rule": "Host(`my.super.host`)", "traefik.Services.powpow.LoadBalancer.server.Port": "2375", } s.startContainerWithNameAndLabels(c, "powpow", "swarm:1.0.0", labels, "manage", "token://blabla") diff --git a/integration/fixtures/access_log_config.toml b/integration/fixtures/access_log_config.toml index 868097f3e..901f74903 100644 --- a/integration/fixtures/access_log_config.toml +++ b/integration/fixtures/access_log_config.toml @@ -25,5 +25,5 @@ checkNewVersion = false [providers] [providers.docker] exposedByDefault = false - defaultRule = "{{ normalize .Name }}.docker.local" + defaultRule = "Host(`{{ normalize .Name }}.docker.local`)" watch = true diff --git a/integration/fixtures/acme/acme_base.toml b/integration/fixtures/acme/acme_base.toml index dd0185a19..76909a03a 100644 --- a/integration/fixtures/acme/acme_base.toml +++ b/integration/fixtures/acme/acme_base.toml @@ -51,5 +51,5 @@ logLevel = "DEBUG" [routers] [routers.test] service = "test" - rule = "Host:traefik.acme.wtf" + rule = "Host(`traefik.acme.wtf`)" entryPoints = ["https"] diff --git a/integration/fixtures/acme/acme_http01_web_path.toml b/integration/fixtures/acme/acme_http01_web_path.toml index cd6c5bfbd..62d65c0c9 100644 --- a/integration/fixtures/acme/acme_http01_web_path.toml +++ b/integration/fixtures/acme/acme_http01_web_path.toml @@ -48,5 +48,5 @@ path="/traefik" [routers] [routers.test] service = "test" - rule = "Host:traefik.acme.wtf" + rule = "Host(`traefik.acme.wtf`)" entryPoints = ["https"] diff --git a/integration/fixtures/acme/acme_tls.toml b/integration/fixtures/acme/acme_tls.toml index f139dea4a..2111f8b8b 100644 --- a/integration/fixtures/acme/acme_tls.toml +++ b/integration/fixtures/acme/acme_tls.toml @@ -52,5 +52,5 @@ logLevel = "DEBUG" [routers] [routers.test] service = "test" - rule = "Host:traefik.acme.wtf" + rule = "Host(`traefik.acme.wtf`)" entryPoints = ["https"] diff --git a/integration/fixtures/acme/certificates.toml b/integration/fixtures/acme/certificates.toml index 090409fc2..58342ddb9 100644 --- a/integration/fixtures/acme/certificates.toml +++ b/integration/fixtures/acme/certificates.toml @@ -7,7 +7,7 @@ [routers] [routers.test] service = "test" - rule = "Host:traefik.acme.wtf" + rule = "Host(`traefik.acme.wtf`)" entryPoints = ["https"] diff --git a/integration/fixtures/consul_catalog/simple.toml b/integration/fixtures/consul_catalog/simple.toml index 79311ea00..254110ee3 100644 --- a/integration/fixtures/consul_catalog/simple.toml +++ b/integration/fixtures/consul_catalog/simple.toml @@ -10,4 +10,4 @@ logLevel = "DEBUG" [providers] [providers.consulCatalog] domain = "consul.localhost" - frontEndRule = "Host:{{.ServiceName}}.{{.Domain}}" + frontEndRule = "Host(`{{.ServiceName}}.{{.Domain}}`)" diff --git a/integration/fixtures/error_pages/error.toml b/integration/fixtures/error_pages/error.toml index d8dd776d8..b164720ab 100644 --- a/integration/fixtures/error_pages/error.toml +++ b/integration/fixtures/error_pages/error.toml @@ -10,7 +10,7 @@ logLevel = "DEBUG" [routers] [routers.router1] - rule = "Host:test.local" + Rule = "Host(`test.local`)" service = "service1" middlewares = ["error"] diff --git a/integration/fixtures/error_pages/simple.toml b/integration/fixtures/error_pages/simple.toml index ae3747ac5..69fcce87f 100644 --- a/integration/fixtures/error_pages/simple.toml +++ b/integration/fixtures/error_pages/simple.toml @@ -10,7 +10,7 @@ logLevel = "DEBUG" [routers] [routers.router1] - rule = "Host:test.local" + Rule = "Host(`test.local`)" service = "service1" middlewares = ["error"] diff --git a/integration/fixtures/file/dir/simple1.toml b/integration/fixtures/file/dir/simple1.toml index 7cb9249b0..15e271056 100644 --- a/integration/fixtures/file/dir/simple1.toml +++ b/integration/fixtures/file/dir/simple1.toml @@ -1,6 +1,6 @@ [routers] [routers.router1] - rule = "Path:/test1" + rule = "Path(`/test1`)" service = "service1" [services] diff --git a/integration/fixtures/file/dir/simple2.toml b/integration/fixtures/file/dir/simple2.toml index 53177b1d6..3347aebd6 100644 --- a/integration/fixtures/file/dir/simple2.toml +++ b/integration/fixtures/file/dir/simple2.toml @@ -1,6 +1,6 @@ [routers] [routers.router2] - rule = "Path:/test2" + rule = "Path(`/test2`)" service = "service2" [services] diff --git a/integration/fixtures/file/simple.toml b/integration/fixtures/file/simple.toml index 238aef85e..b8a79e1f9 100644 --- a/integration/fixtures/file/simple.toml +++ b/integration/fixtures/file/simple.toml @@ -11,11 +11,11 @@ logLevel = "DEBUG" [routers] [routers.router1] - rule = "Host:test.localhost" + rule = "Host(`test.localhost`)" service = "service2" [routers.router2] - rule = "Path:/test" + rule = "Path(`/test`)" middlewares = ["circuitbreaker"] service = "service1" diff --git a/integration/fixtures/grpc/config.toml b/integration/fixtures/grpc/config.toml index e7095b35d..ac014a25d 100644 --- a/integration/fixtures/grpc/config.toml +++ b/integration/fixtures/grpc/config.toml @@ -19,7 +19,7 @@ debug = true [routers] [routers.router1] - rule = "Host:127.0.0.1" + rule = "Host(`127.0.0.1`)" service = "service1" [services] diff --git a/integration/fixtures/grpc/config_h2c.toml b/integration/fixtures/grpc/config_h2c.toml index 271703df3..da9eaec0d 100644 --- a/integration/fixtures/grpc/config_h2c.toml +++ b/integration/fixtures/grpc/config_h2c.toml @@ -13,7 +13,7 @@ debug = true [routers] [routers.router1] - rule = "Host:127.0.0.1" + rule = "Host(`127.0.0.1`)" service = "service1" [services] diff --git a/integration/fixtures/grpc/config_h2c_termination.toml b/integration/fixtures/grpc/config_h2c_termination.toml index 7e2ffa1a3..4741881a5 100644 --- a/integration/fixtures/grpc/config_h2c_termination.toml +++ b/integration/fixtures/grpc/config_h2c_termination.toml @@ -17,7 +17,7 @@ debug = true [routers] [routers.router1] - rule = "Host:127.0.0.1" + rule = "Host(`127.0.0.1`)" service = "service1" [services] diff --git a/integration/fixtures/grpc/config_insecure.toml b/integration/fixtures/grpc/config_insecure.toml index e8e61fd2d..7b8e50c06 100644 --- a/integration/fixtures/grpc/config_insecure.toml +++ b/integration/fixtures/grpc/config_insecure.toml @@ -21,7 +21,7 @@ debug = true [routers] [routers.router1] - rule = "Host:127.0.0.1" + rule = "Host(`127.0.0.1`)" service = "service1" diff --git a/integration/fixtures/grpc/config_with_flush.toml b/integration/fixtures/grpc/config_with_flush.toml index 4f662a465..a92b97cbb 100644 --- a/integration/fixtures/grpc/config_with_flush.toml +++ b/integration/fixtures/grpc/config_with_flush.toml @@ -18,7 +18,7 @@ rootCAs = [ """{{ .CertContent }}""" ] [routers] [routers.router1] - rule = "Host:127.0.0.1" + rule = "Host(`127.0.0.1`)" service = "service1" [services] diff --git a/integration/fixtures/healthcheck/multiple-entrypoints-drr.toml b/integration/fixtures/healthcheck/multiple-entrypoints-drr.toml index 1f17d2b5c..edc7a9b31 100644 --- a/integration/fixtures/healthcheck/multiple-entrypoints-drr.toml +++ b/integration/fixtures/healthcheck/multiple-entrypoints-drr.toml @@ -15,7 +15,7 @@ logLevel = "DEBUG" [routers] [routers.router1] service = "service1" - rule = "Host:test.localhost" + Rule = "Host(`test.localhost`)" [services] [services.service1.loadbalancer] diff --git a/integration/fixtures/healthcheck/multiple-entrypoints-wrr.toml b/integration/fixtures/healthcheck/multiple-entrypoints-wrr.toml index 84c376c54..9021da395 100644 --- a/integration/fixtures/healthcheck/multiple-entrypoints-wrr.toml +++ b/integration/fixtures/healthcheck/multiple-entrypoints-wrr.toml @@ -15,7 +15,7 @@ logLevel = "DEBUG" [routers] [routers.router1] service = "service1" - rule = "Host:test.localhost" + Rule = "Host(`test.localhost`)" [services] [services.service1.loadbalancer] diff --git a/integration/fixtures/healthcheck/port_overload.toml b/integration/fixtures/healthcheck/port_overload.toml index bfa3624a4..1ec52c899 100644 --- a/integration/fixtures/healthcheck/port_overload.toml +++ b/integration/fixtures/healthcheck/port_overload.toml @@ -13,7 +13,7 @@ logLevel = "DEBUG" [routers] [routers.router1] service = "service1" - rule = "Host:test.localhost" + Rule = "Host(`test.localhost`)" [services] [services.service1.loadbalancer] diff --git a/integration/fixtures/healthcheck/simple.toml b/integration/fixtures/healthcheck/simple.toml index 6fd00ee13..e7dfa6fc4 100644 --- a/integration/fixtures/healthcheck/simple.toml +++ b/integration/fixtures/healthcheck/simple.toml @@ -13,7 +13,7 @@ logLevel = "DEBUG" [routers] [routers.router1] service = "service1" - rule = "Host:test.localhost" + Rule = "Host(`test.localhost`)" [services] [services.service1.loadbalancer] diff --git a/integration/fixtures/https/clientca/https_1ca1config.toml b/integration/fixtures/https/clientca/https_1ca1config.toml index 77b5f4838..ed71469e2 100644 --- a/integration/fixtures/https/clientca/https_1ca1config.toml +++ b/integration/fixtures/https/clientca/https_1ca1config.toml @@ -17,10 +17,10 @@ logLevel = "DEBUG" [Routers] [Routers.router1] Service = "service1" - Rule = "Host:snitest.com" + Rule = "Host(`snitest.com`)" [Routers.router2] Service = "service2" - Rule = "Host:snitest.org" + Rule = "Host(`snitest.org`)" [Services] [Services.service1] diff --git a/integration/fixtures/https/clientca/https_2ca1config.toml b/integration/fixtures/https/clientca/https_2ca1config.toml index 84db043cc..108100b17 100644 --- a/integration/fixtures/https/clientca/https_2ca1config.toml +++ b/integration/fixtures/https/clientca/https_2ca1config.toml @@ -16,10 +16,10 @@ logLevel = "DEBUG" [Routers] [Routers.router1] Service = "service1" - Rule = "Host:snitest.com" + Rule = "Host(`snitest.com`)" [Routers.router2] Service = "service2" - Rule = "Host:snitest.org" + Rule = "Host(`snitest.org`)" [Services] [Services.service1] diff --git a/integration/fixtures/https/clientca/https_2ca2config.toml b/integration/fixtures/https/clientca/https_2ca2config.toml index 5e2372a71..d74efc51f 100644 --- a/integration/fixtures/https/clientca/https_2ca2config.toml +++ b/integration/fixtures/https/clientca/https_2ca2config.toml @@ -18,10 +18,10 @@ logLevel = "DEBUG" [Routers] [Routers.router1] Service = "service1" - Rule = "Host:snitest.com" + Rule = "Host(`snitest.com`)" [Routers.router2] Service = "service2" - Rule = "Host:snitest.org" + Rule = "Host(`snitest.org`)" [Services] [Services.service1] diff --git a/integration/fixtures/https/dynamic_https.toml b/integration/fixtures/https/dynamic_https.toml index c1942f519..c6d6abc27 100644 --- a/integration/fixtures/https/dynamic_https.toml +++ b/integration/fixtures/https/dynamic_https.toml @@ -1,10 +1,10 @@ [Routers] [Routers.router1] Service = "service1" - Rule = "Host:snitest.com" + Rule = "Host(`snitest.com`)" [Routers.router2] Service = "service2" - Rule = "Host:snitest.org" + Rule = "Host(`snitest.org`)" [Services] [Services.service1] diff --git a/integration/fixtures/https/dynamic_https_sni_default_cert.toml b/integration/fixtures/https/dynamic_https_sni_default_cert.toml index 1c906d3c2..e232489b4 100644 --- a/integration/fixtures/https/dynamic_https_sni_default_cert.toml +++ b/integration/fixtures/https/dynamic_https_sni_default_cert.toml @@ -17,10 +17,10 @@ logLevel = "DEBUG" [Routers] [Routers.router1] Service = "service1" - Rule = "Host:snitest.com" + Rule = "Host(`snitest.com`)" [Routers.router2] Service = "service1" - Rule = "Host:www.snitest.com" + Rule = "Host(`www.snitest.com`)" [Services] [Services.service1] diff --git a/integration/fixtures/https/https_redirect.toml b/integration/fixtures/https/https_redirect.toml index 9e68b1b4b..782ab8628 100644 --- a/integration/fixtures/https/https_redirect.toml +++ b/integration/fixtures/https/https_redirect.toml @@ -19,52 +19,52 @@ logLevel = "DEBUG" [Routers.router1] Service = "service1" Middlewares = ["redirect-https"] - Rule = "Host: example.com" + Rule = "Host(`example.com`)" [Routers.router2] Service = "service1" Middlewares = ["redirect-https", "api-slash-strip"] - Rule = "Host: example2.com" + Rule = "Host(`example2.com`)" [Routers.router3] Service = "service1" Middlewares = ["redirect-https", "foo-add-prefix"] - Rule = "Host: test.com" + Rule = "Host(`test.com`)" [Routers.router4] Service = "service1" Middlewares = ["redirect-https", "foo-slash-add-prefix"] - Rule = "Host: test2.com" + Rule = "Host(`test2.com`)" [Routers.router5] Service = "service1" Middlewares = ["redirect-https", "id-strip-regex-prefix"] - Rule = "Host: foo.com" + Rule = "Host(`foo.com`)" [Routers.router6] Service = "service1" Middlewares = ["redirect-https", "id-slash-strip-regex-prefix"] - Rule = "Host: foo2.com" + Rule = "Host(`foo2.com`)" [Routers.router7] Service = "service1" Middlewares = ["redirect-https", "api-regex-replace"] - Rule = "Host: bar.com" + Rule = "Host(`bar.com`)" [Routers.router8] Service = "service1" Middlewares = ["redirect-https", "api-slash-regex-replace"] - Rule = "Host: bar2.com" + Rule = "Host(`bar2.com`)" [Routers.router9] Service = "service1" Middlewares = ["redirect-https", "api-replace-path"] - Rule = "Host: pow.com" + Rule = "Host(`pow.com`)" [Routers.router10] Service = "service1" Middlewares = ["redirect-https", "api-slash-replace-path"] - Rule = "Host: pow2.com" + Rule = "Host(`pow2.com`)" [Middlewares] [Middlewares.api-strip.StripPrefix] diff --git a/integration/fixtures/https/https_sni.toml b/integration/fixtures/https/https_sni.toml index 4e5cdd04b..8b29b8dc5 100644 --- a/integration/fixtures/https/https_sni.toml +++ b/integration/fixtures/https/https_sni.toml @@ -15,10 +15,10 @@ logLevel = "DEBUG" [Routers] [Routers.router1] Service = "service1" - Rule = "Host:snitest.com" + Rule = "Host(`snitest.com`)" [Routers.router2] Service = "service2" - Rule = "Host:snitest.org" + Rule = "Host(`snitest.org`)" [Services] [Services.service1] diff --git a/integration/fixtures/https/https_sni_case_insensitive_dynamic.toml b/integration/fixtures/https/https_sni_case_insensitive_dynamic.toml index 45f9f0a6d..1bb018c7c 100644 --- a/integration/fixtures/https/https_sni_case_insensitive_dynamic.toml +++ b/integration/fixtures/https/https_sni_case_insensitive_dynamic.toml @@ -16,10 +16,10 @@ logLevel = "DEBUG" [Routers] [Routers.router1] Service = "service1" - rule = "HostRegexp: {subdomain:[a-z1-9-]+}.snitest.com" + rule = "HostRegexp(`{subdomain:[a-z1-9-]+}.snitest.com`)" [Routers.router2] Service = "service1" - rule = "HostRegexp: {subdomain:[a-z1-9-]+}.www.snitest.com" + rule = "HostRegexp(`{subdomain:[a-z1-9-]+}.www.snitest.com`)" [Services] [Services.service1] diff --git a/integration/fixtures/https/https_sni_default_cert.toml b/integration/fixtures/https/https_sni_default_cert.toml index 5dd6aae6f..9b1dd9e8c 100644 --- a/integration/fixtures/https/https_sni_default_cert.toml +++ b/integration/fixtures/https/https_sni_default_cert.toml @@ -18,10 +18,10 @@ logLevel = "DEBUG" [Routers] [Routers.router1] Service = "service1" - Rule = "Host:snitest.com" + Rule = "Host(`snitest.com`)" [Routers.router2] Service = "service1" - Rule = "Host:www.snitest.com" + Rule = "Host(`www.snitest.com`)" [Services] [Services.service1] diff --git a/integration/fixtures/https/https_sni_strict.toml b/integration/fixtures/https/https_sni_strict.toml index c9aafc7b9..9cf044500 100644 --- a/integration/fixtures/https/https_sni_strict.toml +++ b/integration/fixtures/https/https_sni_strict.toml @@ -19,7 +19,7 @@ logLevel = "DEBUG" [Routers] [Routers.router1] Service = "service1" - Rule = "Host:snitest.com" + Rule = "Host(`snitest.com`)" [Services] [Services.service1] diff --git a/integration/fixtures/https/rootcas/https.toml b/integration/fixtures/https/rootcas/https.toml index 8cb38a98a..614493ea7 100644 --- a/integration/fixtures/https/rootcas/https.toml +++ b/integration/fixtures/https/rootcas/https.toml @@ -33,7 +33,7 @@ fblo6RBxUQ== [Routers] [Routers.router1] Service = "service1" - Rule = "Path: /ping" + Rule = "Path(`/ping`)" [Services] [Services.service1] diff --git a/integration/fixtures/https/rootcas/https_with_file.toml b/integration/fixtures/https/rootcas/https_with_file.toml index 277f7d1c6..f147b97e1 100644 --- a/integration/fixtures/https/rootcas/https_with_file.toml +++ b/integration/fixtures/https/rootcas/https_with_file.toml @@ -17,7 +17,7 @@ rootCAs = [ "fixtures/https/rootcas/local.crt"] [Routers] [Routers.router1] Service = "service1" - Rule = "Path: /ping" + Rule = "Path(`/ping`)" [Services] [Services.service1] diff --git a/integration/fixtures/keep_trailing_slash.toml b/integration/fixtures/keep_trailing_slash.toml index 69739f1a6..352c6489e 100644 --- a/integration/fixtures/keep_trailing_slash.toml +++ b/integration/fixtures/keep_trailing_slash.toml @@ -22,4 +22,4 @@ logLevel = "DEBUG" [frontends.frontend1] backend = "backend1" [frontends.frontend1.routes.test_1] - rule = "Path:/test/foo" + rule = "Path(`/test/foo`)" diff --git a/integration/fixtures/log_rotation_config.toml b/integration/fixtures/log_rotation_config.toml index 571a064e2..bb40bdb62 100644 --- a/integration/fixtures/log_rotation_config.toml +++ b/integration/fixtures/log_rotation_config.toml @@ -34,7 +34,7 @@ entryPoint = "api" [Routers] [Routers.router1] Service = "service1" - Rule = "Path: /test1" + Rule = "Path(`/test1`)" [Services] [Services.service1] diff --git a/integration/fixtures/multiple_provider.toml b/integration/fixtures/multiple_provider.toml index ccade3841..a64345703 100644 --- a/integration/fixtures/multiple_provider.toml +++ b/integration/fixtures/multiple_provider.toml @@ -18,7 +18,7 @@ debug=true [Routers] [Routers.router-1] Service = "service-test" - Rule = "PathPrefix:/file" + Rule = "PathPrefix(`/file`)" [Services] [Services.service-test] diff --git a/integration/fixtures/proxy-protocol/with.toml b/integration/fixtures/proxy-protocol/with.toml index 69ed09c2a..f7727a5f0 100644 --- a/integration/fixtures/proxy-protocol/with.toml +++ b/integration/fixtures/proxy-protocol/with.toml @@ -15,7 +15,7 @@ logLevel = "DEBUG" [Routers] [Routers.router1] Service = "service1" - Rule = "Path:/whoami" + Rule = "Path(`/whoami`)" [Services] [Services.service1] diff --git a/integration/fixtures/proxy-protocol/without.toml b/integration/fixtures/proxy-protocol/without.toml index b1871c5ca..46b32fe9e 100644 --- a/integration/fixtures/proxy-protocol/without.toml +++ b/integration/fixtures/proxy-protocol/without.toml @@ -15,7 +15,7 @@ logLevel = "DEBUG" [Routers] [Routers.router1] Service = "service1" - Rule = "Path:/whoami" + Rule = "Path(`/whoami`)" [Services] [Services.service1] diff --git a/integration/fixtures/ratelimit/simple.toml b/integration/fixtures/ratelimit/simple.toml index d0b8ca842..cdbcd7f42 100644 --- a/integration/fixtures/ratelimit/simple.toml +++ b/integration/fixtures/ratelimit/simple.toml @@ -12,7 +12,7 @@ logLevel = "DEBUG" [Routers.router1] Service = "service1" Middlewares = [ "ratelimit" ] - Rule = "Path:/" + Rule = "Path(`/`)" [Middlewares] [Middlewares.ratelimit.RateLimit] diff --git a/integration/fixtures/reqacceptgrace.toml b/integration/fixtures/reqacceptgrace.toml index 92622ae11..78e36ef6b 100644 --- a/integration/fixtures/reqacceptgrace.toml +++ b/integration/fixtures/reqacceptgrace.toml @@ -20,7 +20,7 @@ logLevel = "DEBUG" [Routers] [Routers.router] Service = "service" - Rule = "Path:/service" + Rule = "Path(`/service`)" [Services] [Services.service] diff --git a/integration/fixtures/retry/simple.toml b/integration/fixtures/retry/simple.toml index 8ce9931f1..817e41759 100644 --- a/integration/fixtures/retry/simple.toml +++ b/integration/fixtures/retry/simple.toml @@ -14,7 +14,7 @@ logLevel = "DEBUG" [Routers.router1] Service = "service1" Middlewares = [ "retry" ] - Rule = "PathPrefix:/" + Rule = "PathPrefix(`/`)" [Middlewares.retry.Retry] Attempts = 3 diff --git a/integration/fixtures/simple_hostresolver.toml b/integration/fixtures/simple_hostresolver.toml index 5af67b98d..3a72ac503 100644 --- a/integration/fixtures/simple_hostresolver.toml +++ b/integration/fixtures/simple_hostresolver.toml @@ -10,7 +10,7 @@ logLevel = "DEBUG" [providers] [providers.docker] exposedByDefault = false - defaultRule = "{{ normalize .Name }}.docker.local" + defaultRule = "Host(`{{ normalize .Name }}.docker.local`)" watch = true [hostResolver] diff --git a/integration/fixtures/simple_stats.toml b/integration/fixtures/simple_stats.toml index dd0d483fc..f127458c7 100644 --- a/integration/fixtures/simple_stats.toml +++ b/integration/fixtures/simple_stats.toml @@ -14,12 +14,12 @@ debug=true [Routers.router1] EntryPoints = ["http"] Service = "service1" - Rule = "PathPrefix:/whoami" + Rule = "PathPrefix(`/whoami`)" [Routers.router2] EntryPoints = ["traefik"] Service = "service2" - Rule = "PathPrefix:/whoami" + Rule = "PathPrefix(`/whoami`)" [Services] [Services.service1] diff --git a/integration/fixtures/timeout/forwarding_timeouts.toml b/integration/fixtures/timeout/forwarding_timeouts.toml index 774b5bb3a..47da759bd 100644 --- a/integration/fixtures/timeout/forwarding_timeouts.toml +++ b/integration/fixtures/timeout/forwarding_timeouts.toml @@ -23,11 +23,11 @@ logLevel = "DEBUG" [Routers] [Routers.router1] Service = "service1" - Rule = "Path:/dialTimeout" + Rule = "Path(`/dialTimeout`)" [Routers.router2] Service = "service2" - Rule = "Path:/responseHeaderTimeout" + Rule = "Path(`/responseHeaderTimeout`)" [Services] [Services.service1] diff --git a/integration/fixtures/tracing/simple.toml b/integration/fixtures/tracing/simple.toml index 36b72924d..99df6a739 100644 --- a/integration/fixtures/tracing/simple.toml +++ b/integration/fixtures/tracing/simple.toml @@ -27,15 +27,15 @@ debug = true [Routers.router1] Service = "service1" Middlewares = ["retry", "ratelimit"] - Rule = "Path:/ratelimit" + Rule = "Path(`/ratelimit`)" [Routers.router2] Service = "service2" Middlewares = ["retry"] - Rule = "Path:/retry" + Rule = "Path(`/retry`)" [Routers.router3] Service = "service3" Middlewares = ["retry", "basic-auth"] - Rule = "Path:/auth" + Rule = "Path(`/auth`)" [Middlewares] [Middlewares.retry.retry] diff --git a/integration/fixtures/traefik_log_config.toml b/integration/fixtures/traefik_log_config.toml index 4cf560609..744794590 100644 --- a/integration/fixtures/traefik_log_config.toml +++ b/integration/fixtures/traefik_log_config.toml @@ -21,5 +21,5 @@ checkNewVersion = false [providers] [providers.docker] exposedByDefault = false - defaultRule = "{{ normalize .Name }}.docker.local" + defaultRule = "Host(`{{ normalize .Name }}.docker.local`)" watch = true diff --git a/integration/fixtures/websocket/config.toml b/integration/fixtures/websocket/config.toml index 91d3b2a53..114c2e127 100644 --- a/integration/fixtures/websocket/config.toml +++ b/integration/fixtures/websocket/config.toml @@ -13,7 +13,7 @@ logLevel = "DEBUG" [Routers] [Routers.router1] Service = "service1" - Rule = "PathPrefix:/ws" + Rule = "PathPrefix(`/ws`)" [Services] [Services.service1] diff --git a/integration/fixtures/websocket/config_https.toml b/integration/fixtures/websocket/config_https.toml index 283c4224f..5b559d6a7 100644 --- a/integration/fixtures/websocket/config_https.toml +++ b/integration/fixtures/websocket/config_https.toml @@ -21,7 +21,7 @@ insecureSkipVerify=true [Routers] [Routers.router1] Service = "service1" - Rule = "Path:/echo,/ws" + Rule = "Path(`/echo`,`/ws`)" [Services] [Services.service1] diff --git a/integration/grpc_test.go b/integration/grpc_test.go index 597b829a9..990e27bee 100644 --- a/integration/grpc_test.go +++ b/integration/grpc_test.go @@ -167,7 +167,7 @@ func (s *GRPCSuite) TestGRPC(c *check.C) { defer cmd.Process.Kill() // wait for Traefik - err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 1*time.Second, try.BodyContains("Host:127.0.0.1")) + err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 1*time.Second, try.BodyContains("Host(`127.0.0.1`)")) c.Assert(err, check.IsNil) var response string @@ -205,7 +205,7 @@ func (s *GRPCSuite) TestGRPCh2c(c *check.C) { defer cmd.Process.Kill() // wait for Traefik - err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 1*time.Second, try.BodyContains("Host:127.0.0.1")) + err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 1*time.Second, try.BodyContains("Host(`127.0.0.1`)")) c.Assert(err, check.IsNil) var response string @@ -247,7 +247,7 @@ func (s *GRPCSuite) TestGRPCh2cTermination(c *check.C) { defer cmd.Process.Kill() // wait for Traefik - err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 1*time.Second, try.BodyContains("Host:127.0.0.1")) + err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 1*time.Second, try.BodyContains("Host(`127.0.0.1`)")) c.Assert(err, check.IsNil) var response string @@ -289,7 +289,7 @@ func (s *GRPCSuite) TestGRPCInsecure(c *check.C) { defer cmd.Process.Kill() // wait for Traefik - err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 1*time.Second, try.BodyContains("Host:127.0.0.1")) + err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 1*time.Second, try.BodyContains("Host(`127.0.0.1`)")) c.Assert(err, check.IsNil) var response string @@ -336,7 +336,7 @@ func (s *GRPCSuite) TestGRPCBuffer(c *check.C) { defer cmd.Process.Kill() // wait for Traefik - err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 1*time.Second, try.BodyContains("Host:127.0.0.1")) + err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 1*time.Second, try.BodyContains("Host(`127.0.0.1`)")) c.Assert(err, check.IsNil) var client helloworld.Greeter_StreamExampleClient client, closer, err := callStreamExampleClientGRPC() @@ -396,7 +396,7 @@ func (s *GRPCSuite) TestGRPCBufferWithFlushInterval(c *check.C) { defer cmd.Process.Kill() // wait for Traefik - err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 1*time.Second, try.BodyContains("Host:127.0.0.1")) + err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 1*time.Second, try.BodyContains("Host(`127.0.0.1`)")) c.Assert(err, check.IsNil) var client helloworld.Greeter_StreamExampleClient diff --git a/integration/healthcheck_test.go b/integration/healthcheck_test.go index 75f2f1b4a..1729a382f 100644 --- a/integration/healthcheck_test.go +++ b/integration/healthcheck_test.go @@ -41,7 +41,7 @@ func (s *HealthCheckSuite) TestSimpleConfiguration(c *check.C) { defer cmd.Process.Kill() // wait for traefik - err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 60*time.Second, try.BodyContains("Host:test.localhost")) + err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 60*time.Second, try.BodyContains("Host(`test.localhost`)")) c.Assert(err, checker.IsNil) frontendHealthReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/health", nil) @@ -117,7 +117,7 @@ func (s *HealthCheckSuite) doTestMultipleEntrypoints(c *check.C, fixture string) defer cmd.Process.Kill() // Wait for traefik - err = try.GetRequest("http://localhost:8080/api/providers/file/routers", 60*time.Second, try.BodyContains("Host:test.localhost")) + err = try.GetRequest("http://localhost:8080/api/providers/file/routers", 60*time.Second, try.BodyContains("Host(`test.localhost`)")) c.Assert(err, checker.IsNil) // Check entrypoint http1 @@ -194,7 +194,7 @@ func (s *HealthCheckSuite) TestPortOverload(c *check.C) { defer cmd.Process.Kill() // wait for traefik - err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 10*time.Second, try.BodyContains("Host:test.localhost")) + err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 10*time.Second, try.BodyContains("Host(`test.localhost`)")) c.Assert(err, checker.IsNil) frontendHealthReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/health", nil) diff --git a/integration/https_test.go b/integration/https_test.go index b05874218..c8ec67958 100644 --- a/integration/https_test.go +++ b/integration/https_test.go @@ -32,7 +32,7 @@ func (s *HTTPSSuite) TestWithSNIConfigHandshake(c *check.C) { defer cmd.Process.Kill() // wait for Traefik - err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 500*time.Millisecond, try.BodyContains("Host:snitest.org")) + err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 500*time.Millisecond, try.BodyContains("Host(`snitest.org`)")) c.Assert(err, checker.IsNil) tlsConfig := &tls.Config{ @@ -66,7 +66,7 @@ func (s *HTTPSSuite) TestWithSNIConfigRoute(c *check.C) { defer cmd.Process.Kill() // wait for Traefik - err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 1*time.Second, try.BodyContains("Host:snitest.org")) + err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 1*time.Second, try.BodyContains("Host(`snitest.org`)")) c.Assert(err, checker.IsNil) backend1 := startTestServer("9010", http.StatusNoContent) @@ -122,7 +122,7 @@ func (s *HTTPSSuite) TestWithSNIStrictNotMatchedRequest(c *check.C) { defer cmd.Process.Kill() // wait for Traefik - err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 500*time.Millisecond, try.BodyContains("Host:snitest.com")) + err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 500*time.Millisecond, try.BodyContains("Host(`snitest.com`)")) c.Assert(err, checker.IsNil) tlsConfig := &tls.Config{ @@ -146,7 +146,7 @@ func (s *HTTPSSuite) TestWithDefaultCertificate(c *check.C) { defer cmd.Process.Kill() // wait for Traefik - err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 500*time.Millisecond, try.BodyContains("Host:snitest.com")) + err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 500*time.Millisecond, try.BodyContains("Host(`snitest.com`)")) c.Assert(err, checker.IsNil) tlsConfig := &tls.Config{ @@ -180,7 +180,7 @@ func (s *HTTPSSuite) TestWithDefaultCertificateNoSNI(c *check.C) { defer cmd.Process.Kill() // wait for Traefik - err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 500*time.Millisecond, try.BodyContains("Host:snitest.com")) + err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 500*time.Millisecond, try.BodyContains("Host(`snitest.com`)")) c.Assert(err, checker.IsNil) tlsConfig := &tls.Config{ @@ -214,7 +214,7 @@ func (s *HTTPSSuite) TestWithOverlappingStaticCertificate(c *check.C) { defer cmd.Process.Kill() // wait for Traefik - err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 500*time.Millisecond, try.BodyContains("Host:snitest.com")) + err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 500*time.Millisecond, try.BodyContains("Host(`snitest.com`)")) c.Assert(err, checker.IsNil) tlsConfig := &tls.Config{ @@ -249,7 +249,7 @@ func (s *HTTPSSuite) TestWithOverlappingDynamicCertificate(c *check.C) { defer cmd.Process.Kill() // wait for Traefik - err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 500*time.Millisecond, try.BodyContains("Host:snitest.com")) + err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 500*time.Millisecond, try.BodyContains("Host(`snitest.com`)")) c.Assert(err, checker.IsNil) tlsConfig := &tls.Config{ @@ -282,7 +282,7 @@ func (s *HTTPSSuite) TestWithClientCertificateAuthentication(c *check.C) { defer cmd.Process.Kill() // wait for Traefik - err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 500*time.Millisecond, try.BodyContains("Host:snitest.org")) + err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 500*time.Millisecond, try.BodyContains("Host(`snitest.org`)")) c.Assert(err, checker.IsNil) tlsConfig := &tls.Config{ @@ -338,7 +338,7 @@ func (s *HTTPSSuite) TestWithClientCertificateAuthenticationMultipeCAs(c *check. defer cmd.Process.Kill() // wait for Traefik - err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 1*time.Second, try.BodyContains("Host:snitest.org")) + err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 1*time.Second, try.BodyContains("Host(`snitest.org`)")) c.Assert(err, checker.IsNil) tlsConfig := &tls.Config{ @@ -399,7 +399,7 @@ func (s *HTTPSSuite) TestWithClientCertificateAuthenticationMultipeCAsMultipleFi defer cmd.Process.Kill() // wait for Traefik - err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 1*time.Second, try.BodyContains("Host:snitest.org")) + err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 1*time.Second, try.BodyContains("Host(`snitest.org`)")) c.Assert(err, checker.IsNil) tlsConfig := &tls.Config{ @@ -544,7 +544,7 @@ func (s *HTTPSSuite) TestWithSNIDynamicConfigRouteWithNoChange(c *check.C) { } // wait for Traefik - err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 1*time.Second, try.BodyContains("Host:"+tr1.TLSClientConfig.ServerName)) + err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 1*time.Second, try.BodyContains("Host(`"+tr1.TLSClientConfig.ServerName+"`)")) c.Assert(err, checker.IsNil) backend1 := startTestServer("9010", http.StatusNoContent) @@ -613,7 +613,7 @@ func (s *HTTPSSuite) TestWithSNIDynamicConfigRouteWithChange(c *check.C) { } // wait for Traefik - err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 1*time.Second, try.BodyContains("Host:"+tr2.TLSClientConfig.ServerName)) + err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 1*time.Second, try.BodyContains("Host(`"+tr2.TLSClientConfig.ServerName+"`)")) c.Assert(err, checker.IsNil) backend1 := startTestServer("9010", http.StatusNoContent) @@ -676,7 +676,7 @@ func (s *HTTPSSuite) TestWithSNIDynamicConfigRouteWithTlsConfigurationDeletion(c } // wait for Traefik - err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 1*time.Second, try.BodyContains("Host:"+tr2.TLSClientConfig.ServerName)) + err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 1*time.Second, try.BodyContains("Host(`"+tr2.TLSClientConfig.ServerName+"`)")) c.Assert(err, checker.IsNil) backend2 := startTestServer("9020", http.StatusResetContent) @@ -741,7 +741,7 @@ func (s *HTTPSSuite) TestEntrypointHttpsRedirectAndPathModification(c *check.C) defer cmd.Process.Kill() // wait for Traefik - err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 5*time.Second, try.BodyContains("Host: example.com")) + err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 5*time.Second, try.BodyContains("Host(`example.com`)")) c.Assert(err, checker.IsNil) client := &http.Client{ @@ -842,7 +842,7 @@ func (s *HTTPSSuite) TestWithSNIDynamicCaseInsensitive(c *check.C) { defer cmd.Process.Kill() // wait for Traefik - err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 500*time.Millisecond, try.BodyContains("HostRegexp: {subdomain:[a-z1-9-]+}.www.snitest.com")) + err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 500*time.Millisecond, try.BodyContains("HostRegexp(`{subdomain:[a-z1-9-]+}.www.snitest.com`)")) c.Assert(err, checker.IsNil) tlsConfig := &tls.Config{ diff --git a/integration/marathon15_test.go b/integration/marathon15_test.go index 4db893df4..6dc18ae5d 100644 --- a/integration/marathon15_test.go +++ b/integration/marathon15_test.go @@ -97,7 +97,7 @@ func (s *MarathonSuite15) TestConfigurationUpdate(c *check.C) { CPU(0.1). Memory(32). EmptyNetworks(). - AddLabel("traefik.Routers.rt.Rule", "PathPrefix:/service") + AddLabel("traefik.Routers.rt.Rule", "PathPrefix(`/service`)") app.Container. Expose(80). Docker. @@ -117,7 +117,7 @@ func (s *MarathonSuite15) TestConfigurationUpdate(c *check.C) { CPU(0.1). Memory(32). EmptyNetworks(). - AddLabel("traefik.Routers.app.Rule", "PathPrefix:/app") + AddLabel("traefik.Routers.app.Rule", "PathPrefix(`/app`)") app.Container. Expose(80). Docker. diff --git a/integration/marathon_test.go b/integration/marathon_test.go index 51a042ea5..1888526e6 100644 --- a/integration/marathon_test.go +++ b/integration/marathon_test.go @@ -108,7 +108,7 @@ func (s *MarathonSuite) TestConfigurationUpdate(c *check.C) { Name("/whoami"). CPU(0.1). Memory(32). - AddLabel("traefik.Routers.rt.Rule", "PathPrefix:/service") + AddLabel("traefik.Routers.rt.Rule", "PathPrefix(`/service`)") app.Container.Docker.Bridged(). Expose(80). Container("containous/whoami") @@ -125,7 +125,7 @@ func (s *MarathonSuite) TestConfigurationUpdate(c *check.C) { Name("/whoami"). CPU(0.1). Memory(32). - AddLabel("traefik.Routers.app.Rule", "PathPrefix:/app") + AddLabel("traefik.Routers.app.Rule", "PathPrefix(`/app`)") app.Container.Docker.Bridged(). Expose(80). Container("containous/whoami") diff --git a/integration/resources/compose/access_log.yml b/integration/resources/compose/access_log.yml index 238b1514a..f18bf4fb5 100644 --- a/integration/resources/compose/access_log.yml +++ b/integration/resources/compose/access_log.yml @@ -3,21 +3,21 @@ server0: labels: - traefik.enable=true - traefik.routers.rt-server0.entryPoints=http - - traefik.routers.rt-server0.rule=Path:/test + - traefik.routers.rt-server0.rule=Path("/test") - traefik.services.service1.loadbalancer.server.port=80 server1: image: containous/whoami labels: - traefik.enable=true - traefik.routers.rt-server1.entryPoints=http - - traefik.routers.rt-server1.rule=Host:frontend1.docker.local + - traefik.routers.rt-server1.rule=Host("frontend1.docker.local") - traefik.services.service1.loadbalancer.server.port=80 server2: image: containous/whoami labels: - traefik.enable=true - traefik.routers.rt-server2.entryPoints=http - - traefik.routers.rt-server2.rule=Host:frontend2.docker.local + - traefik.routers.rt-server2.rule=Host("frontend2.docker.local") - traefik.services.service2.loadbalancer.server.port=80 - traefik.services.service2.loadbalancer.method=drr server3: @@ -25,7 +25,7 @@ server3: labels: - traefik.enable=true - traefik.routers.rt-server3.entryPoints=http - - traefik.routers.rt-server3.rule=Host:frontend2.docker.local + - traefik.routers.rt-server3.rule=Host("frontend2.docker.local") - traefik.services.service2.loadbalancer.server.port=80 - traefik.services.service2.loadbalancer.method=drr authFrontend: @@ -33,7 +33,7 @@ authFrontend: labels: - traefik.enable=true - traefik.routers.rt-authFrontend.entryPoints=httpFrontendAuth - - traefik.routers.rt-authFrontend.rule=Host:frontend.auth.docker.local + - traefik.routers.rt-authFrontend.rule=Host("frontend.auth.docker.local") - traefik.routers.rt-authFrontend.middlewares=basicauth - traefik.middlewares.basicauth.basicauth.users=test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/ - traefik.services.service3.loadbalancer.server.port=80 @@ -42,7 +42,7 @@ digestAuthMiddleware: labels: - traefik.enable=true - traefik.routers.rt-digestAuthMiddleware.entryPoints=digestAuth - - traefik.routers.rt-digestAuthMiddleware.rule=Host:entrypoint.digest.auth.docker.local + - traefik.routers.rt-digestAuthMiddleware.rule=Host("entrypoint.digest.auth.docker.local") - traefik.routers.rt-digestAuthMiddleware.middlewares=digestauth - traefik.middlewares.digestauth.digestauth.users=test:traefik:a2688e031edb4be6a3797f3882655c05, test2:traefik:518845800f9e2bfb1f1f740ec24f074e - traefik.services.service3.loadbalancer.server.port=80 @@ -51,7 +51,7 @@ frontendRedirect: labels: - traefik.enable=true - traefik.routers.rt-frontendRedirect.entryPoints=frontendRedirect - - traefik.routers.rt-frontendRedirect.rule=Path:/test + - traefik.routers.rt-frontendRedirect.rule=Path("/test") - traefik.routers.rt-frontendRedirect.middlewares=redirecthttp - traefik.middlewares.redirecthttp.redirectScheme.scheme=http - traefik.middlewares.redirecthttp.redirectScheme.port=8000 @@ -61,7 +61,7 @@ rateLimit: labels: - traefik.enable=true - traefik.routers.rt-rateLimit.entryPoints=httpRateLimit - - traefik.routers.rt-rateLimit.rule=Host:ratelimit.docker.local + - traefik.routers.rt-rateLimit.rule=Host("ratelimit.docker.local") - traefik.routers.rt-rateLimit.middlewares=rate - traefik.middlewares.rate.ratelimit.extractorfunc=client.ip - traefik.middlewares.rate.ratelimit.rateset.Rate0.average=1 @@ -73,7 +73,7 @@ frontendWhitelist: labels: - traefik.enable=true - traefik.routers.rt-frontendWhitelist.entryPoints=http - - traefik.routers.rt-frontendWhitelist.rule=Host:frontend.whitelist.docker.local + - traefik.routers.rt-frontendWhitelist.rule=Host("frontend.whitelist.docker.local") - traefik.routers.rt-frontendWhitelist.middlewares=wl - traefik.middlewares.wl.ipwhitelist.sourcerange=8.8.8.8/32 - traefik.services.service3.loadbalancer.server.port=80 diff --git a/integration/resources/compose/addprefix.yml b/integration/resources/compose/addprefix.yml deleted file mode 100644 index ab5ac89f7..000000000 --- a/integration/resources/compose/addprefix.yml +++ /dev/null @@ -1,5 +0,0 @@ -whoami1: - image: containous/whoami - labels: - - traefik.enable=true - - traefik.frontend.rule=AddPrefix:/whoami;PathPrefix:/ diff --git a/integration/resources/compose/base.yml b/integration/resources/compose/base.yml index 1fedba145..431347b45 100644 --- a/integration/resources/compose/base.yml +++ b/integration/resources/compose/base.yml @@ -2,7 +2,7 @@ whoami1: image: containous/whoami labels: - traefik.enable=true - - traefik.routers.router1.rule=PathPrefix:/whoami + - traefik.routers.router1.rule=PathPrefix("/whoami") whoami2: image: containous/whoami diff --git a/integration/resources/compose/hostresolver.yml b/integration/resources/compose/hostresolver.yml index 3ab53247f..40c739855 100644 --- a/integration/resources/compose/hostresolver.yml +++ b/integration/resources/compose/hostresolver.yml @@ -3,4 +3,4 @@ server1: labels: - traefik.enable=true - traefik.services.service1.loadbalancer.server.port=80 - - traefik.routers.router1.rule=Host:github.com + - traefik.routers.router1.rule=Host("github.com") diff --git a/integration/resources/compose/minimal.yml b/integration/resources/compose/minimal.yml index 7581f2465..bc15f122e 100644 --- a/integration/resources/compose/minimal.yml +++ b/integration/resources/compose/minimal.yml @@ -1,6 +1,6 @@ whoami1: image: containous/whoami labels: - - traefik.Routers.RouterMini.Rule=PathPrefix:/whoami + - traefik.Routers.RouterMini.Rule=PathPrefix("/whoami") - traefik.enable=true diff --git a/integration/resources/compose/tlsclientheaders.yml b/integration/resources/compose/tlsclientheaders.yml index c4b0c748a..d0b1ed53b 100644 --- a/integration/resources/compose/tlsclientheaders.yml +++ b/integration/resources/compose/tlsclientheaders.yml @@ -2,6 +2,6 @@ whoami: image: containous/whoami labels: - traefik.frontend.passTLSClientCert.pem=true - - traefik.routers.route1.rule=PathPrefix:/ + - traefik.routers.route1.rule=PathPrefix(`/`) - traefik.routers.route1.middlewares=passtls - traefik.middlewares.passtls.passtlsclientcert.pem=true diff --git a/integration/resources/compose/whitelist.yml b/integration/resources/compose/whitelist.yml index 27da5010e..736fa16a9 100644 --- a/integration/resources/compose/whitelist.yml +++ b/integration/resources/compose/whitelist.yml @@ -2,7 +2,7 @@ noOverrideWhitelist: image: containous/whoami labels: - traefik.enable=true - - traefik.routers.rt1.rule=Host:no.override.whitelist.docker.local + - traefik.routers.rt1.rule=Host("no.override.whitelist.docker.local") - traefik.routers.rt1.middlewares=wl1 - traefik.middlewares.wl1.ipwhiteList.sourceRange=8.8.8.8 @@ -10,7 +10,7 @@ overrideIPStrategyRemoteAddrWhitelist: image: containous/whoami labels: - traefik.enable=true - - traefik.routers.rt2.rule=Host:override.remoteaddr.whitelist.docker.local + - traefik.routers.rt2.rule=Host("override.remoteaddr.whitelist.docker.local") - traefik.routers.rt2.middlewares=wl2 - traefik.middlewares.wl2.ipwhitelist.sourceRange=8.8.8.8 - traefik.middlewares.wl2.ipwhitelist.ipStrategy=true @@ -19,7 +19,7 @@ overrideIPStrategyDepthWhitelist: image: containous/whoami labels: - traefik.enable=true - - traefik.routers.rt3.rule=Host:override.depth.whitelist.docker.local + - traefik.routers.rt3.rule=Host("override.depth.whitelist.docker.local") - traefik.routers.rt3.middlewares=wl3 - traefik.middlewares.wl3.ipwhitelist.sourceRange=8.8.8.8 - traefik.middlewares.wl3.ipwhitelist.ipStrategy.depth=3 @@ -28,7 +28,7 @@ overrideIPStrategyExcludedIPsWhitelist: image: containous/whoami labels: - traefik.enable=true - - traefik.routers.rt4.rule=Host:override.excludedips.whitelist.docker.local + - traefik.routers.rt4.rule=Host("override.excludedips.whitelist.docker.local") - traefik.routers.rt4.middlewares=wl4 - traefik.middlewares.wl4.ipwhitelist.sourceRange=8.8.8.8 - traefik.middlewares.wl4.ipwhitelist.ipStrategy.excludedIPs=10.0.0.1,10.0.0.2 diff --git a/integration/rest_test.go b/integration/rest_test.go index 0169b0868..7f3c62886 100644 --- a/integration/rest_test.go +++ b/integration/rest_test.go @@ -38,7 +38,7 @@ func (s *RestSuite) TestSimpleConfiguration(c *check.C) { EntryPoints: []string{"http"}, Middlewares: []string{}, Service: "service1", - Rule: "PathPrefix:/", + Rule: "PathPrefix(`/`)", }, }, Services: map[string]*config.Service{ @@ -65,7 +65,7 @@ func (s *RestSuite) TestSimpleConfiguration(c *check.C) { c.Assert(err, checker.IsNil) c.Assert(response.StatusCode, checker.Equals, http.StatusOK) - err = try.GetRequest("http://127.0.0.1:8080/api/providers/rest/routers", 1000*time.Millisecond, try.BodyContains("PathPrefix:/")) + err = try.GetRequest("http://127.0.0.1:8080/api/providers/rest/routers", 1000*time.Millisecond, try.BodyContains("PathPrefix(`/`)")) c.Assert(err, checker.IsNil) err = try.GetRequest("http://127.0.0.1:8000/", 1000*time.Millisecond, try.StatusCodeIs(http.StatusOK)) diff --git a/integration/retry_test.go b/integration/retry_test.go index e5d73e5d8..f02bbd074 100644 --- a/integration/retry_test.go +++ b/integration/retry_test.go @@ -31,7 +31,7 @@ func (s *RetrySuite) TestRetry(c *check.C) { c.Assert(err, checker.IsNil) defer cmd.Process.Kill() - err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 60*time.Second, try.BodyContains("PathPrefix:/")) + err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 60*time.Second, try.BodyContains("PathPrefix(`/`)")) c.Assert(err, checker.IsNil) // This simulates a DialTimeout when connecting to the backend server. @@ -53,7 +53,7 @@ func (s *RetrySuite) TestRetryWebsocket(c *check.C) { c.Assert(err, checker.IsNil) defer cmd.Process.Kill() - err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 60*time.Second, try.BodyContains("PathPrefix:/")) + err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 60*time.Second, try.BodyContains("PathPrefix(`/`)")) c.Assert(err, checker.IsNil) // This simulates a DialTimeout when connecting to the backend server. diff --git a/integration/simple_test.go b/integration/simple_test.go index 1e43b79f8..e28fecfb7 100644 --- a/integration/simple_test.go +++ b/integration/simple_test.go @@ -455,7 +455,7 @@ func (s *SimpleSuite) TestMultiprovider(c *check.C) { EntryPoints: []string{"http"}, Middlewares: []string{"file.customheader"}, Service: "file.service", - Rule: "PathPrefix:/", + Rule: "PathPrefix(`/`)", }, }, } @@ -470,7 +470,7 @@ func (s *SimpleSuite) TestMultiprovider(c *check.C) { c.Assert(err, checker.IsNil) c.Assert(response.StatusCode, checker.Equals, http.StatusOK) - err = try.GetRequest("http://127.0.0.1:8080/api/providers/rest/routers", 1000*time.Millisecond, try.BodyContains("PathPrefix:/")) + err = try.GetRequest("http://127.0.0.1:8080/api/providers/rest/routers", 1000*time.Millisecond, try.BodyContains("PathPrefix(`/`)")) c.Assert(err, checker.IsNil) err = try.GetRequest("http://127.0.0.1:8000/", 1*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("CustomValue")) diff --git a/integration/timeout_test.go b/integration/timeout_test.go index 51314c150..d45d59abc 100644 --- a/integration/timeout_test.go +++ b/integration/timeout_test.go @@ -31,7 +31,7 @@ func (s *TimeoutSuite) TestForwardingTimeouts(c *check.C) { c.Assert(err, checker.IsNil) defer cmd.Process.Kill() - err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 60*time.Second, try.BodyContains("Path:/dialTimeout")) + err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 60*time.Second, try.BodyContains("Path(`/dialTimeout`)")) c.Assert(err, checker.IsNil) // This simulates a DialTimeout when connecting to the backend server. diff --git a/integration/tls_client_headers_test.go b/integration/tls_client_headers_test.go index ee8605b19..83ba22add 100644 --- a/integration/tls_client_headers_test.go +++ b/integration/tls_client_headers_test.go @@ -50,7 +50,7 @@ func (s *TLSClientHeadersSuite) TestTLSClientHeaders(c *check.C) { c.Assert(err, checker.IsNil) defer cmd.Process.Kill() - err = try.GetRequest("http://127.0.0.1:8080/api/providers/docker/routers", 2*time.Second, try.BodyContains("PathPrefix:/")) + err = try.GetRequest("http://127.0.0.1:8080/api/providers/docker/routers", 2*time.Second, try.BodyContains("PathPrefix(`/`)")) c.Assert(err, checker.IsNil) request, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:8443", nil) diff --git a/middlewares/requestdecorator/request_decorator.go b/middlewares/requestdecorator/request_decorator.go index 3672ad5cc..4f8d6afdc 100644 --- a/middlewares/requestdecorator/request_decorator.go +++ b/middlewares/requestdecorator/request_decorator.go @@ -7,8 +7,7 @@ import ( "strings" "github.com/containous/alice" - "github.com/containous/traefik/config/static" - "github.com/containous/traefik/old/types" + "github.com/containous/traefik/types" ) const ( @@ -24,7 +23,7 @@ type RequestDecorator struct { } // New creates a new request host middleware. -func New(hostResolverConfig *static.HostResolverConfig) *RequestDecorator { +func New(hostResolverConfig *types.HostResolverConfig) *RequestDecorator { requestDecorator := &RequestDecorator{} if hostResolverConfig != nil { requestDecorator.hostResolver = &Resolver{ diff --git a/middlewares/requestdecorator/request_decorator_test.go b/middlewares/requestdecorator/request_decorator_test.go index 0053390ac..0e3700a40 100644 --- a/middlewares/requestdecorator/request_decorator_test.go +++ b/middlewares/requestdecorator/request_decorator_test.go @@ -4,7 +4,8 @@ import ( "net/http" "testing" - "github.com/containous/traefik/config/static" + "github.com/containous/traefik/types" + "github.com/containous/traefik/testhelpers" "github.com/stretchr/testify/assert" ) @@ -90,7 +91,7 @@ func TestRequestFlattening(t *testing.T) { }) rh := New( - &static.HostResolverConfig{ + &types.HostResolverConfig{ CnameFlattening: true, ResolvConfig: "/etc/resolv.conf", ResolvDepth: 5, diff --git a/old/configuration/convert.go b/old/configuration/convert.go index 0c2f8d0cf..0c4904662 100644 --- a/old/configuration/convert.go +++ b/old/configuration/convert.go @@ -205,12 +205,12 @@ func convertConstraints(oldConstraints types.Constraints) types2.Constraints { // ConvertHostResolverConfig FIXME // Deprecated -func ConvertHostResolverConfig(oldconfig *HostResolverConfig) *static.HostResolverConfig { +func ConvertHostResolverConfig(oldconfig *HostResolverConfig) *types2.HostResolverConfig { if oldconfig == nil { return nil } - return &static.HostResolverConfig{ + return &types2.HostResolverConfig{ CnameFlattening: oldconfig.CnameFlattening, ResolvConfig: oldconfig.ResolvConfig, ResolvDepth: oldconfig.ResolvDepth, diff --git a/provider/acme/provider.go b/provider/acme/provider.go index 76aa1cee7..39f23cd34 100644 --- a/provider/acme/provider.go +++ b/provider/acme/provider.go @@ -357,9 +357,7 @@ func (p *Provider) watchNewDomains(ctx context.Context) { for routerName, route := range config.Routers { logger := log.FromContext(ctx).WithField(log.RouterName, routerName) - // FIXME use new rule system - domainRules := rules.Rules{} - domains, err := domainRules.ParseDomains(route.Rule) + domains, err := rules.ParseDomains(route.Rule) if err != nil { logger.Errorf("Error parsing domains in provider ACME: %v", err) continue diff --git a/provider/docker/config_test.go b/provider/docker/config_test.go index 9d1602aaa..4a81bffce 100644 --- a/provider/docker/config_test.go +++ b/provider/docker/config_test.go @@ -41,12 +41,12 @@ func TestDefaultRule(t *testing.T) { }, }, }, - defaultRule: "Host:foo.bar", + defaultRule: "Host(`foo.bar`)", expected: &config.Configuration{ Routers: map[string]*config.Router{ "Test": { Service: "Test", - Rule: "Host:foo.bar", + Rule: "Host(`foo.bar`)", }, }, Middlewares: map[string]*config.Middleware{}, @@ -86,12 +86,12 @@ func TestDefaultRule(t *testing.T) { }, }, }, - defaultRule: "Host:{{ .Name }}.foo.bar", + defaultRule: "Host(`{{ .Name }}.foo.bar`)", expected: &config.Configuration{ Routers: map[string]*config.Router{ "Test": { Service: "Test", - Rule: "Host:Test.foo.bar", + Rule: "Host(`Test.foo.bar`)", }, }, Middlewares: map[string]*config.Middleware{}, @@ -133,12 +133,12 @@ func TestDefaultRule(t *testing.T) { }, }, }, - defaultRule: `Host:{{ .Name }}.{{ index .Labels "traefik.domain" }}`, + defaultRule: `Host("{{ .Name }}.{{ index .Labels "traefik.domain" }}")`, expected: &config.Configuration{ Routers: map[string]*config.Router{ "Test": { Service: "Test", - Rule: "Host:Test.foo.bar", + Rule: `Host("Test.foo.bar")`, }, }, Middlewares: map[string]*config.Middleware{}, @@ -178,7 +178,7 @@ func TestDefaultRule(t *testing.T) { }, }, }, - defaultRule: `Host:{{ .Toto }}`, + defaultRule: `Host("{{ .Toto }}")`, expected: &config.Configuration{ Routers: map[string]*config.Router{}, Middlewares: map[string]*config.Middleware{}, @@ -263,7 +263,7 @@ func TestDefaultRule(t *testing.T) { Routers: map[string]*config.Router{ "Test": { Service: "Test", - Rule: "Host:Test", + Rule: "Host(`Test`)", }, }, Middlewares: map[string]*config.Middleware{}, @@ -343,7 +343,7 @@ func Test_buildConfiguration(t *testing.T) { Routers: map[string]*config.Router{ "Test": { Service: "Test", - Rule: "Host:Test.traefik.wtf", + Rule: "Host(`Test.traefik.wtf`)", }, }, Middlewares: map[string]*config.Middleware{}, @@ -403,11 +403,11 @@ func Test_buildConfiguration(t *testing.T) { Routers: map[string]*config.Router{ "Test": { Service: "Test", - Rule: "Host:Test.traefik.wtf", + Rule: "Host(`Test.traefik.wtf`)", }, "Test2": { Service: "Test2", - Rule: "Host:Test2.traefik.wtf", + Rule: "Host(`Test2.traefik.wtf`)", }, }, Middlewares: map[string]*config.Middleware{}, @@ -481,7 +481,7 @@ func Test_buildConfiguration(t *testing.T) { Routers: map[string]*config.Router{ "Test": { Service: "Test", - Rule: "Host:Test.traefik.wtf", + Rule: "Host(`Test.traefik.wtf`)", }, }, Middlewares: map[string]*config.Middleware{}, @@ -531,7 +531,7 @@ func Test_buildConfiguration(t *testing.T) { Routers: map[string]*config.Router{ "Test": { Service: "Service1", - Rule: "Host:Test.traefik.wtf", + Rule: "Host(`Test.traefik.wtf`)", }, }, Middlewares: map[string]*config.Middleware{}, @@ -559,7 +559,7 @@ func Test_buildConfiguration(t *testing.T) { Name: "Test", Labels: map[string]string{ "traefik.services.Service1.loadbalancer.method": "wrr", - "traefik.routers.Router1.rule": "Host:foo.com", + "traefik.routers.Router1.rule": "Host(`foo.com`)", "traefik.routers.Router1.service": "Service1", }, NetworkSettings: networkSettings{ @@ -579,7 +579,7 @@ func Test_buildConfiguration(t *testing.T) { Routers: map[string]*config.Router{ "Router1": { Service: "Service1", - Rule: "Host:foo.com", + Rule: "Host(`foo.com`)", }, }, Middlewares: map[string]*config.Middleware{}, @@ -606,7 +606,7 @@ func Test_buildConfiguration(t *testing.T) { ServiceName: "Test", Name: "Test", Labels: map[string]string{ - "traefik.routers.Router1.rule": "Host:foo.com", + "traefik.routers.Router1.rule": "Host(`foo.com`)", }, NetworkSettings: networkSettings{ Ports: nat.PortMap{ @@ -640,7 +640,7 @@ func Test_buildConfiguration(t *testing.T) { Routers: map[string]*config.Router{ "Router1": { Service: "Test", - Rule: "Host:foo.com", + Rule: "Host(`foo.com`)", }, }, }, @@ -652,7 +652,7 @@ func Test_buildConfiguration(t *testing.T) { ServiceName: "Test", Name: "Test", Labels: map[string]string{ - "traefik.routers.Router1.rule": "Host:foo.com", + "traefik.routers.Router1.rule": "Host(`foo.com`)", "traefik.services.Service1.loadbalancer.method": "wrr", }, NetworkSettings: networkSettings{ @@ -672,7 +672,7 @@ func Test_buildConfiguration(t *testing.T) { Routers: map[string]*config.Router{ "Router1": { Service: "Service1", - Rule: "Host:foo.com", + Rule: "Host(`foo.com`)", }, }, Middlewares: map[string]*config.Middleware{}, @@ -699,7 +699,7 @@ func Test_buildConfiguration(t *testing.T) { ServiceName: "Test", Name: "Test", Labels: map[string]string{ - "traefik.routers.Router1.rule": "Host:foo.com", + "traefik.routers.Router1.rule": "Host(`foo.com`)", "traefik.services.Service1.loadbalancer.method": "wrr", "traefik.services.Service2.loadbalancer.method": "wrr", }, @@ -793,7 +793,7 @@ func Test_buildConfiguration(t *testing.T) { Routers: map[string]*config.Router{ "Test": { Service: "Service1", - Rule: "Host:Test.traefik.wtf", + Rule: "Host(`Test.traefik.wtf`)", }, }, Middlewares: map[string]*config.Middleware{}, @@ -865,7 +865,7 @@ func Test_buildConfiguration(t *testing.T) { Routers: map[string]*config.Router{ "Test": { Service: "Service1", - Rule: "Host:Test.traefik.wtf", + Rule: "Host(`Test.traefik.wtf`)", }, }, Middlewares: map[string]*config.Middleware{}, @@ -918,7 +918,7 @@ func Test_buildConfiguration(t *testing.T) { Routers: map[string]*config.Router{ "Test": { Service: "Service1", - Rule: "Host:Test.traefik.wtf", + Rule: "Host(`Test.traefik.wtf`)", }, }, Middlewares: map[string]*config.Middleware{}, @@ -968,7 +968,7 @@ func Test_buildConfiguration(t *testing.T) { Routers: map[string]*config.Router{ "Test": { Service: "Test", - Rule: "Host:Test.traefik.wtf", + Rule: "Host(`Test.traefik.wtf`)", }, }, Services: map[string]*config.Service{ @@ -1041,7 +1041,7 @@ func Test_buildConfiguration(t *testing.T) { Routers: map[string]*config.Router{ "Test": { Service: "Test", - Rule: "Host:Test.traefik.wtf", + Rule: "Host(`Test.traefik.wtf`)", }, }, Middlewares: map[string]*config.Middleware{ @@ -1118,7 +1118,7 @@ func Test_buildConfiguration(t *testing.T) { Routers: map[string]*config.Router{ "Test": { Service: "Test", - Rule: "Host:Test.traefik.wtf", + Rule: "Host(`Test.traefik.wtf`)", }, }, Middlewares: map[string]*config.Middleware{}, @@ -1207,7 +1207,7 @@ func Test_buildConfiguration(t *testing.T) { Routers: map[string]*config.Router{ "Test": { Service: "Test", - Rule: "Host:Test.traefik.wtf", + Rule: "Host(`Test.traefik.wtf`)", }, }, Middlewares: map[string]*config.Middleware{}, @@ -1243,7 +1243,7 @@ func Test_buildConfiguration(t *testing.T) { ServiceName: "Test", Name: "Test", Labels: map[string]string{ - "traefik.routers.Router1.rule": "Host:foo.com", + "traefik.routers.Router1.rule": "Host(`foo.com`)", }, NetworkSettings: networkSettings{ Ports: nat.PortMap{ @@ -1262,7 +1262,7 @@ func Test_buildConfiguration(t *testing.T) { ServiceName: "Test", Name: "Test", Labels: map[string]string{ - "traefik.routers.Router1.rule": "Host:bar.com", + "traefik.routers.Router1.rule": "Host(`bar.com`)", }, NetworkSettings: networkSettings{ Ports: nat.PortMap{ @@ -1308,7 +1308,7 @@ func Test_buildConfiguration(t *testing.T) { ServiceName: "Test", Name: "Test", Labels: map[string]string{ - "traefik.routers.Router1.rule": "Host:foo.com", + "traefik.routers.Router1.rule": "Host(`foo.com`)", }, NetworkSettings: networkSettings{ Ports: nat.PortMap{ @@ -1327,7 +1327,7 @@ func Test_buildConfiguration(t *testing.T) { ServiceName: "Test", Name: "Test", Labels: map[string]string{ - "traefik.routers.Router1.rule": "Host:bar.com", + "traefik.routers.Router1.rule": "Host(`bar.com`)", }, NetworkSettings: networkSettings{ Ports: nat.PortMap{ @@ -1346,7 +1346,7 @@ func Test_buildConfiguration(t *testing.T) { ServiceName: "Test", Name: "Test", Labels: map[string]string{ - "traefik.routers.Router1.rule": "Host:foobar.com", + "traefik.routers.Router1.rule": "Host(`foobar.com`)", }, NetworkSettings: networkSettings{ Ports: nat.PortMap{ @@ -1396,7 +1396,7 @@ func Test_buildConfiguration(t *testing.T) { ServiceName: "Test", Name: "Test", Labels: map[string]string{ - "traefik.routers.Router1.rule": "Host:foo.com", + "traefik.routers.Router1.rule": "Host(`foo.com`)", }, NetworkSettings: networkSettings{ Ports: nat.PortMap{ @@ -1415,7 +1415,7 @@ func Test_buildConfiguration(t *testing.T) { ServiceName: "Test", Name: "Test", Labels: map[string]string{ - "traefik.routers.Router1.rule": "Host:foo.com", + "traefik.routers.Router1.rule": "Host(`foo.com`)", }, NetworkSettings: networkSettings{ Ports: nat.PortMap{ @@ -1434,7 +1434,7 @@ func Test_buildConfiguration(t *testing.T) { Routers: map[string]*config.Router{ "Router1": { Service: "Test", - Rule: "Host:foo.com", + Rule: "Host(`foo.com`)", }, }, Middlewares: map[string]*config.Middleware{}, @@ -1465,7 +1465,7 @@ func Test_buildConfiguration(t *testing.T) { ServiceName: "Test", Name: "Test", Labels: map[string]string{ - "traefik.routers.Router1.rule": "Host:foo.com", + "traefik.routers.Router1.rule": "Host(`foo.com`)", }, NetworkSettings: networkSettings{ Ports: nat.PortMap{ @@ -1483,7 +1483,7 @@ func Test_buildConfiguration(t *testing.T) { ServiceName: "Test2", Name: "Test", Labels: map[string]string{ - "traefik.routers.Router1.rule": "Host:foo.com", + "traefik.routers.Router1.rule": "Host(`foo.com`)", }, NetworkSettings: networkSettings{ Ports: nat.PortMap{ @@ -1555,7 +1555,7 @@ func Test_buildConfiguration(t *testing.T) { Routers: map[string]*config.Router{ "Test": { Service: "Test", - Rule: "Host:Test.traefik.wtf", + Rule: "Host(`Test.traefik.wtf`)", }, }, Middlewares: map[string]*config.Middleware{}, @@ -1602,7 +1602,7 @@ func Test_buildConfiguration(t *testing.T) { Routers: map[string]*config.Router{ "Test": { Service: "Service1", - Rule: "Host:Test.traefik.wtf", + Rule: "Host(`Test.traefik.wtf`)", }, }, Middlewares: map[string]*config.Middleware{}, @@ -1849,7 +1849,7 @@ func Test_buildConfiguration(t *testing.T) { Routers: map[string]*config.Router{ "Test": { Service: "Test", - Rule: "Host:Test.traefik.wtf", + Rule: "Host(`Test.traefik.wtf`)", }, }, Middlewares: map[string]*config.Middleware{}, @@ -1896,7 +1896,7 @@ func Test_buildConfiguration(t *testing.T) { Routers: map[string]*config.Router{ "Test": { Service: "Test", - Rule: "Host:Test.traefik.wtf", + Rule: "Host(`Test.traefik.wtf`)", Middlewares: []string{"Middleware1"}, }, }, @@ -1936,7 +1936,7 @@ func Test_buildConfiguration(t *testing.T) { p := Provider{ ExposedByDefault: true, - DefaultRule: "Host:{{ normalize .Name }}.traefik.wtf", + DefaultRule: "Host(`{{ normalize .Name }}.traefik.wtf`)", } p.Constraints = test.constraints diff --git a/provider/docker/docker.go b/provider/docker/docker.go index f3a2dc204..cb160532e 100644 --- a/provider/docker/docker.go +++ b/provider/docker/docker.go @@ -34,7 +34,7 @@ const ( // SwarmAPIVersion is a constant holding the version of the Provider API traefik will use. SwarmAPIVersion = "1.24" // DefaultTemplateRule The default template for the default rule. - DefaultTemplateRule = "Host:{{ normalize .Name }}" + DefaultTemplateRule = "Host(`{{ normalize .Name }}`)" ) var _ provider.Provider = (*Provider)(nil) diff --git a/provider/docker/swarm_test.go b/provider/docker/swarm_test.go index cc9a55bba..45d7957a2 100644 --- a/provider/docker/swarm_test.go +++ b/provider/docker/swarm_test.go @@ -132,7 +132,6 @@ func (c *fakeServicesClient) TaskList(ctx context.Context, options dockertypes.T func TestListServices(t *testing.T) { testCases := []struct { desc string - extraConf configuration services []swarm.Service tasks []swarm.Task dockerVersion string diff --git a/provider/marathon/config_test.go b/provider/marathon/config_test.go index 74310f943..ef71116cc 100644 --- a/provider/marathon/config_test.go +++ b/provider/marathon/config_test.go @@ -48,7 +48,7 @@ func TestBuildConfiguration(t *testing.T) { Routers: map[string]*config.Router{ "app": { Service: "app", - Rule: "Host:app.marathon.localhost", + Rule: "Host(`app.marathon.localhost`)", }, }, Middlewares: map[string]*config.Middleware{}, @@ -92,7 +92,7 @@ func TestBuildConfiguration(t *testing.T) { Routers: map[string]*config.Router{ "app": { Service: "app", - Rule: "Host:app.marathon.localhost", + Rule: "Host(`app.marathon.localhost`)", }, }, Middlewares: map[string]*config.Middleware{}, @@ -124,7 +124,7 @@ func TestBuildConfiguration(t *testing.T) { Routers: map[string]*config.Router{ "app": { Service: "app", - Rule: "Host:app.marathon.localhost", + Rule: "Host(`app.marathon.localhost`)", Middlewares: []string{"Middleware1"}, }, }, @@ -160,21 +160,21 @@ func TestBuildConfiguration(t *testing.T) { withTasks(localhostTask(taskPorts(8080))), withLabel("traefik.services.Service1.LoadBalancer.server.port", "index:0"), - withLabel("traefik.routers.Router1.rule", "Host:app.marathon.localhost"), + withLabel("traefik.routers.Router1.rule", "Host(`app.marathon.localhost`)"), ), application( appID("/foo-v001"), withTasks(localhostTask(taskPorts(8081))), withLabel("traefik.services.Service1.LoadBalancer.server.port", "index:0"), - withLabel("traefik.routers.Router1.rule", "Host:app.marathon.localhost"), + withLabel("traefik.routers.Router1.rule", "Host(`app.marathon.localhost`)"), ), ), expected: &config.Configuration{ Routers: map[string]*config.Router{ "Router1": { Service: "Service1", - Rule: "Host:app.marathon.localhost", + Rule: "Host(`app.marathon.localhost`)", }, }, Middlewares: map[string]*config.Middleware{}, @@ -205,7 +205,7 @@ func TestBuildConfiguration(t *testing.T) { withTasks(localhostTask(taskPorts(8081))), withLabel("traefik.services.Service1.LoadBalancer.server.port", "index:0"), - withLabel("traefik.routers.Router1.rule", "Host:app.marathon.localhost"), + withLabel("traefik.routers.Router1.rule", "Host(`app.marathon.localhost`)"), ), application( appID("/foo-v001"), @@ -213,14 +213,14 @@ func TestBuildConfiguration(t *testing.T) { withTasks(localhostTask(taskPorts(8083))), withLabel("traefik.services.Service1.LoadBalancer.server.port", "index:0"), - withLabel("traefik.routers.Router1.rule", "Host:app.marathon.localhost"), + withLabel("traefik.routers.Router1.rule", "Host(`app.marathon.localhost`)"), ), ), expected: &config.Configuration{ Routers: map[string]*config.Router{ "Router1": { Service: "Service1", - Rule: "Host:app.marathon.localhost", + Rule: "Host(`app.marathon.localhost`)", }, }, Middlewares: map[string]*config.Middleware{}, @@ -266,11 +266,11 @@ func TestBuildConfiguration(t *testing.T) { Routers: map[string]*config.Router{ "foo": { Service: "foo", - Rule: "Host:foo.marathon.localhost", + Rule: "Host(`foo.marathon.localhost`)", }, "bar": { Service: "bar", - Rule: "Host:bar.marathon.localhost", + Rule: "Host(`bar.marathon.localhost`)", }, }, Middlewares: map[string]*config.Middleware{}, @@ -310,7 +310,7 @@ func TestBuildConfiguration(t *testing.T) { Routers: map[string]*config.Router{ "app": { Service: "app", - Rule: "Host:app.marathon.localhost", + Rule: "Host(`app.marathon.localhost`)", }, }, Middlewares: map[string]*config.Middleware{}, @@ -347,7 +347,7 @@ func TestBuildConfiguration(t *testing.T) { Routers: map[string]*config.Router{ "app": { Service: "Service1", - Rule: "Host:app.marathon.localhost", + Rule: "Host(`app.marathon.localhost`)", }, }, Middlewares: map[string]*config.Middleware{}, @@ -373,14 +373,14 @@ func TestBuildConfiguration(t *testing.T) { appPorts(80, 81), withTasks(localhostTask(taskPorts(80, 81))), withLabel("traefik.services.Service1.loadbalancer.method", "wrr"), - withLabel("traefik.routers.Router1.rule", "Host:foo.com"), + withLabel("traefik.routers.Router1.rule", "Host(`foo.com`)"), withLabel("traefik.routers.Router1.service", "Service1"), )), expected: &config.Configuration{ Routers: map[string]*config.Router{ "Router1": { Service: "Service1", - Rule: "Host:foo.com", + Rule: "Host(`foo.com`)", }, }, Middlewares: map[string]*config.Middleware{}, @@ -407,7 +407,7 @@ func TestBuildConfiguration(t *testing.T) { appID("/app"), appPorts(80, 81), withTasks(localhostTask(taskPorts(80, 81))), - withLabel("traefik.routers.Router1.rule", "Host:foo.com"), + withLabel("traefik.routers.Router1.rule", "Host(`foo.com`)"), )), expected: &config.Configuration{ Middlewares: map[string]*config.Middleware{}, @@ -428,7 +428,7 @@ func TestBuildConfiguration(t *testing.T) { Routers: map[string]*config.Router{ "Router1": { Service: "app", - Rule: "Host:foo.com", + Rule: "Host(`foo.com`)", }, }, }, @@ -440,14 +440,14 @@ func TestBuildConfiguration(t *testing.T) { appID("/app"), appPorts(80, 81), withTasks(localhostTask(taskPorts(80, 81))), - withLabel("traefik.routers.Router1.rule", "Host:foo.com"), + withLabel("traefik.routers.Router1.rule", "Host(`foo.com`)"), withLabel("traefik.services.Service1.loadbalancer.method", "wrr"), )), expected: &config.Configuration{ Routers: map[string]*config.Router{ "Router1": { Service: "Service1", - Rule: "Host:foo.com", + Rule: "Host(`foo.com`)", }, }, Middlewares: map[string]*config.Middleware{}, @@ -474,7 +474,7 @@ func TestBuildConfiguration(t *testing.T) { appID("/app"), appPorts(80, 81), withTasks(localhostTask(taskPorts(80, 81))), - withLabel("traefik.routers.Router1.rule", "Host:foo.com"), + withLabel("traefik.routers.Router1.rule", "Host(`foo.com`)"), withLabel("traefik.services.Service1.loadbalancer.method", "wrr"), withLabel("traefik.services.Service2.loadbalancer.method", "wrr"), )), @@ -528,11 +528,11 @@ func TestBuildConfiguration(t *testing.T) { Routers: map[string]*config.Router{ "app": { Service: "Service1", - Rule: "Host:app.marathon.localhost", + Rule: "Host(`app.marathon.localhost`)", }, "app2": { Service: "Service1", - Rule: "Host:app2.marathon.localhost", + Rule: "Host(`app2.marathon.localhost`)", }, }, Middlewares: map[string]*config.Middleware{}, @@ -558,11 +558,11 @@ func TestBuildConfiguration(t *testing.T) { Routers: map[string]*config.Router{ "app": { Service: "app", - Rule: "Host:app.marathon.localhost", + Rule: "Host(`app.marathon.localhost`)", }, "app2": { Service: "app2", - Rule: "Host:app2.marathon.localhost", + Rule: "Host(`app2.marathon.localhost`)", }, }, Middlewares: map[string]*config.Middleware{ @@ -620,11 +620,11 @@ func TestBuildConfiguration(t *testing.T) { Routers: map[string]*config.Router{ "app": { Service: "app", - Rule: "Host:app.marathon.localhost", + Rule: "Host(`app.marathon.localhost`)", }, "app2": { Service: "app2", - Rule: "Host:app2.marathon.localhost", + Rule: "Host(`app2.marathon.localhost`)", }, }, Middlewares: map[string]*config.Middleware{}, @@ -663,13 +663,13 @@ func TestBuildConfiguration(t *testing.T) { appID("/app"), appPorts(80, 81), withTasks(localhostTask(taskPorts(80, 81))), - withLabel("traefik.routers.Router1.rule", "Host:foo.com"), + withLabel("traefik.routers.Router1.rule", "Host(`foo.com`)"), ), application( appID("/app2"), appPorts(80, 81), withTasks(localhostTask(taskPorts(80, 81))), - withLabel("traefik.routers.Router1.rule", "Host:bar.com"), + withLabel("traefik.routers.Router1.rule", "Host(`bar.com`)"), )), expected: &config.Configuration{ Routers: map[string]*config.Router{}, @@ -709,21 +709,21 @@ func TestBuildConfiguration(t *testing.T) { appID("/app"), appPorts(80, 81), withTasks(localhostTask(taskPorts(80, 81))), - withLabel("traefik.routers.Router1.rule", "Host:foo.com"), + withLabel("traefik.routers.Router1.rule", "Host(`foo.com`)"), withLabel("traefik.services.Service1.LoadBalancer.method", "wrr"), ), application( appID("/app2"), appPorts(80, 81), withTasks(localhostTask(taskPorts(80, 81))), - withLabel("traefik.routers.Router1.rule", "Host:foo.com"), + withLabel("traefik.routers.Router1.rule", "Host(`foo.com`)"), withLabel("traefik.services.Service1.LoadBalancer.method", "wrr"), )), expected: &config.Configuration{ Routers: map[string]*config.Router{ "Router1": { Service: "Service1", - Rule: "Host:foo.com", + Rule: "Host(`foo.com`)", }, }, Middlewares: map[string]*config.Middleware{}, @@ -754,13 +754,13 @@ func TestBuildConfiguration(t *testing.T) { appID("/app"), appPorts(80, 81), withTasks(localhostTask(taskPorts(80, 81))), - withLabel("traefik.routers.Router1.rule", "Host:foo.com"), + withLabel("traefik.routers.Router1.rule", "Host(`foo.com`)"), ), application( appID("/app2"), appPorts(80, 81), withTasks(localhostTask(taskPorts(80, 81))), - withLabel("traefik.routers.Router1.rule", "Host:foo.com"), + withLabel("traefik.routers.Router1.rule", "Host(`foo.com`)"), )), expected: &config.Configuration{ Routers: map[string]*config.Router{}, @@ -806,7 +806,7 @@ func TestBuildConfiguration(t *testing.T) { Routers: map[string]*config.Router{ "app": { Service: "app", - Rule: "Host:app.marathon.localhost", + Rule: "Host(`app.marathon.localhost`)", }, }, Middlewares: map[string]*config.Middleware{}, @@ -840,7 +840,7 @@ func TestBuildConfiguration(t *testing.T) { Routers: map[string]*config.Router{ "app": { Service: "Service1", - Rule: "Host:app.marathon.localhost", + Rule: "Host(`app.marathon.localhost`)", }, }, Middlewares: map[string]*config.Middleware{}, @@ -1026,7 +1026,7 @@ func TestBuildConfiguration(t *testing.T) { Routers: map[string]*config.Router{ "app": { Service: "app", - Rule: "Host:app.marathon.localhost", + Rule: "Host(`app.marathon.localhost`)", }, }, Middlewares: map[string]*config.Middleware{}, @@ -1067,7 +1067,7 @@ func TestBuildConfiguration(t *testing.T) { Routers: map[string]*config.Router{ "app": { Service: "app", - Rule: "Host:app.marathon.localhost", + Rule: "Host(`app.marathon.localhost`)", }, }, Middlewares: map[string]*config.Middleware{}, @@ -1089,7 +1089,7 @@ func TestBuildConfiguration(t *testing.T) { }, { desc: "one app with group as subdomain rule", - defaultRule: `Host:{{ .Name | trimPrefix "/" | splitList "/" | strsToItfs | reverse | join "." }}.marathon.localhost`, + defaultRule: `Host("{{ .Name | trimPrefix "/" | splitList "/" | strsToItfs | reverse | join "." }}.marathon.localhost")`, applications: withApplications( application( appID("/a/b/app"), @@ -1100,7 +1100,7 @@ func TestBuildConfiguration(t *testing.T) { Routers: map[string]*config.Router{ "a_b_app": { Service: "a_b_app", - Rule: "Host:app.b.a.marathon.localhost", + Rule: `Host("app.b.a.marathon.localhost")`, }, }, Middlewares: map[string]*config.Middleware{}, @@ -1127,7 +1127,7 @@ func TestBuildConfiguration(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - defaultRule := DefaultTemplateRule + ".marathon.localhost" + defaultRule := "Host(`{{ normalize .Name }}.marathon.localhost`)" if len(test.defaultRule) > 0 { defaultRule = test.defaultRule } diff --git a/provider/marathon/marathon.go b/provider/marathon/marathon.go index f1d797dba..1689ba620 100644 --- a/provider/marathon/marathon.go +++ b/provider/marathon/marathon.go @@ -23,7 +23,7 @@ import ( const ( // DefaultTemplateRule The default template for the default rule. - DefaultTemplateRule = "Host:{{ normalize .Name }}" + DefaultTemplateRule = "Host(`{{ normalize .Name }}`)" traceMaxScanTokenSize = 1024 * 1024 marathonEventIDs = marathon.EventIDApplications | marathon.EventIDAddHealthCheck | diff --git a/rules/parser.go b/rules/parser.go new file mode 100644 index 000000000..8e8ea4d2c --- /dev/null +++ b/rules/parser.go @@ -0,0 +1,97 @@ +package rules + +import ( + "strings" + + "github.com/pkg/errors" + "github.com/vulcand/predicate" +) + +type treeBuilder func() *tree + +// ParseDomains extract domains from rule +func ParseDomains(rule string) ([]string, error) { + parser, err := newParser() + if err != nil { + return nil, err + } + + parse, err := parser.Parse(rule) + if err != nil { + return nil, err + } + + buildTree, ok := parse.(treeBuilder) + if !ok { + return nil, errors.New("cannot parse") + } + + return lower(parseDomain(buildTree())), nil +} + +func lower(slice []string) []string { + var lowerStrings []string + for _, value := range slice { + lowerStrings = append(lowerStrings, strings.ToLower(value)) + } + return lowerStrings +} + +func parseDomain(tree *tree) []string { + switch tree.matcher { + case "and", "or": + return append(parseDomain(tree.ruleLeft), parseDomain(tree.ruleRight)...) + case "Host": + return tree.value + default: + return nil + } +} + +func andFunc(left, right treeBuilder) treeBuilder { + return func() *tree { + return &tree{ + matcher: "and", + ruleLeft: left(), + ruleRight: right(), + } + } +} + +func orFunc(left, right treeBuilder) treeBuilder { + return func() *tree { + return &tree{ + matcher: "or", + ruleLeft: left(), + ruleRight: right(), + } + } +} + +func newParser() (predicate.Parser, error) { + parserFuncs := make(map[string]interface{}) + + for matcherName := range funcs { + matcherName := matcherName + fn := func(value ...string) treeBuilder { + return func() *tree { + return &tree{ + matcher: matcherName, + value: value, + } + } + } + parserFuncs[matcherName] = fn + parserFuncs[strings.ToLower(matcherName)] = fn + parserFuncs[strings.ToUpper(matcherName)] = fn + parserFuncs[strings.Title(strings.ToLower(matcherName))] = fn + } + + return predicate.NewParser(predicate.Def{ + Operators: predicate.Operators{ + AND: andFunc, + OR: orFunc, + }, + Functions: parserFuncs, + }) +} diff --git a/rules/rules.go b/rules/rules.go index 2f0043633..7c1af84b0 100644 --- a/rules/rules.go +++ b/rules/rules.go @@ -1,45 +1,116 @@ package rules import ( - "errors" "fmt" "net/http" - "reflect" - "sort" "strings" "github.com/containous/mux" - "github.com/containous/traefik/hostresolver" - "github.com/containous/traefik/old/middlewares" - "github.com/containous/traefik/old/types" + "github.com/containous/traefik/log" + "github.com/containous/traefik/middlewares/requestdecorator" + "github.com/vulcand/predicate" ) -// Rules holds rule parsing and configuration -type Rules struct { - Route *types.ServerRoute - err error - HostResolver *hostresolver.Resolver +var funcs = map[string]func(*mux.Route, ...string) error{ + "Host": host, + "HostRegexp": hostRegexp, + "Path": path, + "PathPrefix": pathPrefix, + "Method": methods, + "Headers": headers, + "HeadersRegexp": headersRegexp, + "Query": query, } -func (r *Rules) host(hosts ...string) *mux.Route { +// Router handle routing with rules +type Router struct { + *mux.Router + parser predicate.Parser +} + +// NewRouter returns a new router instance. +func NewRouter() (*Router, error) { + parser, err := newParser() + if err != nil { + return nil, err + } + + return &Router{ + Router: mux.NewRouter().SkipClean(true), + parser: parser, + }, nil +} + +// AddRoute add a new route to the router. +func (r *Router) AddRoute(rule string, priority int, handler http.Handler) error { + parse, err := r.parser.Parse(rule) + if err != nil { + return fmt.Errorf("error while parsing rule %s: %v", rule, err) + } + + buildTree, ok := parse.(treeBuilder) + if !ok { + return fmt.Errorf("error while parsing rule %s", rule) + } + + if priority == 0 { + priority = len(rule) + } + + route := r.NewRoute().Handler(handler).Priority(priority) + return addRuleOnRoute(route, buildTree()) +} + +type tree struct { + matcher string + value []string + ruleLeft *tree + ruleRight *tree +} + +func path(route *mux.Route, paths ...string) error { + rt := route.Subrouter() + + for _, path := range paths { + tmpRt := rt.Path(path) + if tmpRt.GetError() != nil { + return tmpRt.GetError() + } + } + return nil +} + +func pathPrefix(route *mux.Route, paths ...string) error { + rt := route.Subrouter() + + for _, path := range paths { + tmpRt := rt.PathPrefix(path) + if tmpRt.GetError() != nil { + return tmpRt.GetError() + } + } + return nil +} + +func host(route *mux.Route, hosts ...string) error { for i, host := range hosts { hosts[i] = strings.ToLower(host) } - return r.Route.Route.MatcherFunc(func(req *http.Request, route *mux.RouteMatch) bool { - reqHost := middlewares.GetCanonizedHost(req.Context()) + route.MatcherFunc(func(req *http.Request, _ *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 } - if r.HostResolver != nil && r.HostResolver.CnameFlattening { - reqH, flatH := r.HostResolver.CNAMEFlatten(reqHost) + flatH := requestdecorator.GetCNAMEFlatten(req.Context()) + if len(flatH) > 0 { for _, host := range hosts { - if strings.EqualFold(reqH, host) || strings.EqualFold(flatH, host) { + if strings.EqualFold(reqHost, host) || strings.EqualFold(flatH, host) { return true } - // FIXME - //log.Debugf("CNAMEFlattening: request %s which resolved to %s, is not matched to route %s", reqH, flatH, host) + log.FromContext(req.Context()).Debugf("CNAMEFlattening: request %s which resolved to %s, is not matched to route %s", reqHost, flatH, host) } return false } @@ -51,270 +122,107 @@ func (r *Rules) host(hosts ...string) *mux.Route { } return false }) + return nil } -func (r *Rules) hostRegexp(hostPatterns ...string) *mux.Route { - router := r.Route.Route.Subrouter() - for _, hostPattern := range hostPatterns { - router.Host(hostPattern) - } - return r.Route.Route -} - -func (r *Rules) path(paths ...string) *mux.Route { - router := r.Route.Route.Subrouter() - for _, path := range paths { - router.Path(path) - } - return r.Route.Route -} - -func (r *Rules) pathPrefix(paths ...string) *mux.Route { - router := r.Route.Route.Subrouter() - for _, path := range paths { - buildPath(path, router) - } - return r.Route.Route -} - -func buildPath(path string, router *mux.Router) { - // {} are used to define a regex pattern in http://www.gorillatoolkit.org/pkg/mux. - // if we find a { in the path, that means we use regex, then the gorilla/mux implementation is chosen - // otherwise, we use a lightweight implementation - if strings.Contains(path, "{") { - router.PathPrefix(path) - } else { - m := &prefixMatcher{prefix: path} - router.NewRoute().MatcherFunc(m.Match) - } -} - -type prefixMatcher struct { - prefix string -} - -func (m *prefixMatcher) Match(r *http.Request, _ *mux.RouteMatch) bool { - return strings.HasPrefix(r.URL.Path, m.prefix) || strings.HasPrefix(r.URL.Path, m.prefix+"/") -} - -type bySize []string - -func (a bySize) Len() int { return len(a) } -func (a bySize) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func (a bySize) Less(i, j int) bool { return len(a[i]) > len(a[j]) } - -func (r *Rules) pathStrip(paths ...string) *mux.Route { - sort.Sort(bySize(paths)) - r.Route.StripPrefixes = paths - router := r.Route.Route.Subrouter() - for _, path := range paths { - router.Path(strings.TrimSpace(path)) - } - return r.Route.Route -} - -func (r *Rules) pathStripRegex(paths ...string) *mux.Route { - sort.Sort(bySize(paths)) - r.Route.StripPrefixesRegex = paths - router := r.Route.Route.Subrouter() - for _, path := range paths { - router.Path(path) - } - return r.Route.Route -} - -func (r *Rules) replacePath(paths ...string) *mux.Route { - for _, path := range paths { - r.Route.ReplacePath = path - } - return r.Route.Route -} - -func (r *Rules) replacePathRegex(paths ...string) *mux.Route { - for _, path := range paths { - r.Route.ReplacePathRegex = path - } - return r.Route.Route -} - -func (r *Rules) addPrefix(paths ...string) *mux.Route { - for _, path := range paths { - r.Route.AddPrefix = path - } - return r.Route.Route -} - -func (r *Rules) pathPrefixStrip(paths ...string) *mux.Route { - sort.Sort(bySize(paths)) - r.Route.StripPrefixes = paths - router := r.Route.Route.Subrouter() - for _, path := range paths { - buildPath(path, router) - } - return r.Route.Route -} - -func (r *Rules) pathPrefixStripRegex(paths ...string) *mux.Route { - sort.Sort(bySize(paths)) - r.Route.StripPrefixesRegex = paths - router := r.Route.Route.Subrouter() - for _, path := range paths { - router.PathPrefix(path) - } - return r.Route.Route -} - -func (r *Rules) methods(methods ...string) *mux.Route { - return r.Route.Route.Methods(methods...) -} - -func (r *Rules) headers(headers ...string) *mux.Route { - return r.Route.Route.Headers(headers...) -} - -func (r *Rules) headersRegexp(headers ...string) *mux.Route { - return r.Route.Route.HeadersRegexp(headers...) -} - -func (r *Rules) query(query ...string) *mux.Route { - var queries []string - for _, elem := range query { - queries = append(queries, strings.Split(elem, "=")...) - } - - return r.Route.Route.Queries(queries...) -} - -func (r *Rules) parseRules(expression string, onRule func(functionName string, function interface{}, arguments []string) error) error { - functions := map[string]interface{}{ - "Host": r.host, - "HostRegexp": r.hostRegexp, - "Path": r.path, - "PathStrip": r.pathStrip, - "PathStripRegex": r.pathStripRegex, - "PathPrefix": r.pathPrefix, - "PathPrefixStrip": r.pathPrefixStrip, - "PathPrefixStripRegex": r.pathPrefixStripRegex, - "Method": r.methods, - "Headers": r.headers, - "HeadersRegexp": r.headersRegexp, - "AddPrefix": r.addPrefix, - "ReplacePath": r.replacePath, - "ReplacePathRegex": r.replacePathRegex, - "Query": r.query, - } - - if len(expression) == 0 { - return errors.New("empty rule") - } - - f := func(c rune) bool { - return c == ':' - } - - // Allow multiple rules separated by ; - splitRule := func(c rune) bool { - return c == ';' - } - - parsedRules := strings.FieldsFunc(expression, splitRule) - - for _, rule := range parsedRules { - // get function - parsedFunctions := strings.FieldsFunc(rule, f) - if len(parsedFunctions) == 0 { - return fmt.Errorf("error parsing rule: '%s'", rule) - } - - functionName := strings.TrimSpace(parsedFunctions[0]) - parsedFunction, ok := functions[functionName] - if !ok { - return fmt.Errorf("error parsing rule: '%s'. Unknown function: '%s'", rule, parsedFunctions[0]) - } - parsedFunctions = append(parsedFunctions[:0], parsedFunctions[1:]...) - - // get function - fargs := func(c rune) bool { - return c == ',' - } - parsedArgs := strings.FieldsFunc(strings.Join(parsedFunctions, ":"), fargs) - if len(parsedArgs) == 0 { - return fmt.Errorf("error parsing args from rule: '%s'", rule) - } - - for i := range parsedArgs { - parsedArgs[i] = strings.TrimSpace(parsedArgs[i]) - } - - err := onRule(functionName, parsedFunction, parsedArgs) - if err != nil { - return fmt.Errorf("parsing error on rule: %v", err) +func hostRegexp(route *mux.Route, hosts ...string) error { + router := route.Subrouter() + for _, host := range hosts { + tmpRt := router.Host(host) + if tmpRt.GetError() != nil { + return tmpRt.GetError() } } return nil } -// Parse parses rules expressions -func (r *Rules) Parse(expression string) (*mux.Route, error) { - var resultRoute *mux.Route - - err := r.parseRules(expression, func(functionName string, function interface{}, arguments []string) error { - inputs := make([]reflect.Value, len(arguments)) - for i := range arguments { - inputs[i] = reflect.ValueOf(arguments[i]) - } - method := reflect.ValueOf(function) - if method.IsValid() { - resultRoute = method.Call(inputs)[0].Interface().(*mux.Route) - if r.err != nil { - return r.err - } - if resultRoute == nil { - return fmt.Errorf("invalid expression: %s", expression) - } - if resultRoute.GetError() != nil { - return resultRoute.GetError() - } - } else { - return fmt.Errorf("method not found: '%s'", functionName) - } - return nil - }) - if err != nil { - return nil, fmt.Errorf("error parsing rule: %v", err) - } - - return resultRoute, nil +func methods(route *mux.Route, methods ...string) error { + return route.Methods(methods...).GetError() } -// ParseDomains parses rules expressions and returns domains -func (r *Rules) ParseDomains(expression string) ([]string, error) { - var domains []string - isHostRule := false - - err := r.parseRules(expression, func(functionName string, function interface{}, arguments []string) error { - if functionName == "Host" { - isHostRule = true - domains = append(domains, arguments...) - } - return nil - }) - if err != nil { - return nil, fmt.Errorf("error parsing domains: %v", err) - } - - var cleanDomains []string - for _, domain := range domains { - canonicalDomain := strings.ToLower(domain) - if len(canonicalDomain) > 0 { - cleanDomains = append(cleanDomains, canonicalDomain) - } - } - - // Return an error if an Host rule is detected but no domain are parsed - if isHostRule && len(cleanDomains) == 0 { - return nil, fmt.Errorf("unable to parse correctly the domains in the Host rule from %q", expression) - } - - return cleanDomains, nil +func headers(route *mux.Route, headers ...string) error { + return route.Headers(headers...).GetError() +} + +func headersRegexp(route *mux.Route, headers ...string) error { + return route.HeadersRegexp(headers...).GetError() +} + +func query(route *mux.Route, query ...string) error { + var queries []string + for _, elem := range query { + queries = append(queries, strings.Split(elem, "=")...) + } + + route.Queries(queries...) + // Queries can return nil so we can't chain the GetError() + return route.GetError() +} + +func addRuleOnRouter(router *mux.Router, rule *tree) error { + switch rule.matcher { + case "and": + route := router.NewRoute() + err := addRuleOnRoute(route, rule.ruleLeft) + if err != nil { + return err + } + + return addRuleOnRoute(route, rule.ruleRight) + case "or": + err := addRuleOnRouter(router, rule.ruleLeft) + if err != nil { + return err + } + + return addRuleOnRouter(router, rule.ruleRight) + default: + err := checkRule(rule) + if err != nil { + return err + } + + return funcs[rule.matcher](router.NewRoute(), rule.value...) + } +} + +func addRuleOnRoute(route *mux.Route, rule *tree) error { + switch rule.matcher { + case "and": + err := addRuleOnRoute(route, rule.ruleLeft) + if err != nil { + return err + } + + return addRuleOnRoute(route, rule.ruleRight) + case "or": + subRouter := route.Subrouter() + + err := addRuleOnRouter(subRouter, rule.ruleLeft) + if err != nil { + return err + } + + return addRuleOnRouter(subRouter, rule.ruleRight) + default: + err := checkRule(rule) + if err != nil { + return err + } + + return funcs[rule.matcher](route, rule.value...) + } +} + +func checkRule(rule *tree) error { + if len(rule.value) == 0 { + return fmt.Errorf("no args for matcher %s", rule.matcher) + } + + for _, v := range rule.value { + if len(v) == 0 { + return fmt.Errorf("empty args for matcher %s, %v", rule.matcher, rule.value) + } + } + return nil } diff --git a/rules/rules_test.go b/rules/rules_test.go index 222aed363..c60ca8fd3 100644 --- a/rules/rules_test.go +++ b/rules/rules_test.go @@ -2,169 +2,569 @@ package rules import ( "net/http" - "net/url" + "net/http/httptest" "testing" "github.com/containous/mux" - "github.com/containous/traefik/old/middlewares" - "github.com/containous/traefik/old/types" + "github.com/containous/traefik/middlewares/requestdecorator" "github.com/containous/traefik/testhelpers" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func TestParseOneRule(t *testing.T) { - reqHostMid := &middlewares.RequestHost{} - rules := &Rules{ - Route: &types.ServerRoute{ - Route: mux.NewRouter().NewRoute(), - }, - } - - expression := "Host:foo.bar" - - routeResult, err := rules.Parse(expression) - require.NoError(t, err, "Error while building route for %s", expression) - - request := testhelpers.MustNewRequest(http.MethodGet, "http://foo.bar", nil) - - reqHostMid.ServeHTTP(nil, request, func(w http.ResponseWriter, r *http.Request) { - routeMatch := routeResult.Match(r, &mux.RouteMatch{Route: routeResult}) - assert.True(t, routeMatch, "Rule %s don't match.", expression) - }) -} - -func TestParseTwoRules(t *testing.T) { - reqHostMid := &middlewares.RequestHost{} - rules := &Rules{ - Route: &types.ServerRoute{ - Route: mux.NewRouter().NewRoute(), - }, - } - - expression := "Host: Foo.Bar ; Path:/FOObar" - - routeResult, err := rules.Parse(expression) - require.NoError(t, err, "Error while building route for %s.", expression) - - request := testhelpers.MustNewRequest(http.MethodGet, "http://foo.bar/foobar", nil) - reqHostMid.ServeHTTP(nil, request, func(w http.ResponseWriter, r *http.Request) { - routeMatch := routeResult.Match(r, &mux.RouteMatch{Route: routeResult}) - assert.False(t, routeMatch, "Rule %s don't match.", expression) - }) - - request = testhelpers.MustNewRequest(http.MethodGet, "http://foo.bar/FOObar", nil) - reqHostMid.ServeHTTP(nil, request, func(w http.ResponseWriter, r *http.Request) { - routeMatch := routeResult.Match(r, &mux.RouteMatch{Route: routeResult}) - assert.True(t, routeMatch, "Rule %s don't match.", expression) - }) -} - -func TestParseDomains(t *testing.T) { - rules := &Rules{} - - tests := []struct { - description string - expression string - domain []string - errorExpected bool +func Test_addRoute(t *testing.T) { + testCases := []struct { + desc string + rule string + headers map[string]string + expected map[string]int + expectedError bool }{ { - description: "Many host rules", - expression: "Host:foo.bar,test.bar", - domain: []string{"foo.bar", "test.bar"}, - errorExpected: false, + desc: "no tree", + expectedError: true, }, { - description: "No host rule", - expression: "Path:/test", - errorExpected: false, + desc: "Rule with no matcher", + rule: "rulewithnotmatcher", + expectedError: true, }, { - description: "Host rule and another rule", - expression: "Host:foo.bar;Path:/test", - domain: []string{"foo.bar"}, - errorExpected: false, + desc: "PathPrefix", + rule: "PathPrefix(`/foo`)", + expected: map[string]int{ + "http://localhost/foo": http.StatusOK, + }, }, { - description: "Host rule to trim and another rule", - expression: "Host: Foo.Bar ;Path:/test", - domain: []string{"foo.bar"}, - errorExpected: false, + desc: "wrong PathPrefix", + rule: "PathPrefix(`/bar`)", + expected: map[string]int{ + "http://localhost/foo": http.StatusNotFound, + }, }, { - description: "Host rule with no domain", - expression: "Host: ;Path:/test", - errorExpected: true, + 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(`/foo`)", + 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: "Rule with simple path", + rule: `Path("/a")`, + expected: map[string]int{ + "http://plop/a": http.StatusOK, + }, + }, + { + desc: `Rule with a simple host`, + rule: `Host("plop")`, + expected: map[string]int{ + "http://plop": http.StatusOK, + }, + }, + { + desc: "Rule with Path AND Host", + rule: `Path("/a") && Host("plop")`, + expected: map[string]int{ + "http://plop/a": http.StatusOK, + "http://plopi/a": http.StatusNotFound, + }, + }, + { + desc: "Rule with Host OR Host", + rule: `Host("tchouk") || Host("pouet")`, + expected: map[string]int{ + "http://tchouk/toto": http.StatusOK, + "http://pouet/a": http.StatusOK, + "http://plopi/a": http.StatusNotFound, + }, + }, + { + desc: "Rule with host OR (host AND path)", + rule: `Host("tchouk") || (Host("pouet") && Path("/powpow"))`, + expected: map[string]int{ + "http://tchouk/toto": http.StatusOK, + "http://tchouk/powpow": http.StatusOK, + "http://pouet/powpow": http.StatusOK, + "http://pouet/toto": http.StatusNotFound, + "http://plopi/a": http.StatusNotFound, + }, + }, + { + desc: "Rule with host OR host AND path", + rule: `Host("tchouk") || Host("pouet") && Path("/powpow")`, + expected: map[string]int{ + "http://tchouk/toto": http.StatusOK, + "http://tchouk/powpow": http.StatusOK, + "http://pouet/powpow": http.StatusOK, + "http://pouet/toto": http.StatusNotFound, + "http://plopi/a": http.StatusNotFound, + }, + }, + { + desc: "Rule with (host OR host) AND path", + rule: `(Host("tchouk") || Host("pouet")) && Path("/powpow")`, + expected: map[string]int{ + "http://tchouk/toto": http.StatusNotFound, + "http://tchouk/powpow": http.StatusOK, + "http://pouet/powpow": http.StatusOK, + "http://pouet/toto": http.StatusNotFound, + "http://plopi/a": http.StatusNotFound, + }, + }, + { + desc: "Rule with multiple host AND path", + rule: `(Host("tchouk","pouet")) && Path("/powpow")`, + expected: map[string]int{ + "http://tchouk/toto": http.StatusNotFound, + "http://tchouk/powpow": http.StatusOK, + "http://pouet/powpow": http.StatusOK, + "http://pouet/toto": http.StatusNotFound, + "http://plopi/a": http.StatusNotFound, + }, + }, + { + desc: "Rule with multiple host AND multiple path", + rule: `Host("tchouk","pouet") && Path("/powpow", "/titi")`, + expected: map[string]int{ + "http://tchouk/toto": http.StatusNotFound, + "http://tchouk/powpow": http.StatusOK, + "http://pouet/powpow": http.StatusOK, + "http://tchouk/titi": http.StatusOK, + "http://pouet/titi": http.StatusOK, + "http://pouet/toto": http.StatusNotFound, + "http://plopi/a": http.StatusNotFound, + }, + }, + { + desc: "Rule with (host AND path) OR (host AND path)", + rule: `(Host("tchouk") && Path("/titi")) || ((Host("pouet")) && Path("/powpow"))`, + expected: map[string]int{ + "http://tchouk/titi": http.StatusOK, + "http://tchouk/powpow": http.StatusNotFound, + "http://pouet/powpow": http.StatusOK, + "http://pouet/toto": http.StatusNotFound, + "http://plopi/a": http.StatusNotFound, + }, + }, + { + desc: "Rule without quote", + rule: `Host(tchouk)`, + expectedError: true, + }, + { + desc: "Rule case UPPER", + rule: `(HOST("tchouk") && PATHPREFIX("/titi"))`, + expected: map[string]int{ + "http://tchouk/titi": http.StatusOK, + "http://tchouk/powpow": http.StatusNotFound, + }, + }, + { + desc: "Rule case lower", + rule: `(host("tchouk") && pathprefix("/titi"))`, + expected: map[string]int{ + "http://tchouk/titi": http.StatusOK, + "http://tchouk/powpow": http.StatusNotFound, + }, + }, + { + desc: "Rule case CamelCase", + rule: `(Host("tchouk") && PathPrefix("/titi"))`, + expected: map[string]int{ + "http://tchouk/titi": http.StatusOK, + "http://tchouk/powpow": http.StatusNotFound, + }, + }, + { + desc: "Rule case Title", + rule: `(Host("tchouk") && Pathprefix("/titi"))`, + expected: map[string]int{ + "http://tchouk/titi": http.StatusOK, + "http://tchouk/powpow": http.StatusNotFound, + }, + }, + { + desc: "Rule Path with error", + rule: `Path("titi")`, + expectedError: true, + }, + { + desc: "Rule PathPrefix with error", + rule: `PathPrefix("titi")`, + expectedError: true, + }, + { + desc: "Rule HostRegexp with error", + rule: `HostRegexp("{test")`, + expectedError: true, + }, + { + desc: "Rule Headers with error", + rule: `Headers("titi")`, + expectedError: true, + }, + { + desc: "Rule HeadersRegexp with error", + rule: `HeadersRegexp("titi")`, + expectedError: true, + }, + { + desc: "Rule Query", + rule: `Query("titi")`, + expectedError: true, + }, + { + desc: "Rule Query with bad syntax", + rule: `Query("titi={test")`, + expectedError: true, + }, + { + desc: "Rule with Path without args", + rule: `Host("tchouk") && Path()`, + expectedError: true, + }, + { + desc: "Rule with an empty path", + rule: `Host("tchouk") && Path("")`, + expectedError: true, + }, + { + desc: "Rule with an empty path", + rule: `Host("tchouk") && Path("", "/titi")`, + expectedError: true, }, } - for _, test := range tests { + for _, test := range testCases { test := test - t.Run(test.expression, func(t *testing.T) { + t.Run(test.desc, func(t *testing.T) { t.Parallel() - domains, err := rules.ParseDomains(test.expression) + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) + router, err := NewRouter() + require.NoError(t, err) - if test.errorExpected { - require.Errorf(t, err, "unable to parse correctly the domains in the Host rule from %q", test.expression) + err = router.AddRoute(test.rule, 0, handler) + if test.expectedError { + require.Error(t, err) } else { - require.NoError(t, err, "%s: Error while parsing domain.", test.expression) - } + require.NoError(t, err) - assert.EqualValues(t, test.domain, domains, "%s: Error parsing domains from expression.", test.expression) + // 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 TestPriorities(t *testing.T) { - router := mux.NewRouter() - router.StrictSlash(true) +func Test_addRoutePriority(t *testing.T) { + type Case struct { + xFrom string + rule string + priority int + } - rules := &Rules{Route: &types.ServerRoute{Route: router.NewRoute()}} - expression01 := "PathPrefix:/foo" + 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", + }, + { + desc: "Higher priority on longest rule (longest third)", + path: "/mypath", + cases: []Case{ + { + xFrom: "header1", + rule: "PathPrefix(`/my`)", + }, + { + xFrom: "header2", + rule: "PathPrefix(`/mypa`)", + }, + { + xFrom: "header3", + rule: "PathPrefix(`/mypath`)", + }, + }, + expected: "header3", + }, + } - routeFoo, err := rules.Parse(expression01) - require.NoError(t, err, "Error while building route for %s", expression01) + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + router, err := NewRouter() + require.NoError(t, err) - fooHandler := &fakeHandler{name: "fooHandler"} - routeFoo.Handler(fooHandler) + for _, route := range test.cases { + route := route + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("X-From", route.xFrom) + }) - routeMatch := router.Match(&http.Request{URL: &url.URL{Path: "/foo"}}, &mux.RouteMatch{}) - assert.True(t, routeMatch, "Error matching route") + err := router.AddRoute(route.rule, route.priority, handler) + require.NoError(t, err, route.rule) + } - routeMatch = router.Match(&http.Request{URL: &url.URL{Path: "/fo"}}, &mux.RouteMatch{}) - assert.False(t, routeMatch, "Error matching route") + router.SortRoutes() - multipleRules := &Rules{Route: &types.ServerRoute{Route: router.NewRoute()}} - expression02 := "PathPrefix:/foobar" + w := httptest.NewRecorder() + req := testhelpers.MustNewRequest(http.MethodGet, test.path, nil) - routeFoobar, err := multipleRules.Parse(expression02) - require.NoError(t, err, "Error while building route for %s", expression02) + router.ServeHTTP(w, req) - foobarHandler := &fakeHandler{name: "foobarHandler"} - routeFoobar.Handler(foobarHandler) - routeMatch = router.Match(&http.Request{URL: &url.URL{Path: "/foo"}}, &mux.RouteMatch{}) - - assert.True(t, routeMatch, "Error matching route") - - fooMatcher := &mux.RouteMatch{} - routeMatch = router.Match(&http.Request{URL: &url.URL{Path: "/foobar"}}, fooMatcher) - - assert.True(t, routeMatch, "Error matching route") - assert.NotEqual(t, fooMatcher.Handler, foobarHandler, "Error matching priority") - assert.Equal(t, fooMatcher.Handler, fooHandler, "Error matching priority") - - routeFoo.Priority(1) - routeFoobar.Priority(10) - router.SortRoutes() - - foobarMatcher := &mux.RouteMatch{} - routeMatch = router.Match(&http.Request{URL: &url.URL{Path: "/foobar"}}, foobarMatcher) - - assert.True(t, routeMatch, "Error matching route") - assert.Equal(t, foobarMatcher.Handler, foobarHandler, "Error matching priority") - assert.NotEqual(t, foobarMatcher.Handler, fooHandler, "Error matching priority") + assert.Equal(t, test.expected, w.Header().Get("X-From")) + }) + } } func TestHostRegexp(t *testing.T) { @@ -235,13 +635,9 @@ func TestHostRegexp(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - rls := &Rules{ - Route: &types.ServerRoute{ - Route: &mux.Route{}, - }, - } - - rt := rls.hostRegexp(test.hostExp) + rt := &mux.Route{} + err := hostRegexp(rt, test.hostExp) + require.NoError(t, err) for testURL, match := range test.urls { req := testhelpers.MustNewRequest(http.MethodGet, testURL, nil) @@ -251,83 +647,57 @@ func TestHostRegexp(t *testing.T) { } } -func TestParseInvalidSyntax(t *testing.T) { - router := mux.NewRouter() - - rules := &Rules{Route: &types.ServerRoute{Route: router.NewRoute()}} - expression01 := "Path: /path1;Query:param_one=true, /path2" - - routeFoo, err := rules.Parse(expression01) - require.Error(t, err) - assert.Nil(t, routeFoo) -} - -func TestPathPrefix(t *testing.T) { +func TestParseDomains(t *testing.T) { testCases := []struct { - desc string - path string - urls map[string]bool + description string + expression string + domain []string + errorExpected bool }{ { - desc: "leading slash", - path: "/bar", - urls: map[string]bool{ - "http://foo.com/bar": true, - "http://foo.com/bar/": true, - }, + description: "Many host rules", + expression: "Host(`foo.bar`,`test.bar`)", + domain: []string{"foo.bar", "test.bar"}, + errorExpected: false, }, { - desc: "leading trailing slash", - path: "/bar/", - urls: map[string]bool{ - "http://foo.com/bar": false, - "http://foo.com/bar/": true, - }, + description: "No host rule", + expression: "Path(`/test`)", + errorExpected: false, }, { - desc: "no slash", - path: "bar", - urls: map[string]bool{ - "http://foo.com/bar": false, - "http://foo.com/bar/": false, - }, + description: "Host rule and another rule", + expression: "Host(`foo.bar`) && Path(`/test`)", + domain: []string{"foo.bar"}, + errorExpected: false, }, { - desc: "trailing slash", - path: "bar/", - urls: map[string]bool{ - "http://foo.com/bar": false, - "http://foo.com/bar/": false, - }, + description: "Host rule to trim and another rule", + expression: "Host(`Foo.Bar`) && Path(`/test`)", + domain: []string{"foo.bar"}, + errorExpected: false, + }, + { + description: "Host rule with no domain", + expression: "Host() && Path(`/test`)", + errorExpected: false, }, } for _, test := range testCases { test := test - t.Run(test.desc, func(t *testing.T) { + t.Run(test.expression, func(t *testing.T) { t.Parallel() - rls := &Rules{ - Route: &types.ServerRoute{ - Route: &mux.Route{}, - }, + domains, err := ParseDomains(test.expression) + + if test.errorExpected { + require.Errorf(t, err, "unable to parse correctly the domains in the Host rule from %q", test.expression) + } else { + require.NoError(t, err, "%s: Error while parsing domain.", test.expression) } - rt := rls.pathPrefix(test.path) - - for testURL, expectedMatch := range test.urls { - req := testhelpers.MustNewRequest(http.MethodGet, testURL, nil) - match := rt.Match(req, &mux.RouteMatch{}) - if match != expectedMatch { - t.Errorf("Error matching %s with %s, got %v expected %v", test.path, testURL, match, expectedMatch) - } - } + assert.EqualValues(t, test.domain, domains, "%s: Error parsing domains from expression.", test.expression) }) } } - -type fakeHandler struct { - name string -} - -func (h *fakeHandler) ServeHTTP(http.ResponseWriter, *http.Request) {} diff --git a/server/middleware/middlewares.go b/server/middleware/middlewares.go index 8d3fba945..132ab7ac1 100644 --- a/server/middleware/middlewares.go +++ b/server/middleware/middlewares.go @@ -55,8 +55,8 @@ func NewBuilder(configs map[string]*config.Middleware, serviceBuilder serviceBui // BuildChain creates a middleware chain func (b *Builder) BuildChain(ctx context.Context, middlewares []string) *alice.Chain { chain := alice.New() - for _, middlewareName := range middlewares { - middlewareName := internal.GetQualifiedName(ctx, middlewareName) + for _, name := range middlewares { + middlewareName := internal.GetQualifiedName(ctx, name) constructorContext := internal.AddProviderInContext(ctx, middlewareName) chain = chain.Append(func(next http.Handler) (http.Handler, error) { diff --git a/server/router/router.go b/server/router/router.go index 7be92dd19..2f7fcde7d 100644 --- a/server/router/router.go +++ b/server/router/router.go @@ -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 diff --git a/server/router/router_test.go b/server/router/router_test.go index cc7944452..0314594e2 100644 --- a/server/router/router_test.go +++ b/server/router/router_test.go @@ -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{ diff --git a/server/router/rules.go b/server/router/rules.go deleted file mode 100644 index 9cd127276..000000000 --- a/server/router/rules.go +++ /dev/null @@ -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...) -} diff --git a/server/router/rules_test.go b/server/router/rules_test.go deleted file mode 100644 index 0ce9df220..000000000 --- a/server/router/rules_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/server/server_configuration_test.go b/server/server_configuration_test.go index 03bea85ff..359b7e1dc 100644 --- a/server/server_configuration_test.go +++ b/server/server_configuration_test.go @@ -98,10 +98,10 @@ func TestReuseService(t *testing.T) { th.WithRouters( th.WithRouter("foo", th.WithServiceName("bar"), - th.WithRule("Path:/ok")), + th.WithRule("Path(`/ok`)")), th.WithRouter("foo2", th.WithEntryPoints("http"), - th.WithRule("Path:/unauthorized"), + th.WithRule("Path(`/unauthorized`)"), th.WithServiceName("bar"), th.WithRouterMiddlewares("basicauth")), ), diff --git a/server/server_test.go b/server/server_test.go index fc8fedcf5..ab8dc8274 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -142,7 +142,7 @@ func setupListenProvider(throttleDuration time.Duration) (server *Server, stop c func TestServerResponseEmptyBackend(t *testing.T) { const requestPath = "/path" - const routeRule = "Path:" + requestPath + const routeRule = "Path(`" + requestPath + "`)" testCases := []struct { desc string diff --git a/types/host_resolver.go b/types/host_resolver.go new file mode 100644 index 000000000..d48b59884 --- /dev/null +++ b/types/host_resolver.go @@ -0,0 +1,8 @@ +package types + +// HostResolverConfig contain configuration for CNAME Flattening. +type HostResolverConfig struct { + CnameFlattening bool `description:"A flag to enable/disable CNAME flattening" export:"true"` + ResolvConfig string `description:"resolv.conf used for DNS resolving" export:"true"` + ResolvDepth int `description:"The maximal depth of DNS recursive resolving" export:"true"` +}