diff --git a/.travis.yml b/.travis.yml index 979ded43c..db7f46d8d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,16 +1,14 @@ branches: except: - /^v\d\.\d\.\d.*$/ - env: REPO: $TRAVIS_REPO_SLUG VERSION: v1.0.0-beta.$TRAVIS_BUILD_NUMBER - + global: + secure: btt4r13t09gQlHb6gYrvGC2yGCMMHfnp1Mz1RQedc4Mpf/FfT8aE6xmK2a2i9CCvskjrP0t/BFaS4yxIURjnFRn+ugQIEa0pLspB9UJArW/vgOSpIWM9/OQ/fg8z5XuMxN6Md4DL1/iLypMNSageA1x0TRdt89+D1N1dALpg5XRCXLFbC84TLi0gjlFuib9ibPKzEhLT+anCRJ6iZMzeupDSoaCVbAtJMoDvXw4+4AcRZ1+k4MybBLyCib5boaEOt4pTT88mz4Kk0YaMwPVJyg9Qv36VqyUcPS09Yd95LuyVQ4+tZt8Y1ccbIzULsK+sLM3hLCzxlmlpN3dQBlZJiiRtQde0mgGAKyC0P0A1XjuDTywcsa5edB+fTk1Dsewz9xZ9V0NmMz8t+UNZnaSsAPga9i86jULbXUUwMVSzVRc+Xgx02liB/8qI1xYC9FM6ilStt7rn7mF0k3KbiWhcptgeXjO6Lah9FjEKd5w4MXsdUSTi/86rQaLo+kj+XdaTrXCTulKHyRyQEUj+8V1w0oVz7pcGjePHd7y5oU9ByifVQy6sytuFBfRZvugM5bKHo+i0pcWvixrZS42DrzwxZJsspANOvqSe5ifVbvOkfUppQdCBIwptxV5N1b49XPKU3W/w34QJ8xGmKp3TFA7WwVCztriFHjPgiRpB3EG99Bg= sudo: required - services: - docker - install: - sudo service docker stop - sudo curl https://get.docker.com/builds/Linux/x86_64/docker-1.10.1 -o /usr/bin/docker @@ -18,16 +16,13 @@ install: - sudo service docker start - pip install --user mkdocs - pip install --user pymdown-extensions - before_script: - make validate - make binary - script: - make test-unit - make test-integration - make crossbinary - make image - after_success: -- make deploy \ No newline at end of file +- make deploy diff --git a/.travis/traefik.id_rsa.enc b/.travis/traefik.id_rsa.enc index f7edf637f..1a1bb76c4 100644 Binary files a/.travis/traefik.id_rsa.enc and b/.travis/traefik.id_rsa.enc differ diff --git a/docs/basics.md b/docs/basics.md index b7d44a17d..230f83909 100644 --- a/docs/basics.md +++ b/docs/basics.md @@ -59,13 +59,14 @@ Here is an example of entrypoints definition: A frontend is a set of rules that forwards the incoming traffic from an entrypoint to a backend. Frontends can be defined using the following rules: -- `Headers`: Headers adds a matcher for request header values. It accepts a sequence of key/value pairs to be matched. For example: `Content-Type, application/json` -- `HeadersRegexp`: Regular expressions can be used with headers as well. It accepts a sequence of key/value pairs, where the value has regex support. For example: `Content-Type, application/(text|json)` -- `Host`: Host adds a matcher for the URL host. It accepts a template with zero or more URL variables enclosed by `{}`. Variables can define an optional regexp pattern to be matched: `www.traefik.io`, `{subdomain:[a-z]+}.traefik.io` -- `Methods`: Methods adds a matcher for HTTP methods. It accepts a sequence of one or more methods to be matched, e.g.: `GET`, `POST`, `PUT` -- `Path`: Path adds a matcher for the URL path. It accepts a template with zero or more URL variables enclosed by `{}`. The template must start with a `/`. For exemple `/products/` `/articles/{category}/{id:[0-9]+}` +- `Headers: Content-Type, application/json`: Headers adds a matcher for request header values. It accepts a sequence of key/value pairs to be matched. +- `HeadersRegexp: Content-Type, application/(text|json)`: Regular expressions can be used with headers as well. It accepts a sequence of key/value pairs, where the value has regex support. +- `Host: traefik.io, www.traefik.io`: Match request host with given host list. +- `HostRegexp: traefik.io, {subdomain:[a-z]+}.traefik.io`: Adds a matcher for the URL hosts. It accepts templates with zero or more URL variables enclosed by `{}`. Variables can define an optional regexp pattern to be matched. +- `Method: GET, POST, PUT`: Methods adds a matcher for HTTP methods. It accepts a sequence of one or more methods to be matched. +- `Path: /products/, /articles/{category}/{id:[0-9]+}`: Path adds a matcher for the URL paths. It accepts templates with zero or more URL variables enclosed by `{}`. - `PathStrip`: Same as `Path` but strip the given prefix from the request URL's Path. -- `PathPrefix`: PathPrefix adds a matcher for the URL path prefix. This matches if the given template is a prefix of the full URL path. +- `PathPrefix`: PathPrefix adds a matcher for the URL path prefixes. This matches if the given template is a prefix of the full URL path. - `PathPrefixStrip`: Same as `PathPrefix` but strip the given prefix from the request URL's Path. You can optionally enable `passHostHeader` to forward client `Host` header to the backend. @@ -77,21 +78,21 @@ Here is an example of frontends definition: [frontends.frontend1] backend = "backend2" [frontends.frontend1.routes.test_1] - rule = "Host:test.localhost" + rule = "Host: test.localhost, test2.localhost" [frontends.frontend2] backend = "backend1" passHostHeader = true entrypoints = ["https"] # overrides defaultEntryPoints [frontends.frontend2.routes.test_1] - rule = "Host:{subdomain:[a-z]+}.localhost" + rule = "Host: localhost, {subdomain:[a-z]+}.localhost" [frontends.frontend3] backend = "backend2" rule = "Path:/test" ``` - Three frontends are defined: `frontend1`, `frontend2` and `frontend3` -- `frontend1` will forward the traffic to the `backend2` if the rule `Host:test.localhost` is matched -- `frontend2` will forward the traffic to the `backend1` if the rule `Host:{subdomain:[a-z]+}.localhost` is matched (forwarding client `Host` header to the backend) +- `frontend1` will forward the traffic to the `backend2` if the rule `Host: test.localhost, test2.localhost` is matched +- `frontend2` will forward the traffic to the `backend1` if the rule `Host: localhost, {subdomain:[a-z]+}.localhost` is matched (forwarding client `Host` header to the backend) - `frontend3` will forward the traffic to the `backend2` if the rule `Path:/test` is matched ## Backends diff --git a/middlewares/stripPrefix.go b/middlewares/stripPrefix.go index 92eb7a5f1..2a13c78bb 100644 --- a/middlewares/stripPrefix.go +++ b/middlewares/stripPrefix.go @@ -7,18 +7,20 @@ import ( // StripPrefix is a middleware used to strip prefix from an URL request type StripPrefix struct { - Handler http.Handler - Prefix string + Handler http.Handler + Prefixes []string } func (s *StripPrefix) ServeHTTP(w http.ResponseWriter, r *http.Request) { - if p := strings.TrimPrefix(r.URL.Path, s.Prefix); len(p) < len(r.URL.Path) { - r.URL.Path = p - r.RequestURI = r.URL.RequestURI() - s.Handler.ServeHTTP(w, r) - } else { - http.NotFound(w, r) + for _, prefix := range s.Prefixes { + if p := strings.TrimPrefix(r.URL.Path, strings.TrimSpace(prefix)); len(p) < len(r.URL.Path) { + r.URL.Path = p + r.RequestURI = r.URL.RequestURI() + s.Handler.ServeHTTP(w, r) + return + } } + http.NotFound(w, r) } // SetHandler sets handler diff --git a/rules.go b/rules.go index 8d43a884c..29152008a 100644 --- a/rules.go +++ b/rules.go @@ -3,7 +3,9 @@ package main import ( "errors" "github.com/gorilla/mux" + "net/http" "reflect" + "sort" "strings" ) @@ -12,26 +14,65 @@ type Rules struct { route *serverRoute } -func (r *Rules) host(host string) *mux.Route { - return r.route.route.Host(host) +func (r *Rules) host(hosts ...string) *mux.Route { + return r.route.route.MatcherFunc(func(req *http.Request, route *mux.RouteMatch) bool { + for _, host := range hosts { + if strings.EqualFold(req.Host, strings.TrimSpace(host)) { + return true + } + } + return false + }) } -func (r *Rules) path(path string) *mux.Route { - return r.route.route.Path(path) +func (r *Rules) hostRegexp(hosts ...string) *mux.Route { + router := r.route.route.Subrouter() + for _, host := range hosts { + router.Host(strings.TrimSpace(host)) + } + return r.route.route } -func (r *Rules) pathPrefix(path string) *mux.Route { - return r.route.route.PathPrefix(path) +func (r *Rules) path(paths ...string) *mux.Route { + router := r.route.route.Subrouter() + for _, path := range paths { + router.Path(strings.TrimSpace(path)) + } + return r.route.route } -func (r *Rules) pathStrip(path string) *mux.Route { - r.route.stripPrefix = path - return r.route.route.Path(path) +func (r *Rules) pathPrefix(paths ...string) *mux.Route { + router := r.route.route.Subrouter() + for _, path := range paths { + router.PathPrefix(strings.TrimSpace(path)) + } + return r.route.route } -func (r *Rules) pathPrefixStrip(path string) *mux.Route { - r.route.stripPrefix = path - return r.route.route.PathPrefix(path) +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) pathPrefixStrip(paths ...string) *mux.Route { + sort.Sort(bySize(paths)) + r.route.stripPrefixes = paths + router := r.route.route.Subrouter() + for _, path := range paths { + router.PathPrefix(strings.TrimSpace(path)) + } + return r.route.route } func (r *Rules) methods(methods ...string) *mux.Route { @@ -50,32 +91,33 @@ func (r *Rules) headersRegexp(headers ...string) *mux.Route { func (r *Rules) Parse(expression string) (*mux.Route, error) { functions := map[string]interface{}{ "Host": r.host, + "HostRegexp": r.hostRegexp, "Path": r.path, "PathStrip": r.pathStrip, "PathPrefix": r.pathPrefix, "PathPrefixStrip": r.pathPrefixStrip, - "Methods": r.methods, + "Method": r.methods, "Headers": r.headers, "HeadersRegexp": r.headersRegexp, } f := func(c rune) bool { - return c == ':' || c == '=' + return c == ':' } // get function parsedFunctions := strings.FieldsFunc(expression, f) - if len(parsedFunctions) != 2 { + if len(parsedFunctions) == 0 { 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]) } - + parsedFunctions = append(parsedFunctions[:0], parsedFunctions[1:]...) fargs := func(c rune) bool { return c == ',' || c == ';' } // get function - parsedArgs := strings.FieldsFunc(parsedFunctions[1], fargs) + parsedArgs := strings.FieldsFunc(strings.Join(parsedFunctions, ":"), fargs) if len(parsedArgs) == 0 { return nil, errors.New("Error parsing args from rule: " + expression) } @@ -86,7 +128,11 @@ func (r *Rules) Parse(expression string) (*mux.Route, error) { } method := reflect.ValueOf(parsedFunction) if method.IsValid() { - return method.Call(inputs)[0].Interface().(*mux.Route), nil + resultRoute := method.Call(inputs)[0].Interface().(*mux.Route) + if resultRoute.GetError() != nil { + return nil, resultRoute.GetError() + } + return resultRoute, nil } return nil, errors.New("Method not found: " + parsedFunctions[0]) } diff --git a/script/deploy.sh b/script/deploy.sh index 4bd811c12..7b5515ecb 100755 --- a/script/deploy.sh +++ b/script/deploy.sh @@ -10,7 +10,7 @@ fi # load ssh key echo "Loading key..." -openssl aes-256-cbc -K $encrypted_27087ae1f4db_key -iv $encrypted_27087ae1f4db_iv -in .travis/traefik.id_rsa.enc -out ~/.ssh/traefik.id_rsa -d +openssl aes-256-cbc -d -k "$pass" -in .travis/traefik.id_rsa.enc -out ~/.ssh/traefik.id_rsa eval "$(ssh-agent -s)" chmod 600 ~/.ssh/traefik.id_rsa ssh-add ~/.ssh/traefik.id_rsa diff --git a/server.go b/server.go index 820968ff3..c9dbbc551 100644 --- a/server.go +++ b/server.go @@ -57,8 +57,8 @@ type serverEntryPoint struct { } type serverRoute struct { - route *mux.Route - stripPrefix string + route *mux.Route + stripPrefixes []string } // NewServer returns an initialized Server. @@ -425,7 +425,7 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo } // retry ? if globalConfiguration.Retry != nil { - retries := len(configuration.Backends[frontend.Backend].Servers) - 1 + retries := len(configuration.Backends[frontend.Backend].Servers) if globalConfiguration.Retry.Attempts > 0 { retries = globalConfiguration.Retry.Attempts } @@ -471,10 +471,10 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo func (server *Server) wireFrontendBackend(serverRoute *serverRoute, handler http.Handler) { // strip prefix - if len(serverRoute.stripPrefix) > 0 { + if len(serverRoute.stripPrefixes) > 0 { serverRoute.route.Handler(&middlewares.StripPrefix{ - Prefix: serverRoute.stripPrefix, - Handler: handler, + Prefixes: serverRoute.stripPrefixes, + Handler: handler, }) } else { serverRoute.route.Handler(handler) diff --git a/traefik.sample.toml b/traefik.sample.toml index 945f963a3..6141d1ea0 100644 --- a/traefik.sample.toml +++ b/traefik.sample.toml @@ -513,7 +513,7 @@ # [frontends.frontend1] # backend = "backend2" # [frontends.frontend1.routes.test_1] -# rule = "Host:test.localhost" +# rule = "Host: test.localhost, other.localhost" # [frontends.frontend2] # backend = "backend1" # passHostHeader = true @@ -523,4 +523,4 @@ # [frontends.frontend3] # entrypoints = ["http", "https"] # overrides defaultEntryPoints # backend = "backend2" -# rule = "Path:/test" \ No newline at end of file +# rule = "Path: /test, /other" \ No newline at end of file