Refactor frontends rules

Signed-off-by: Emile Vauge <emile@vauge.com>
This commit is contained in:
Emile Vauge 2016-03-27 01:05:17 +01:00
parent dd160dc342
commit 8737530a7d
No known key found for this signature in database
GPG key ID: D808B4C167352E59
24 changed files with 198 additions and 222 deletions

View file

@ -792,7 +792,7 @@ Labels can be used on containers to override default behaviour:
- `traefik.protocol=https`: override the default `http` protocol - `traefik.protocol=https`: override the default `http` protocol
- `traefik.weight=10`: assign this weight to the container - `traefik.weight=10`: assign this weight to the container
- `traefik.enable=false`: disable this container in Træfɪk - `traefik.enable=false`: disable this container in Træfɪk
- `traefik.frontend.rule=Host`: override the default frontend rule (Default: Host). See [frontends](#frontends). - `traefik.frontend.rule=Host:test.traefik.io`: override the default frontend rule (Default: `Host:{containerName}.{domain}`). See [frontends](#frontends).
- `traefik.frontend.value=test.example.com`: override the default frontend value (Default: `{containerName}.{domain}`) See [frontends](#frontends). Must be associated with label traefik.frontend.rule. - `traefik.frontend.value=test.example.com`: override the default frontend value (Default: `{containerName}.{domain}`) See [frontends](#frontends). Must be associated with label traefik.frontend.rule.
- `traefik.frontend.passHostHeader=true`: forward client `Host` header to the backend. - `traefik.frontend.passHostHeader=true`: forward client `Host` header to the backend.
- `traefik.frontend.entryPoints=http,https`: assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`. - `traefik.frontend.entryPoints=http,https`: assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`.

6
glide.lock generated
View file

@ -1,5 +1,5 @@
hash: 21d4e8dc80c87101568a719ecf01d1af9a1b58f03c5c9dc864a8cb1005ddc160 hash: a8cca3f2e5bde6b96d0c195402b14606877bc8630cd7f2f06e65e0884c6a008b
updated: 2016-03-29T21:50:20.577439177+02:00 updated: 2016-03-29T23:24:08.772606184+02:00
imports: imports:
- name: github.com/alecthomas/template - name: github.com/alecthomas/template
version: b867cc6ab45cece8143cfcc6fc9c77cf3f2c23c0 version: b867cc6ab45cece8143cfcc6fc9c77cf3f2c23c0
@ -264,7 +264,7 @@ imports:
subpackages: subpackages:
- bson - bson
- name: gopkg.in/square/go-jose.v1 - name: gopkg.in/square/go-jose.v1
version: 7d9df93c5ee8a09ed250b3b2360972fa29b4bb3c version: 40d457b439244b546f023d056628e5184136899b
subpackages: subpackages:
- cipher - cipher
- json - json

View file

@ -44,7 +44,7 @@ import:
ref: 6b4e7dc5e3143b85ea77909c72caf89416fc2915 ref: 6b4e7dc5e3143b85ea77909c72caf89416fc2915
- package: github.com/gambol99/go-marathon - package: github.com/gambol99/go-marathon
ref: ade11d1dc2884ee1f387078fc28509559b6235d1 ref: ade11d1dc2884ee1f387078fc28509559b6235d1
- package: github.com/mailgun/predicate - package: github.com/vulcand/predicate
ref: cb0bff91a7ab7cf7571e661ff883fc997bc554a3 ref: cb0bff91a7ab7cf7571e661ff883fc997bc554a3
- package: github.com/thoas/stats - package: github.com/thoas/stats
ref: 54ed61c2b47e263ae2f01b86837b0c4bd1da28e8 ref: 54ed61c2b47e263ae2f01b86837b0c4bd1da28e8

View file

@ -133,8 +133,7 @@ func (s *DockerSuite) TestDockerContainersWithLabels(c *check.C) {
defer os.Remove(file) defer os.Remove(file)
// Start a container with some labels // Start a container with some labels
labels := map[string]string{ labels := map[string]string{
"traefik.frontend.rule": "Host", "traefik.frontend.rule": "Host:my.super.host",
"traefik.frontend.value": "my.super.host",
} }
s.startContainerWithLabels(c, "swarm:1.0.0", labels, "manage", "token://blabla") s.startContainerWithLabels(c, "swarm:1.0.0", labels, "manage", "token://blabla")

View file

@ -33,10 +33,8 @@ logLevel = "DEBUG"
[frontends.frontend1] [frontends.frontend1]
backend = "backend2" backend = "backend2"
[frontends.frontend1.routes.test_1] [frontends.frontend1.routes.test_1]
rule = "Host" rule = "Host:test.localhost"
value = "test.localhost"
[frontends.frontend2] [frontends.frontend2]
backend = "backend1" backend = "backend1"
[frontends.frontend2.routes.test_2] [frontends.frontend2.routes.test_2]
rule = "Path" rule = "Path:/test"
value = "/test"

View file

@ -27,10 +27,8 @@ defaultEntryPoints = ["https"]
[frontends.frontend1] [frontends.frontend1]
backend = "backend1" backend = "backend1"
[frontends.frontend1.routes.test_1] [frontends.frontend1.routes.test_1]
rule = "Host" rule = "Host:snitest.com"
value = "snitest.com"
[frontends.frontend2] [frontends.frontend2]
backend = "backend2" backend = "backend2"
[frontends.frontend2.routes.test_2] [frontends.frontend2.routes.test_2]
rule = "Host" rule = "Host:snitest.org"
value = "snitest.org"

View file

@ -20,3 +20,8 @@ func (s *StripPrefix) ServeHTTP(w http.ResponseWriter, r *http.Request) {
http.NotFound(w, r) http.NotFound(w, r)
} }
} }
// SetHandler sets handler
func (s *StripPrefix) SetHandler(Handler http.Handler) {
s.Handler = Handler
}

View file

@ -89,7 +89,7 @@ func (provider *ConsulCatalog) getBackend(node *api.ServiceEntry) string {
} }
func (provider *ConsulCatalog) getFrontendValue(service string) string { func (provider *ConsulCatalog) getFrontendValue(service string) string {
return service + "." + provider.Domain return "Host:" + service + "." + provider.Domain
} }
func (provider *ConsulCatalog) buildConfig(catalog []catalogUpdate) *types.Configuration { func (provider *ConsulCatalog) buildConfig(catalog []catalogUpdate) *types.Configuration {

View file

@ -8,7 +8,7 @@ import (
"github.com/hashicorp/consul/api" "github.com/hashicorp/consul/api"
) )
func TestConsulCatalogGetFrontendValue(t *testing.T) { func TestConsulCatalogGetFrontendRule(t *testing.T) {
provider := &ConsulCatalog{ provider := &ConsulCatalog{
Domain: "localhost", Domain: "localhost",
} }
@ -19,7 +19,7 @@ func TestConsulCatalogGetFrontendValue(t *testing.T) {
}{ }{
{ {
service: "foo", service: "foo",
expected: "foo.localhost", expected: "Host:foo.localhost",
}, },
} }
@ -78,8 +78,7 @@ func TestConsulCatalogBuildConfig(t *testing.T) {
Backend: "backend-test", Backend: "backend-test",
Routes: map[string]types.Route{ Routes: map[string]types.Route{
"route-host-test": { "route-host-test": {
Rule: "Host", Rule: "Host:test.localhost",
Value: "test.localhost",
}, },
}, },
}, },

View file

@ -2,7 +2,6 @@ package provider
import ( import (
"errors" "errors"
"fmt"
"strconv" "strconv"
"strings" "strings"
"text/template" "text/template"
@ -108,7 +107,6 @@ func (provider *Docker) loadDockerConfig(containersInspected []docker.Container)
"getProtocol": provider.getProtocol, "getProtocol": provider.getProtocol,
"getPassHostHeader": provider.getPassHostHeader, "getPassHostHeader": provider.getPassHostHeader,
"getEntryPoints": provider.getEntryPoints, "getEntryPoints": provider.getEntryPoints,
"getFrontendValue": provider.getFrontendValue,
"getFrontendRule": provider.getFrontendRule, "getFrontendRule": provider.getFrontendRule,
"replace": replace, "replace": replace,
} }
@ -154,31 +152,18 @@ func containerFilter(container docker.Container) bool {
return false return false
} }
labels, err := getLabels(container, []string{"traefik.frontend.rule", "traefik.frontend.value"}) // labels, err := getLabels(container, []string{"traefik.frontend.rule"})
if len(labels) != 0 && err != nil { // if len(labels) != 0 && err != nil {
log.Debugf("Filtering bad labeled container %s", container.Name) // log.Debugf("Filtering bad labeled container %s", container.Name)
return false // return false
} // }
return true return true
} }
func (provider *Docker) getFrontendName(container docker.Container) string { func (provider *Docker) getFrontendName(container docker.Container) string {
// Replace '.' with '-' in quoted keys because of this issue https://github.com/BurntSushi/toml/issues/78 // Replace '.' with '-' in quoted keys because of this issue https://github.com/BurntSushi/toml/issues/78
frontendName := fmt.Sprintf("%s-%s", provider.getFrontendRule(container), provider.getFrontendValue(container)) return normalize(provider.getFrontendRule(container))
frontendName = strings.Replace(frontendName, "[", "", -1)
frontendName = strings.Replace(frontendName, "]", "", -1)
return strings.Replace(frontendName, ".", "-", -1)
}
// GetFrontendValue returns the frontend value for the specified container, using
// it's label. It returns a default one if the label is not present.
func (provider *Docker) getFrontendValue(container docker.Container) string {
if label, err := getLabel(container, "traefik.frontend.value"); err == nil {
return label
}
return getEscapedName(container.Name) + "." + provider.Domain
} }
// GetFrontendRule returns the frontend rule for the specified container, using // GetFrontendRule returns the frontend rule for the specified container, using
@ -187,14 +172,14 @@ func (provider *Docker) getFrontendRule(container docker.Container) string {
if label, err := getLabel(container, "traefik.frontend.rule"); err == nil { if label, err := getLabel(container, "traefik.frontend.rule"); err == nil {
return label return label
} }
return "Host" return "Host:" + getEscapedName(container.Name) + "." + provider.Domain
} }
func (provider *Docker) getBackend(container docker.Container) string { func (provider *Docker) getBackend(container docker.Container) string {
if label, err := getLabel(container, "traefik.backend"); err == nil { if label, err := getLabel(container, "traefik.backend"); err == nil {
return label return label
} }
return getEscapedName(container.Name) return normalize(container.Name)
} }
func (provider *Docker) getPort(container docker.Container) string { func (provider *Docker) getPort(container docker.Container) string {
@ -211,7 +196,7 @@ func (provider *Docker) getWeight(container docker.Container) string {
if label, err := getLabel(container, "traefik.weight"); err == nil { if label, err := getLabel(container, "traefik.weight"); err == nil {
return label return label
} }
return "0" return "1"
} }
func (provider *Docker) getDomain(container docker.Container) string { func (provider *Docker) getDomain(container docker.Container) string {

View file

@ -30,18 +30,18 @@ func TestDockerGetFrontendName(t *testing.T) {
Name: "bar", Name: "bar",
Config: &docker.Config{ Config: &docker.Config{
Labels: map[string]string{ Labels: map[string]string{
"traefik.frontend.rule": "Header", "traefik.frontend.rule": "Headers:User-Agent,bat/0.1.0",
}, },
}, },
}, },
expected: "Header-bar-docker-localhost", expected: "Headers-User-Agent-bat-0-1-0",
}, },
{ {
container: docker.Container{ container: docker.Container{
Name: "test", Name: "test",
Config: &docker.Config{ Config: &docker.Config{
Labels: map[string]string{ Labels: map[string]string{
"traefik.frontend.value": "foo.bar", "traefik.frontend.rule": "Host:foo.bar",
}, },
}, },
}, },
@ -52,24 +52,22 @@ func TestDockerGetFrontendName(t *testing.T) {
Name: "test", Name: "test",
Config: &docker.Config{ Config: &docker.Config{
Labels: map[string]string{ Labels: map[string]string{
"traefik.frontend.value": "foo.bar", "traefik.frontend.rule": "Path:/test",
"traefik.frontend.rule": "Header",
}, },
}, },
}, },
expected: "Header-foo-bar", expected: "Path-test",
}, },
{ {
container: docker.Container{ container: docker.Container{
Name: "test", Name: "test",
Config: &docker.Config{ Config: &docker.Config{
Labels: map[string]string{ Labels: map[string]string{
"traefik.frontend.value": "[foo.bar]", "traefik.frontend.rule": "PathPrefix:/test2",
"traefik.frontend.rule": "Header",
}, },
}, },
}, },
expected: "Header-foo-bar", expected: "PathPrefix-test2",
}, },
} }
@ -81,7 +79,7 @@ func TestDockerGetFrontendName(t *testing.T) {
} }
} }
func TestDockerGetFrontendValue(t *testing.T) { func TestDockerGetFrontendRule(t *testing.T) {
provider := &Docker{ provider := &Docker{
Domain: "docker.localhost", Domain: "docker.localhost",
} }
@ -95,60 +93,36 @@ func TestDockerGetFrontendValue(t *testing.T) {
Name: "foo", Name: "foo",
Config: &docker.Config{}, Config: &docker.Config{},
}, },
expected: "foo.docker.localhost", expected: "Host:foo.docker.localhost",
}, },
{ {
container: docker.Container{ container: docker.Container{
Name: "bar", Name: "bar",
Config: &docker.Config{}, Config: &docker.Config{},
}, },
expected: "bar.docker.localhost", expected: "Host:bar.docker.localhost",
}, },
{ {
container: docker.Container{ container: docker.Container{
Name: "test", Name: "test",
Config: &docker.Config{ Config: &docker.Config{
Labels: map[string]string{ Labels: map[string]string{
"traefik.frontend.value": "foo.bar", "traefik.frontend.rule": "Host:foo.bar",
}, },
}, },
}, },
expected: "foo.bar", expected: "Host:foo.bar",
},
}
for _, e := range containers {
actual := provider.getFrontendValue(e.container)
if actual != e.expected {
t.Fatalf("expected %q, got %q", e.expected, actual)
}
}
}
func TestDockerGetFrontendRule(t *testing.T) {
provider := &Docker{}
containers := []struct {
container docker.Container
expected string
}{
{
container: docker.Container{
Name: "foo",
Config: &docker.Config{},
},
expected: "Host",
}, },
{ {
container: docker.Container{ container: docker.Container{
Name: "test", Name: "test",
Config: &docker.Config{ Config: &docker.Config{
Labels: map[string]string{ Labels: map[string]string{
"traefik.frontend.rule": "foo", "traefik.frontend.rule": "Path:/test",
}, },
}, },
}, },
expected: "foo", expected: "Path:/test",
}, },
} }
@ -281,7 +255,7 @@ func TestDockerGetWeight(t *testing.T) {
Name: "foo", Name: "foo",
Config: &docker.Config{}, Config: &docker.Config{},
}, },
expected: "0", expected: "1",
}, },
{ {
container: docker.Container{ container: docker.Container{
@ -535,7 +509,7 @@ func TestDockerTraefikFilter(t *testing.T) {
container: docker.Container{ container: docker.Container{
Config: &docker.Config{ Config: &docker.Config{
Labels: map[string]string{ Labels: map[string]string{
"traefik.frontend.rule": "Host", "traefik.frontend.rule": "Host:foo.bar",
}, },
}, },
NetworkSettings: &docker.NetworkSettings{ NetworkSettings: &docker.NetworkSettings{
@ -544,22 +518,7 @@ func TestDockerTraefikFilter(t *testing.T) {
}, },
}, },
}, },
expected: false, expected: true,
},
{
container: docker.Container{
Config: &docker.Config{
Labels: map[string]string{
"traefik.frontend.value": "foo.bar",
},
},
NetworkSettings: &docker.NetworkSettings{
Ports: map[docker.Port][]docker.PortBinding{
"80/tcp": {},
},
},
},
expected: false,
}, },
{ {
container: docker.Container{ container: docker.Container{
@ -634,8 +593,7 @@ func TestDockerTraefikFilter(t *testing.T) {
container: docker.Container{ container: docker.Container{
Config: &docker.Config{ Config: &docker.Config{
Labels: map[string]string{ Labels: map[string]string{
"traefik.frontend.rule": "Host", "traefik.frontend.rule": "Host:foo.bar",
"traefik.frontend.value": "foo.bar",
}, },
}, },
NetworkSettings: &docker.NetworkSettings{ NetworkSettings: &docker.NetworkSettings{
@ -651,7 +609,7 @@ func TestDockerTraefikFilter(t *testing.T) {
for _, e := range containers { for _, e := range containers {
actual := containerFilter(e.container) actual := containerFilter(e.container)
if actual != e.expected { if actual != e.expected {
t.Fatalf("expected %v, got %v", e.expected, actual) t.Fatalf("expected %v for %+v, got %+v", e.expected, e, actual)
} }
} }
} }
@ -690,8 +648,7 @@ func TestDockerLoadDockerConfig(t *testing.T) {
EntryPoints: []string{}, EntryPoints: []string{},
Routes: map[string]types.Route{ Routes: map[string]types.Route{
`"route-frontend-Host-test-docker-localhost"`: { `"route-frontend-Host-test-docker-localhost"`: {
Rule: "Host", Rule: "Host:test.docker.localhost",
Value: "test.docker.localhost",
}, },
}, },
}, },
@ -701,6 +658,7 @@ func TestDockerLoadDockerConfig(t *testing.T) {
Servers: map[string]types.Server{ Servers: map[string]types.Server{
"server-test": { "server-test": {
URL: "http://127.0.0.1:80", URL: "http://127.0.0.1:80",
Weight: 1,
}, },
}, },
CircuitBreaker: nil, CircuitBreaker: nil,
@ -741,7 +699,7 @@ func TestDockerLoadDockerConfig(t *testing.T) {
"80/tcp": {}, "80/tcp": {},
}, },
Networks: map[string]docker.ContainerNetwork{ Networks: map[string]docker.ContainerNetwork{
"bridgde": { "bridge": {
IPAddress: "127.0.0.1", IPAddress: "127.0.0.1",
}, },
}, },
@ -754,8 +712,7 @@ func TestDockerLoadDockerConfig(t *testing.T) {
EntryPoints: []string{"http", "https"}, EntryPoints: []string{"http", "https"},
Routes: map[string]types.Route{ Routes: map[string]types.Route{
`"route-frontend-Host-test1-docker-localhost"`: { `"route-frontend-Host-test1-docker-localhost"`: {
Rule: "Host", Rule: "Host:test1.docker.localhost",
Value: "test1.docker.localhost",
}, },
}, },
}, },
@ -764,8 +721,7 @@ func TestDockerLoadDockerConfig(t *testing.T) {
EntryPoints: []string{}, EntryPoints: []string{},
Routes: map[string]types.Route{ Routes: map[string]types.Route{
`"route-frontend-Host-test2-docker-localhost"`: { `"route-frontend-Host-test2-docker-localhost"`: {
Rule: "Host", Rule: "Host:test2.docker.localhost",
Value: "test2.docker.localhost",
}, },
}, },
}, },
@ -775,9 +731,11 @@ func TestDockerLoadDockerConfig(t *testing.T) {
Servers: map[string]types.Server{ Servers: map[string]types.Server{
"server-test1": { "server-test1": {
URL: "http://127.0.0.1:80", URL: "http://127.0.0.1:80",
Weight: 1,
}, },
"server-test2": { "server-test2": {
URL: "http://127.0.0.1:80", URL: "http://127.0.0.1:80",
Weight: 1,
}, },
}, },
CircuitBreaker: nil, CircuitBreaker: nil,

View file

@ -96,7 +96,6 @@ func (provider *Marathon) loadMarathonConfig() *types.Configuration {
"getProtocol": provider.getProtocol, "getProtocol": provider.getProtocol,
"getPassHostHeader": provider.getPassHostHeader, "getPassHostHeader": provider.getPassHostHeader,
"getEntryPoints": provider.getEntryPoints, "getEntryPoints": provider.getEntryPoints,
"getFrontendValue": provider.getFrontendValue,
"getFrontendRule": provider.getFrontendRule, "getFrontendRule": provider.getFrontendRule,
"getFrontendBackend": provider.getFrontendBackend, "getFrontendBackend": provider.getFrontendBackend,
"replace": replace, "replace": replace,
@ -309,22 +308,13 @@ func (provider *Marathon) getEntryPoints(application marathon.Application) []str
return []string{} return []string{}
} }
// getFrontendValue returns the frontend value for the specified application, using
// it's label. It returns a default one if the label is not present.
func (provider *Marathon) getFrontendValue(application marathon.Application) string {
if label, err := provider.getLabel(application, "traefik.frontend.value"); err == nil {
return label
}
return getEscapedName(application.ID) + "." + provider.Domain
}
// getFrontendRule returns the frontend rule for the specified application, using // getFrontendRule returns the frontend rule for the specified application, using
// it's label. It returns a default one (Host) if the label is not present. // it's label. It returns a default one (Host) if the label is not present.
func (provider *Marathon) getFrontendRule(application marathon.Application) string { func (provider *Marathon) getFrontendRule(application marathon.Application) string {
if label, err := provider.getLabel(application, "traefik.frontend.rule"); err == nil { if label, err := provider.getLabel(application, "traefik.frontend.rule"); err == nil {
return label return label
} }
return "Host" return "Host:" + getEscapedName(application.ID) + "." + provider.Domain
} }
func (provider *Marathon) getBackend(task marathon.Task, applications []marathon.Application) string { func (provider *Marathon) getBackend(task marathon.Task, applications []marathon.Application) string {

View file

@ -86,8 +86,7 @@ func TestMarathonLoadConfig(t *testing.T) {
EntryPoints: []string{}, EntryPoints: []string{},
Routes: map[string]types.Route{ Routes: map[string]types.Route{
`route-host-test`: { `route-host-test`: {
Rule: "Host", Rule: "Host:test.docker.localhost",
Value: "test.docker.localhost",
}, },
}, },
}, },
@ -831,7 +830,7 @@ func TestMarathonGetEntryPoints(t *testing.T) {
} }
} }
func TestMarathonGetFrontendValue(t *testing.T) { func TestMarathonGetFrontendRule(t *testing.T) {
provider := &Marathon{ provider := &Marathon{
Domain: "docker.localhost", Domain: "docker.localhost",
} }
@ -842,50 +841,21 @@ func TestMarathonGetFrontendValue(t *testing.T) {
}{ }{
{ {
application: marathon.Application{}, application: marathon.Application{},
expected: ".docker.localhost", expected: "Host:.docker.localhost",
}, },
{ {
application: marathon.Application{ application: marathon.Application{
ID: "test", ID: "test",
}, },
expected: "test.docker.localhost", expected: "Host:test.docker.localhost",
}, },
{ {
application: marathon.Application{ application: marathon.Application{
Labels: map[string]string{ Labels: map[string]string{
"traefik.frontend.value": "foo.bar", "traefik.frontend.rule": "Host:foo.bar",
}, },
}, },
expected: "foo.bar", expected: "Host:foo.bar",
},
}
for _, a := range applications {
actual := provider.getFrontendValue(a.application)
if actual != a.expected {
t.Fatalf("expected %q, got %q", a.expected, actual)
}
}
}
func TestMarathonGetFrontendRule(t *testing.T) {
provider := &Marathon{}
applications := []struct {
application marathon.Application
expected string
}{
{
application: marathon.Application{},
expected: "Host",
},
{
application: marathon.Application{
Labels: map[string]string{
"traefik.frontend.rule": "Header",
},
},
expected: "Header",
}, },
} }

View file

@ -9,6 +9,7 @@ import (
"github.com/BurntSushi/toml" "github.com/BurntSushi/toml"
"github.com/containous/traefik/autogen" "github.com/containous/traefik/autogen"
"github.com/containous/traefik/types" "github.com/containous/traefik/types"
"unicode"
) )
// Provider defines methods of a provider. // Provider defines methods of a provider.
@ -67,3 +68,11 @@ func replace(s1 string, s2 string, s3 string) string {
func getEscapedName(name string) string { func getEscapedName(name string) string {
return strings.Replace(strings.TrimPrefix(name, "/"), "/", "-", -1) return strings.Replace(strings.TrimPrefix(name, "/"), "/", "-", -1)
} }
func normalize(name string) string {
fargs := func(c rune) bool {
return !unicode.IsLetter(c) && !unicode.IsNumber(c)
}
// get function
return strings.Join(strings.FieldsFunc(name, fargs), "-")
}

92
rules.go Normal file
View file

@ -0,0 +1,92 @@
package main
import (
"errors"
"github.com/gorilla/mux"
"reflect"
"strings"
)
// Rules holds rule parsing and configuration
type Rules struct {
route *serverRoute
}
func (r *Rules) host(host string) *mux.Route {
return r.route.route.Host(host)
}
func (r *Rules) path(path string) *mux.Route {
return r.route.route.Path(path)
}
func (r *Rules) pathPrefix(path string) *mux.Route {
return r.route.route.PathPrefix(path)
}
func (r *Rules) pathStrip(path string) *mux.Route {
r.route.stripPrefix = path
return r.route.route.Path(path)
}
func (r *Rules) pathPrefixStrip(path string) *mux.Route {
r.route.stripPrefix = path
return r.route.route.PathPrefix(path)
}
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...)
}
// Parse parses rules expressions
func (r *Rules) Parse(expression string) (*mux.Route, error) {
functions := map[string]interface{}{
"Host": r.host,
"Path": r.path,
"PathStrip": r.pathStrip,
"PathPrefix": r.pathPrefix,
"PathPrefixStrip": r.pathPrefixStrip,
"Methods": r.methods,
"Headers": r.headers,
"HeadersRegexp": r.headersRegexp,
}
f := func(c rune) bool {
return c == ':' || c == '='
}
// get function
parsedFunctions := strings.FieldsFunc(expression, f)
if len(parsedFunctions) != 2 {
return nil, errors.New("Error parsing rule: " + expression)
}
parsedFunction, ok := functions[parsedFunctions[0]]
if !ok {
return nil, errors.New("Error parsing rule: " + expression + ". Unknow function: " + parsedFunctions[0])
}
fargs := func(c rune) bool {
return c == ',' || c == ';'
}
// get function
parsedArgs := strings.FieldsFunc(parsedFunctions[1], fargs)
if len(parsedArgs) == 0 {
return nil, errors.New("Error parsing args from rule: " + expression)
}
inputs := make([]reflect.Value, len(parsedArgs))
for i := range parsedArgs {
inputs[i] = reflect.ValueOf(parsedArgs[i])
}
method := reflect.ValueOf(parsedFunction)
if method.IsValid() {
return method.Call(inputs)[0].Interface().(*mux.Route), nil
}
return nil, errors.New("Method not found: " + parsedFunctions[0])
}

View file

@ -55,6 +55,11 @@ type serverEntryPoint struct {
httpRouter *middlewares.HandlerSwitcher httpRouter *middlewares.HandlerSwitcher
} }
type serverRoute struct {
route *mux.Route
stripPrefix string
}
// NewServer returns an initialized Server. // NewServer returns an initialized Server.
func NewServer(globalConfiguration GlobalConfiguration) *Server { func NewServer(globalConfiguration GlobalConfiguration) *Server {
server := new(Server) server := new(Server)
@ -354,23 +359,22 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
if _, ok := serverEntryPoints[entryPointName]; !ok { if _, ok := serverEntryPoints[entryPointName]; !ok {
return nil, errors.New("Undefined entrypoint: " + entryPointName) return nil, errors.New("Undefined entrypoint: " + entryPointName)
} }
newRoute := serverEntryPoints[entryPointName].httpRouter.GetHandler().NewRoute().Name(frontendName) newServerRoute := &serverRoute{route: serverEntryPoints[entryPointName].httpRouter.GetHandler().NewRoute().Name(frontendName)}
for routeName, route := range frontend.Routes { for routeName, route := range frontend.Routes {
log.Debugf("Creating route %s %s:%s", routeName, route.Rule, route.Value) log.Debugf("Creating route %s %s", routeName, route.Rule)
route, err := getRoute(newRoute, route.Rule, route.Value) err := getRoute(newServerRoute, route)
if err != nil { if err != nil {
return nil, err return nil, err
} }
newRoute = route
} }
entryPoint := globalConfiguration.EntryPoints[entryPointName] entryPoint := globalConfiguration.EntryPoints[entryPointName]
if entryPoint.Redirect != nil { if entryPoint.Redirect != nil {
if redirectHandlers[entryPointName] != nil { if redirectHandlers[entryPointName] != nil {
newRoute.Handler(redirectHandlers[entryPointName]) newServerRoute.route.Handler(redirectHandlers[entryPointName])
} else if handler, err := server.loadEntryPointConfig(entryPointName, entryPoint); err != nil { } else if handler, err := server.loadEntryPointConfig(entryPointName, entryPoint); err != nil {
return nil, err return nil, err
} else { } else {
newRoute.Handler(handler) newServerRoute.route.Handler(handler)
redirectHandlers[entryPointName] = handler redirectHandlers[entryPointName] = handler
} }
} else { } else {
@ -448,9 +452,9 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
} else { } else {
log.Debugf("Reusing backend %s", frontend.Backend) log.Debugf("Reusing backend %s", frontend.Backend)
} }
server.wireFrontendBackend(frontend.Routes, newRoute, backends[frontend.Backend]) server.wireFrontendBackend(newServerRoute, backends[frontend.Backend])
} }
err := newRoute.GetError() err := newServerRoute.route.GetError()
if err != nil { if err != nil {
log.Errorf("Error building route: %s", err) log.Errorf("Error building route: %s", err)
} }
@ -460,29 +464,15 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
return serverEntryPoints, nil return serverEntryPoints, nil
} }
func (server *Server) wireFrontendBackend(routes map[string]types.Route, newRoute *mux.Route, handler http.Handler) { func (server *Server) wireFrontendBackend(serverRoute *serverRoute, handler http.Handler) {
// strip prefix // strip prefix
var strip bool if len(serverRoute.stripPrefix) > 0 {
for _, route := range routes { serverRoute.route.Handler(&middlewares.StripPrefix{
switch route.Rule { Prefix: serverRoute.stripPrefix,
case "PathStrip":
newRoute.Handler(&middlewares.StripPrefix{
Prefix: route.Value,
Handler: handler, Handler: handler,
}) })
strip = true } else {
break serverRoute.route.Handler(handler)
case "PathPrefixStrip":
newRoute.Handler(&middlewares.StripPrefix{
Prefix: route.Value,
Handler: handler,
})
strip = true
break
}
}
if !strip {
newRoute.Handler(handler)
} }
} }
@ -522,32 +512,21 @@ func (server *Server) buildDefaultHTTPRouter() *mux.Router {
return router return router
} }
func getRoute(any interface{}, rule string, value ...interface{}) (*mux.Route, error) { func getRoute(serverRoute *serverRoute, route types.Route) error {
switch rule { rules := Rules{route: serverRoute}
case "PathStrip": newRoute, err := rules.Parse(route.Rule)
rule = "Path" if err != nil {
case "PathPrefixStrip": return err
rule = "PathPrefix"
} }
inputs := make([]reflect.Value, len(value)) serverRoute.route = newRoute
for i := range value { return nil
inputs[i] = reflect.ValueOf(value[i])
}
method := reflect.ValueOf(any).MethodByName(rule)
if method.IsValid() {
return method.Call(inputs)[0].Interface().(*mux.Route), nil
}
return nil, errors.New("Method not found: " + rule)
} }
func sortedFrontendNamesForConfig(configuration *types.Configuration) []string { func sortedFrontendNamesForConfig(configuration *types.Configuration) []string {
keys := []string{} keys := []string{}
for key := range configuration.Frontends { for key := range configuration.Frontends {
keys = append(keys, key) keys = append(keys, key)
} }
sort.Strings(keys) sort.Strings(keys)
return keys return keys
} }

View file

@ -8,6 +8,5 @@
backend = "backend-{{.}}" backend = "backend-{{.}}"
passHostHeader = false passHostHeader = false
[frontends.frontend-{{.}}.routes.route-host-{{.}}] [frontends.frontend-{{.}}.routes.route-host-{{.}}]
rule = "Host" rule = "{{getFrontendValue .}}"
value = "{{getFrontendValue .}}"
{{end}} {{end}}

View file

@ -13,5 +13,4 @@
{{end}}] {{end}}]
[frontends."frontend-{{$frontend}}".routes."route-frontend-{{$frontend}}"] [frontends."frontend-{{$frontend}}".routes."route-frontend-{{$frontend}}"]
rule = "{{getFrontendRule $container}}" rule = "{{getFrontendRule $container}}"
value = "{{getFrontendValue $container}}"
{{end}} {{end}}

View file

@ -37,6 +37,5 @@
{{range $routes}} {{range $routes}}
[frontends.{{$frontend}}.routes.{{Last .}}] [frontends.{{$frontend}}.routes.{{Last .}}]
rule = "{{Get "" . "/rule"}}" rule = "{{Get "" . "/rule"}}"
value = "{{Get "" . "/value"}}"
{{end}} {{end}}
{{end}} {{end}}

View file

@ -14,5 +14,4 @@
{{end}}] {{end}}]
[frontends.frontend{{.ID | replace "/" "-"}}.routes.route-host{{.ID | replace "/" "-"}}] [frontends.frontend{{.ID | replace "/" "-"}}.routes.route-host{{.ID | replace "/" "-"}}]
rule = "{{getFrontendRule .}}" rule = "{{getFrontendRule .}}"
value = "{{getFrontendValue .}}"
{{end}} {{end}}

View file

@ -25,6 +25,7 @@
], ],
"labels": { "labels": {
"traefik.weight": "1", "traefik.weight": "1",
"traefik.protocole": "http" "traefik.protocole": "http",
"traefik.frontend.rule" : "Headers:Host,test.localhost"
} }
} }

View file

@ -31,7 +31,6 @@ type Server struct {
// Route holds route configuration. // Route holds route configuration.
type Route struct { type Route struct {
Rule string `json:"rule,omitempty"` Rule string `json:"rule,omitempty"`
Value string `json:"value,omitempty"`
} }
// Frontend holds frontend configuration. // Frontend holds frontend configuration.

View file

@ -7,12 +7,10 @@
<tr> <tr>
<td><em>Route</em></td> <td><em>Route</em></td>
<td><em>Rule</em></td> <td><em>Rule</em></td>
<td><em>Value</em></td>
</tr> </tr>
<tr data-ng-repeat="(routeId, route) in frontendCtrl.frontend.routes"> <tr data-ng-repeat="(routeId, route) in frontendCtrl.frontend.routes">
<td>{{routeId}}</td> <td>{{routeId}}</td>
<td>{{route.rule}}</td> <td><code>{{route.rule}}</code></td>
<td><code>{{route.value}}</code></td>
</tr> </tr>
</table> </table>
</div> </div>

View file

@ -30,7 +30,7 @@
<nav class="navbar navbar-default"> <nav class="navbar navbar-default">
<div class="container-fluid"> <div class="container-fluid">
<div class="navbar-header"> <div class="navbar-header">
<a class="navbar-brand traefik-text" ui-sref="provider"><img src="traefik.icon.png"/></a> <a class="navbar-brand traefik-text" ui-sref="provider"><img height="16" src="traefik.icon.png"/></a>
</div> </div>
<div class="collapse navbar-collapse"> <div class="collapse navbar-collapse">