Fix regex with PathStrip

This commit is contained in:
Stéphane Seguin 2017-03-24 12:07:59 +01:00 committed by Fernandez Ludovic
parent 74925ba996
commit bf3673879f
5 changed files with 156 additions and 17 deletions

View file

@ -104,9 +104,11 @@ Following is the list of existing matcher rules along with examples:
- `HostRegexp: traefik.io, {subdomain:[a-z]+}.traefik.io`: Match request host. It accepts a sequence of literal and regular expression hosts.
- `Method: GET, POST, PUT`: Match request HTTP method. It accepts a sequence of HTTP methods.
- `Path: /products/, /articles/{category}/{id:[0-9]+}`: Match exact request path. It accepts a sequence of literal and regular expression paths.
- `PathStrip: /products/, /articles/{category}/{id:[0-9]+}`: Match exact path and strip off the path prior to forwarding the request to the backend. It accepts a sequence of literal and regular expression paths.
- `PathStrip: /products/`: Match exact path and strip off the path prior to forwarding the request to the backend. It accepts a sequence of literal paths.
- `PathStripRegex: /articles/{category}/{id:[0-9]+}`: Match exact path and strip off the path prior to forwarding the request to the backend. It accepts a sequence of literal and regular expression paths.
- `PathPrefix: /products/, /articles/{category}/{id:[0-9]+}`: Match request prefix path. It accepts a sequence of literal and regular expression prefix paths.
- `PathPrefixStrip: /products/, /articles/{category}/{id:[0-9]+}`: Match request prefix path and strip off the path prefix prior to forwarding the request to the backend. It accepts a sequence of literal and regular expression prefix paths. Starting with Traefik 1.3, the stripped prefix path will be available in the `X-Forwarded-Prefix` header.
- `PathPrefixStrip: /products/`: Match request prefix path and strip off the path prefix prior to forwarding the request to the backend. It accepts a sequence of literal prefix paths. Starting with Traefik 1.3, the stripped prefix path will be available in the `X-Forwarded-Prefix` header.
- `PathPrefixStripRegex: /articles/{category}/{id:[0-9]+}`: Match request prefix path and strip off the path prefix prior to forwarding the request to the backend. It accepts a sequence of literal and regular expression prefix paths. Starting with Traefik 1.3, the stripped prefix path will be available in the `X-Forwarded-Prefix` header.
In order to use regular expressions with Host and Path matchers, you must declare an arbitrarily named variable followed by the colon-separated regular expression, all enclosed in curly braces. Any pattern supported by [Go's regexp package](https://golang.org/pkg/regexp/) may be used. Example: `/posts/{id:[0-9]+}`.

View file

@ -0,0 +1,54 @@
package middlewares
import (
"net/http"
"github.com/containous/mux"
"github.com/containous/traefik/log"
)
// StripPrefixRegex is a middleware used to strip prefix from an URL request
type StripPrefixRegex struct {
Handler http.Handler
router *mux.Router
}
// NewStripPrefixRegex builds a new StripPrefixRegex given a handler and prefixes
func NewStripPrefixRegex(handler http.Handler, prefixes []string) *StripPrefixRegex {
stripPrefix := StripPrefixRegex{Handler: handler, router: mux.NewRouter()}
for _, prefix := range prefixes {
stripPrefix.router.PathPrefix(prefix)
}
return &stripPrefix
}
func (s *StripPrefixRegex) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var match mux.RouteMatch
if s.router.Match(r, &match) {
params := make([]string, 0, len(match.Vars)*2)
for key, val := range match.Vars {
params = append(params, key)
params = append(params, val)
}
prefix, err := match.Route.URL(params...)
if err != nil || len(prefix.Path) > len(r.URL.Path) {
log.Error("Error in stripPrefix middleware", err)
return
}
r.URL.Path = r.URL.Path[len(prefix.Path):]
r.Header[forwardedPrefixHeader] = []string{prefix.Path}
r.RequestURI = r.URL.RequestURI()
s.Handler.ServeHTTP(w, r)
return
}
http.NotFound(w, r)
}
// SetHandler sets handler
func (s *StripPrefixRegex) SetHandler(Handler http.Handler) {
s.Handler = Handler
}

View file

@ -0,0 +1,55 @@
package middlewares
import (
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
)
func TestStripPrefixRegex(t *testing.T) {
handlerPath := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, r.URL.Path)
})
handler := NewStripPrefixRegex(handlerPath, []string{"/a/api/", "/b/{regex}/", "/c/{category}/{id:[0-9]+}/"})
server := httptest.NewServer(handler)
defer server.Close()
tests := []struct {
expectedCode int
expectedResponse string
url string
}{
{url: "/a/test", expectedCode: 404, expectedResponse: "404 page not found\n"},
{url: "/a/api/test", expectedCode: 200, expectedResponse: "test"},
{url: "/b/api/", expectedCode: 200, expectedResponse: ""},
{url: "/b/api/test1", expectedCode: 200, expectedResponse: "test1"},
{url: "/b/api2/test2", expectedCode: 200, expectedResponse: "test2"},
{url: "/c/api/123/", expectedCode: 200, expectedResponse: ""},
{url: "/c/api/123/test3", expectedCode: 200, expectedResponse: "test3"},
{url: "/c/api/abc/test4", expectedCode: 404, expectedResponse: "404 page not found\n"},
}
for _, test := range tests {
resp, err := http.Get(server.URL + test.url)
if err != nil {
t.Fatal(err)
}
if resp.StatusCode != test.expectedCode {
t.Fatalf("Received non-%d response: %d\n", test.expectedCode, resp.StatusCode)
}
response, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
if test.expectedResponse != string(response) {
t.Errorf("Expected '%s' : '%s'\n", test.expectedResponse, response)
}
}
}

View file

@ -75,6 +75,16 @@ func (r *Rules) pathStrip(paths ...string) *mux.Route {
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(strings.TrimSpace(path))
}
return r.route.route
}
func (r *Rules) replacePath(paths ...string) *mux.Route {
for _, path := range paths {
r.route.replacePath = path
@ -99,6 +109,16 @@ func (r *Rules) pathPrefixStrip(paths ...string) *mux.Route {
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(strings.TrimSpace(path))
}
return r.route.route
}
func (r *Rules) methods(methods ...string) *mux.Route {
return r.route.route.Methods(methods...)
}
@ -113,17 +133,19 @@ func (r *Rules) headersRegexp(headers ...string) *mux.Route {
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,
"PathPrefix": r.pathPrefix,
"PathPrefixStrip": r.pathPrefixStrip,
"Method": r.methods,
"Headers": r.headers,
"HeadersRegexp": r.headersRegexp,
"AddPrefix": r.addPrefix,
"ReplacePath": r.replacePath,
"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,
}
if len(expression) == 0 {

View file

@ -62,10 +62,11 @@ type serverEntryPoint struct {
}
type serverRoute struct {
route *mux.Route
stripPrefixes []string
addPrefix string
replacePath string
route *mux.Route
stripPrefixes []string
stripPrefixesRegex []string
addPrefix string
replacePath string
}
// NewServer returns an initialized Server.
@ -807,6 +808,11 @@ func (server *Server) wireFrontendBackend(serverRoute *serverRoute, handler http
}
}
// strip prefix with regex
if len(serverRoute.stripPrefixesRegex) > 0 {
handler = middlewares.NewStripPrefixRegex(handler, serverRoute.stripPrefixesRegex)
}
// path replace
if len(serverRoute.replacePath) > 0 {
handler = &middlewares.ReplacePath{