-
Notifications
You must be signed in to change notification settings - Fork 5.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fixed ReplacePath rule executing out of order, when combined with PathPrefixStrip #1577
Conversation
4205655
to
dab6b22
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@aantono Thanks for your contribution :)
Few comments, but looks great!
middlewares/addPrefix.go
Outdated
@@ -11,7 +12,9 @@ type AddPrefix struct { | |||
} | |||
|
|||
func (s *AddPrefix) ServeHTTP(w http.ResponseWriter, r *http.Request) { | |||
r.URL.Path = s.Prefix + r.URL.Path | |||
newPath := s.Prefix + r.URL.Path | |||
log.Debugf("Adding Prefix %s to %s = %s", s.Prefix, r.URL.Path, newPath) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you remove this Debug log? This is way too verbose to keep it in production code :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hello @emilevauge, I definitely can, I just added it because I thought it would be very useful to see in the debug mode (in production) how the modifiers get applied. It actually helped a lot while testing the config to make sure it works properly, and once deployed in prod, the debug mode is typically silenced, so the logs are not too verbose. But if you think that's not necessary, I'll surely take it out. Just let me know what your thoughts are on this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@aantono we think it's not necessary and this can be a problem in some case (If you use some logs tools (like Logstash, Graylog,...) you possibly want to always use debug in production).
Could you remove? 😃
middlewares/replace_path.go
Outdated
@@ -15,6 +16,12 @@ const ReplacedPathHeader = "X-Replaced-Path" | |||
|
|||
func (s *ReplacePath) ServeHTTP(w http.ResponseWriter, r *http.Request) { | |||
r.Header.Add(ReplacedPathHeader, r.URL.Path) | |||
log.Debugf("Replacing Path %s with %s", r.URL.Path, s.Path) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you remove this Debug log? This is way too verbose to keep it in production code :)
middlewares/stripPrefix.go
Outdated
@@ -18,6 +19,7 @@ type StripPrefix struct { | |||
func (s *StripPrefix) ServeHTTP(w http.ResponseWriter, r *http.Request) { | |||
for _, prefix := range s.Prefixes { | |||
if p := strings.TrimPrefix(r.URL.Path, strings.TrimSpace(prefix)); len(p) < len(r.URL.Path) { | |||
log.Debugf("Striping Path Prefix %s from %s = %s", prefix, r.URL.Path, p) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you remove this Debug log? This is way too verbose to keep it in production code :)
middlewares/stripPrefixRegex.go
Outdated
@@ -39,7 +39,9 @@ func (s *StripPrefixRegex) ServeHTTP(w http.ResponseWriter, r *http.Request) { | |||
return | |||
} | |||
|
|||
r.URL.Path = r.URL.Path[len(prefix.Path):] | |||
newPath := r.URL.Path[len(prefix.Path):] | |||
log.Debugf("Striping Path Prefix Regex %s from %s = %s", prefix.Path, r.URL.Path, newPath) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you remove this Debug log? This is way too verbose to keep it in production code :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can revert the entire change list block to its original state?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left a few comments.
General question: Should we leave a remark at some place of the documentation describing the behavior when certain modifiers/matchers are used together (including that the order does not matter)?
middlewares/addPrefix.go
Outdated
@@ -1,6 +1,7 @@ | |||
package middlewares | |||
|
|||
import ( | |||
"github.com/containous/traefik/log" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add the non-stdlib imports after the stdlib ones, separated by a blank line?
Similar for other import changes below.
}, | ||
} | ||
|
||
for _, test := range cases { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use sub-tests and call t.Parallel()
inside. (Make sure you don't forget test := test
to capture the loop variable.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You forgot test := test
. :-)
server/server_test.go
Outdated
serverRoute := &serverRoute{route: route} | ||
rules := &Rules{route: serverRoute} | ||
|
||
expression := test.expression //"Host:foo.bar" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need the comment.
server/server_test.go
Outdated
routeResult, err := rules.Parse(expression) | ||
|
||
if err != nil { | ||
t.Fatal("Error while building route for Host:foo.bar") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please include the error.
server/server_test.go
Outdated
t.Fatal("Error while building route for Host:foo.bar") | ||
} | ||
|
||
request, err := http.NewRequest("GET", test.requestURL, nil) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: Use http.MethodGet
instead of the literal method name.
server/server_test.go
Outdated
server := new(Server) | ||
|
||
server.wireFrontendBackend(serverRoute, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
if test.expectedURL != r.URL.String() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In Go, actual comes first, expected second.
Adjust in the next line too please.
server/server_test.go
Outdated
} | ||
|
||
for _, lbMethod := range []string{"Wrr", "Drr"} { | ||
for _, rule := range rules { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You need to have rule := rule
between line 128 and 129.
server/server_test.go
Outdated
}, | ||
} | ||
|
||
for _, lbMethod := range []string{"Wrr", "Drr"} { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd suggest to export loadBalancerMethodNames
and iterate through that.
server/server_test.go
Outdated
EntryPoints: EntryPoints{ | ||
"http": &EntryPoint{}, | ||
}, | ||
HealthCheck: &HealthCheckConfig{Interval: flaeg.Duration(5 * time.Second)}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We shouldn't need the health check for this test.
(By the way: In a singular, unified test, we'd just pass nil
for "your" test cases.)
server/server_test.go
Outdated
} | ||
} | ||
|
||
func TestServerLoadConfigMultipleFrontendRules(t *testing.T) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of duplicating lots of code from TestServerLoadConfigHealthCheckOptions
, can we try to handle both test functions in a single test with separate test cases?
Let me know if you need help.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll remove this test, as I just realized that it is actually testing the same thing as TestServerMultipleFrontendRules
does, so no need to duplicate.
@aantono we'd like this fix to still make it into the 1.3 release. Could you please rebase your PR onto the v1.3 branch? Since some people tend to struggle with rebasing, here's a quick how-to if a simple
I can also do it for you if you like. Just let me know. |
Will rebase and force push as soon as tests finish running locally. Will you cherry-pick the changes back into master, or do you want me to create a separate PR for that? |
We'll merge the v1.3 branch back into master once this PR (and one or two others currently in the making) have landed. So you don't need to worry about getting things into master, I'll handle that. |
Sounds good, just pushed the updates, please let me know if that looks as what you expect. |
602b39a
to
095d1ac
Compare
Also took a stab at adding documentation section about Rules Order, so let me know if that is what you were looking for as well. |
docs/basics.md
Outdated
@@ -191,6 +191,10 @@ backend = "backend2" | |||
rule = "Path:/test1,/test2" | |||
``` | |||
|
|||
### Rules Order | |||
When combining `Modifier` rules with `Matcher` rules, it is important to remember that `Modifier` rules **ALWAYS** apply after the `Matcher` rules. Some `Path*Strip*` rules, like `PathPrefixStripRegex`, or `PathStrip` for example, are both `Matchers` and `Modifiers`, so in this case the `Matcher` portion of the rule will apply first, and the `Modifier` will apply later. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could you add a white line between title and text?
middlewares/addPrefix.go
Outdated
@@ -11,7 +12,9 @@ type AddPrefix struct { | |||
} | |||
|
|||
func (s *AddPrefix) ServeHTTP(w http.ResponseWriter, r *http.Request) { | |||
r.URL.Path = s.Prefix + r.URL.Path | |||
newPath := s.Prefix + r.URL.Path | |||
log.Debugf("Adding Prefix %s to %s = %s", s.Prefix, r.URL.Path, newPath) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@aantono we think it's not necessary and this can be a problem in some case (If you use some logs tools (like Logstash, Graylog,...) you possibly want to always use debug in production).
Could you remove? 😃
middlewares/replace_path.go
Outdated
@@ -1,6 +1,7 @@ | |||
package middlewares | |||
|
|||
import ( | |||
"github.com/containous/traefik/log" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add the non-stdlib imports after the stdlib ones, separated by a blank line?
middlewares/stripPrefix.go
Outdated
@@ -1,6 +1,7 @@ | |||
package middlewares | |||
|
|||
import ( | |||
"github.com/containous/traefik/log" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add the non-stdlib imports after the stdlib ones, separated by a blank line?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another round. :)
middlewares/addPrefix.go
Outdated
@@ -11,7 +11,8 @@ type AddPrefix struct { | |||
} | |||
|
|||
func (s *AddPrefix) ServeHTTP(w http.ResponseWriter, r *http.Request) { | |||
r.URL.Path = s.Prefix + r.URL.Path | |||
newPath := s.Prefix + r.URL.Path | |||
r.URL.Path = newPath |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can revert to using a single line I think.
docs/basics.md
Outdated
### Rules Order | ||
|
||
When combining `Modifier` rules with `Matcher` rules, it is important to remember that `Modifier` rules **ALWAYS** apply after the `Matcher` rules. Some `Path*Strip*` rules, like `PathPrefixStripRegex`, or `PathStrip` for example, are both `Matchers` and `Modifiers`, so in this case the `Matcher` portion of the rule will apply first, and the `Modifier` will apply later. | ||
`Modifiers` will be applied in a pre-determined order, `PathPrefixStripRegex` followed by `PathPrefixStrip`, followed by `AddPrefix` and `ReplacePath` being the last, regardless of their order in the `rule` configuration section. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we have a real list here (one line per item) for better readability?
expectedURL: "http://foo.bar/blah/baz", | ||
}, | ||
{ | ||
expression: "PathPrefixStripRegex:/one/{two}/{three:[0-9]+}", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wow, you are totally right -- we do offer PathPrefixStripRegex
!
I must have missed it as it seemingly only entered the primary documentation when we released 1.3.0-rc1.
So all good. 👍
}, | ||
} | ||
|
||
for _, test := range cases { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You forgot test := test
. :-)
testhelpers/helpers.go
Outdated
// Intp returns a pointer to the given integer value. | ||
func Intp(i int) *int { | ||
return &i | ||
} | ||
|
||
// MustNewRequest creates a new http get request or panics if it can't | ||
func MustNewRequest(rawurl string) *http.Request { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The method should match the parameters of http.NewRequest
, i.e., accept a method and body parameter that would then be passed through.
middlewares/replace_path.go
Outdated
@@ -18,3 +18,8 @@ func (s *ReplacePath) ServeHTTP(w http.ResponseWriter, r *http.Request) { | |||
r.URL.Path = s.Path | |||
s.Handler.ServeHTTP(w, r) | |||
} | |||
|
|||
// SetHandler sets handler | |||
func (s *ReplacePath) SetHandler(Handler http.Handler) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't used anywhere, or is it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be honest, I'm not sure if this is being used or not, just saw it missing from this rule and present in addPrefix.go
and stripPrefix.go
, so added it here for consistency. If you think it's not needed, I'll be glad to remove it. (love deleting code!!!)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should not be in use since you've added it only but there's no call happening.
If I'm wrong, the CI will tell us. :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll go ahead and remove it. Does that mean that it is not really needed in the other 2 places also? (addPrefix.go
and stripPrefix.go
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's my thinking too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've updated the code to remove it from this particular place, so hopefully it's good to go. ;)
server/server_test.go
Outdated
|
||
server.wireFrontendBackend(serverRoute, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
if r.URL.String() != test.expectedURL { | ||
t.Fatalf("Expected URL for [%s]: %s, got %s", test.expression, test.expectedURL, r.URL.String()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please revert the reporting order (got first, expected second) here too.
server/server_test.go
Outdated
if r.URL.String() != test.expectedURL { | ||
t.Fatalf("Expected URL for [%s]: %s, got %s", test.expression, test.expectedURL, r.URL.String()) | ||
} | ||
t.Logf("Received request to: %s", r.URL.String()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMHO we don't need this anymore now that we use sub-tests.
1b14f8e
to
0ff0689
Compare
server/server_test.go
Outdated
routeMatch := routeResult.Match(request, &mux.RouteMatch{Route: routeResult}) | ||
|
||
if !routeMatch { | ||
t.Fatalf("Rule %s don't match", expression) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Grammar: don't -> doesn't.
middlewares/stripPrefixRegex.go
Outdated
@@ -39,7 +39,9 @@ func (s *StripPrefixRegex) ServeHTTP(w http.ResponseWriter, r *http.Request) { | |||
return | |||
} | |||
|
|||
r.URL.Path = r.URL.Path[len(prefix.Path):] | |||
newPath := r.URL.Path[len(prefix.Path):] | |||
log.Debugf("Striping Path Prefix Regex %s from %s = %s", prefix.Path, r.URL.Path, newPath) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can revert the entire change list block to its original state?
server/server_test.go
Outdated
|
||
server.wireFrontendBackend(serverRoute, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
if r.URL.String() != test.expectedURL { | ||
t.Fatalf("Expected URL for [%s]: got %s, expected %s", test.expression, r.URL.String(), test.expectedURL) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't need to mention the expression since the sub test description already includes it and adds it automatically. So let's remove everything before and including the colon.
Let's just say got URL %s, expected %s
.
I see this is still marked as |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks complete from my perspective.
You have my LGTM.
I've rebased the PR against the trunk of v1.3 branch to make it mergable... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @aantono for your great contribution 👏
LGTM!
Addresses issue raised in #1569