diff --git a/docs/index.md b/docs/index.md index 5b6500757..f122a098b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -37,7 +37,9 @@ Frontends can be defined using the following rules: - `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]+}` +- `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. +- `PathPrefixStrip`: Same as `PathPrefix` but strip the given prefix from the request URL's Path. A frontend is a set of rules that forwards the incoming http traffic to a backend. diff --git a/middlewares/StripPrefix.go b/middlewares/StripPrefix.go new file mode 100644 index 000000000..244d2862a --- /dev/null +++ b/middlewares/StripPrefix.go @@ -0,0 +1,22 @@ +package middlewares + +import ( + "net/http" + "strings" +) + +// StripPrefix is a middleware used to strip prefix from an URL request +type StripPrefix struct { + Handler http.Handler + Prefix 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) + } +} diff --git a/server.go b/server.go index e62eaf356..9a7db602b 100644 --- a/server.go +++ b/server.go @@ -7,6 +7,16 @@ import ( "crypto/tls" "encoding/json" "errors" + "net/http" + "net/url" + "os" + "os/signal" + "reflect" + "regexp" + "sort" + "sync" + "syscall" + "time" log "github.com/Sirupsen/logrus" "github.com/codegangsta/negroni" @@ -18,16 +28,6 @@ import ( "github.com/mailgun/oxy/cbreaker" "github.com/mailgun/oxy/forward" "github.com/mailgun/oxy/roundrobin" - "net/http" - "net/url" - "os" - "os/signal" - "reflect" - "regexp" - "sort" - "sync" - "syscall" - "time" ) var oxyLogger = &OxyLogger{} @@ -335,11 +335,11 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo newRoute := serverEntryPoints[entryPointName].httpRouter.NewRoute().Name(frontendName) for routeName, route := range frontend.Routes { log.Debugf("Creating route %s %s:%s", routeName, route.Rule, route.Value) - newRouteReflect, err := invoke(newRoute, route.Rule, route.Value) + route, err := getRoute(newRoute, route.Rule, route.Value) if err != nil { return nil, err } - newRoute = newRouteReflect[0].Interface().(*mux.Route) + newRoute = route } entryPoint := globalConfiguration.EntryPoints[entryPointName] if entryPoint.Redirect != nil { @@ -399,7 +399,7 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo } else { log.Debugf("Reusing backend %s", frontend.Backend) } - newRoute.Handler(backends[frontend.Backend]) + server.wireFrontendBackend(frontend.Routes, newRoute, backends[frontend.Backend]) } err := newRoute.GetError() if err != nil { @@ -411,6 +411,32 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo return serverEntryPoints, nil } +func (server *Server) wireFrontendBackend(routes map[string]types.Route, newRoute *mux.Route, handler http.Handler) { + // strip prefix + var strip bool + for _, route := range routes { + switch route.Rule { + case "PathStrip": + newRoute.Handler(&middlewares.StripPrefix{ + Prefix: route.Value, + Handler: handler, + }) + strip = true + break + case "PathPrefixStrip": + newRoute.Handler(&middlewares.StripPrefix{ + Prefix: route.Value, + Handler: handler, + }) + strip = true + break + } + } + if !strip { + newRoute.Handler(handler) + } +} + func (server *Server) loadEntryPointConfig(entryPointName string, entryPoint *EntryPoint) (http.Handler, error) { regex := entryPoint.Redirect.Regex replacement := entryPoint.Redirect.Replacement @@ -443,9 +469,28 @@ func (server *Server) loadEntryPointConfig(entryPointName string, entryPoint *En func (server *Server) buildDefaultHTTPRouter() *mux.Router { router := mux.NewRouter() router.NotFoundHandler = http.HandlerFunc(notFoundHandler) + router.StrictSlash(true) return router } +func getRoute(any interface{}, rule string, value ...interface{}) (*mux.Route, error) { + switch rule { + case "PathStrip": + rule = "Path" + case "PathPrefixStrip": + rule = "PathPrefix" + } + inputs := make([]reflect.Value, len(value)) + for i := range value { + 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 { keys := []string{} diff --git a/utils.go b/utils.go deleted file mode 100644 index bed6ae7f8..000000000 --- a/utils.go +++ /dev/null @@ -1,23 +0,0 @@ -/* -Copyright -*/ -package main - -import ( - "errors" - "reflect" -) - -// Invoke calls the specified method with the specified arguments on the specified interface. -// It uses the go(lang) reflect package. -func invoke(any interface{}, name string, args ...interface{}) ([]reflect.Value, error) { - inputs := make([]reflect.Value, len(args)) - for i := range args { - inputs[i] = reflect.ValueOf(args[i]) - } - method := reflect.ValueOf(any).MethodByName(name) - if method.IsValid() { - return method.Call(inputs), nil - } - return nil, errors.New("Method not found: " + name) -}