Add PathPrefixStrip and PathStrip rules

Signed-off-by: Emile Vauge <emile@vauge.com>
This commit is contained in:
Emile Vauge 2016-02-26 15:29:53 +01:00
parent b84b95fe97
commit 122783e36b
No known key found for this signature in database
GPG key ID: D808B4C167352E59
4 changed files with 82 additions and 36 deletions

View file

@ -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` - `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` - `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]+}` - `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. - `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. A frontend is a set of rules that forwards the incoming http traffic to a backend.

View file

@ -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)
}
}

View file

@ -7,6 +7,16 @@ import (
"crypto/tls" "crypto/tls"
"encoding/json" "encoding/json"
"errors" "errors"
"net/http"
"net/url"
"os"
"os/signal"
"reflect"
"regexp"
"sort"
"sync"
"syscall"
"time"
log "github.com/Sirupsen/logrus" log "github.com/Sirupsen/logrus"
"github.com/codegangsta/negroni" "github.com/codegangsta/negroni"
@ -18,16 +28,6 @@ import (
"github.com/mailgun/oxy/cbreaker" "github.com/mailgun/oxy/cbreaker"
"github.com/mailgun/oxy/forward" "github.com/mailgun/oxy/forward"
"github.com/mailgun/oxy/roundrobin" "github.com/mailgun/oxy/roundrobin"
"net/http"
"net/url"
"os"
"os/signal"
"reflect"
"regexp"
"sort"
"sync"
"syscall"
"time"
) )
var oxyLogger = &OxyLogger{} var oxyLogger = &OxyLogger{}
@ -335,11 +335,11 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
newRoute := serverEntryPoints[entryPointName].httpRouter.NewRoute().Name(frontendName) newRoute := serverEntryPoints[entryPointName].httpRouter.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:%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 { if err != nil {
return nil, err return nil, err
} }
newRoute = newRouteReflect[0].Interface().(*mux.Route) newRoute = route
} }
entryPoint := globalConfiguration.EntryPoints[entryPointName] entryPoint := globalConfiguration.EntryPoints[entryPointName]
if entryPoint.Redirect != nil { if entryPoint.Redirect != nil {
@ -399,7 +399,7 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
} else { } else {
log.Debugf("Reusing backend %s", frontend.Backend) log.Debugf("Reusing backend %s", frontend.Backend)
} }
newRoute.Handler(backends[frontend.Backend]) server.wireFrontendBackend(frontend.Routes, newRoute, backends[frontend.Backend])
} }
err := newRoute.GetError() err := newRoute.GetError()
if err != nil { if err != nil {
@ -411,6 +411,32 @@ 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) {
// 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) { func (server *Server) loadEntryPointConfig(entryPointName string, entryPoint *EntryPoint) (http.Handler, error) {
regex := entryPoint.Redirect.Regex regex := entryPoint.Redirect.Regex
replacement := entryPoint.Redirect.Replacement replacement := entryPoint.Redirect.Replacement
@ -443,9 +469,28 @@ func (server *Server) loadEntryPointConfig(entryPointName string, entryPoint *En
func (server *Server) buildDefaultHTTPRouter() *mux.Router { func (server *Server) buildDefaultHTTPRouter() *mux.Router {
router := mux.NewRouter() router := mux.NewRouter()
router.NotFoundHandler = http.HandlerFunc(notFoundHandler) router.NotFoundHandler = http.HandlerFunc(notFoundHandler)
router.StrictSlash(true)
return router 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 { func sortedFrontendNamesForConfig(configuration *types.Configuration) []string {
keys := []string{} keys := []string{}

View file

@ -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)
}