Merge pull request #1339 from seguins/928-fix-regex-pathstrip
Fix regex with PathStrip
This commit is contained in:
commit
441d5442a1
5 changed files with 156 additions and 17 deletions
|
@ -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.
|
- `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.
|
- `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.
|
- `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.
|
- `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]+}`.
|
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]+}`.
|
||||||
|
|
||||||
|
|
54
middlewares/stripPrefixRegex.go
Normal file
54
middlewares/stripPrefixRegex.go
Normal 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
|
||||||
|
}
|
55
middlewares/stripPrefixRegex_test.go
Normal file
55
middlewares/stripPrefixRegex_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -75,6 +75,16 @@ func (r *Rules) pathStrip(paths ...string) *mux.Route {
|
||||||
return r.route.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 {
|
func (r *Rules) replacePath(paths ...string) *mux.Route {
|
||||||
for _, path := range paths {
|
for _, path := range paths {
|
||||||
r.route.replacePath = path
|
r.route.replacePath = path
|
||||||
|
@ -99,6 +109,16 @@ func (r *Rules) pathPrefixStrip(paths ...string) *mux.Route {
|
||||||
return r.route.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 {
|
func (r *Rules) methods(methods ...string) *mux.Route {
|
||||||
return r.route.route.Methods(methods...)
|
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 {
|
func (r *Rules) parseRules(expression string, onRule func(functionName string, function interface{}, arguments []string) error) error {
|
||||||
functions := map[string]interface{}{
|
functions := map[string]interface{}{
|
||||||
"Host": r.host,
|
"Host": r.host,
|
||||||
"HostRegexp": r.hostRegexp,
|
"HostRegexp": r.hostRegexp,
|
||||||
"Path": r.path,
|
"Path": r.path,
|
||||||
"PathStrip": r.pathStrip,
|
"PathStrip": r.pathStrip,
|
||||||
"PathPrefix": r.pathPrefix,
|
"PathStripRegex": r.pathStripRegex,
|
||||||
"PathPrefixStrip": r.pathPrefixStrip,
|
"PathPrefix": r.pathPrefix,
|
||||||
"Method": r.methods,
|
"PathPrefixStrip": r.pathPrefixStrip,
|
||||||
"Headers": r.headers,
|
"PathPrefixStripRegex": r.pathPrefixStripRegex,
|
||||||
"HeadersRegexp": r.headersRegexp,
|
"Method": r.methods,
|
||||||
"AddPrefix": r.addPrefix,
|
"Headers": r.headers,
|
||||||
"ReplacePath": r.replacePath,
|
"HeadersRegexp": r.headersRegexp,
|
||||||
|
"AddPrefix": r.addPrefix,
|
||||||
|
"ReplacePath": r.replacePath,
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(expression) == 0 {
|
if len(expression) == 0 {
|
||||||
|
|
|
@ -62,10 +62,11 @@ type serverEntryPoint struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type serverRoute struct {
|
type serverRoute struct {
|
||||||
route *mux.Route
|
route *mux.Route
|
||||||
stripPrefixes []string
|
stripPrefixes []string
|
||||||
addPrefix string
|
stripPrefixesRegex []string
|
||||||
replacePath string
|
addPrefix string
|
||||||
|
replacePath string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewServer returns an initialized Server.
|
// 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
|
// path replace
|
||||||
if len(serverRoute.replacePath) > 0 {
|
if len(serverRoute.replacePath) > 0 {
|
||||||
handler = &middlewares.ReplacePath{
|
handler = &middlewares.ReplacePath{
|
||||||
|
|
Loading…
Reference in a new issue