-
Notifications
You must be signed in to change notification settings - Fork 617
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
Implement redirect capability #395
Conversation
Thx a lot. A number of users have been asking for this. I’ll have a look tonight. |
Yes, we were just discussing doing this last week but it looks like you beat us to it. Thank you, this is one of the few things we're still using HAProxy for on the frontend. |
Happy to help! I hope the syntax for indicating a redirect is acceptable. It's not even close to what was discussed in #87 but it enables you to redirect to any given URL, which I find valuable. Also what's up with travis? The failure doesn't seem legit. |
Looks like golang |
lets disable |
This is doing the right thing in the I assume you need to make the changes for matching the ports in order to do the
I'd also like to see an integration test in https://github.com/fabiolb/fabio/blob/master/proxy/http_integration_test.go |
It may also make sense to get #385 fixed first. I have the patch but need to add the test. |
Ok I'll see what I can get done on this tonight. Understood regarding tests.. will add those too.
Correct. This makes it possible to define a route explicitly on http but not https (and vice versa), which was required for this feature. My understanding is that the |
The |
I've just merged #388 so you want to rebase. |
I implemented your suggestions, minus the |
Got the kids today. So this might be tomorrow. |
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 left some comments but mostly realized that the the syntax I suggested earlier is not sufficient since you need to be able to specify a redirect target. I'm suggesting
redirect=302,http://www.bar.com/foo
but I'm open for suggestions.
route/route.go
Outdated
@@ -134,7 +136,12 @@ func contains(src, dst []string) bool { | |||
} | |||
|
|||
func (r *Route) TargetConfig(t *Target, addWeight bool) string { | |||
s := fmt.Sprintf("route add %s %s %s", t.Service, r.Host+r.Path, t.URL) | |||
s := fmt.Sprintf("route add %s %s", t.Service, r.Host+r.Path) | |||
if t.RedirectCode != 0 { |
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 is still using the old syntax
route/route.go
Outdated
@@ -215,7 +222,7 @@ func (r *Route) weighTargets() { | |||
} | |||
|
|||
// compute the weight for the targets with dynamic weights | |||
dynamic := (1 - sumFixed) / float64(len(r.Targets)-nFixed) | |||
dynamic := float64(1-sumFixed) / float64(len(r.Targets)-nFixed) |
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.
gofmt?
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.
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.
If you divide something by a float64
then the result can never be an int
since Go doesn't auto-convert between numeric types. const
values are the only exception.
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.
Ok, this makes sense. My bad.
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.
No worries. You live and learn :)
route/table_test.go
Outdated
@@ -416,6 +416,9 @@ func TestTableParse(t *testing.T) { | |||
targetURLs := make([]string, len(r.wTargets)) | |||
for i, tg := range r.wTargets { | |||
targetURLs[i] = tg.URL.Scheme + "://" + tg.URL.Host + tg.URL.Path | |||
if tg.RedirectCode != 0 { |
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.
old syntax here as well
route/table.go
Outdated
@@ -273,8 +273,8 @@ func (t Table) route(host, path string) *Route { | |||
|
|||
// normalizeHost returns the hostname from the request | |||
// and removes the default port if present. | |||
func normalizeHost(req *http.Request) string { | |||
host := strings.ToLower(req.Host) | |||
func normalizeHost(host string, req *http.Request) 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.
The reason I've passed in *http.Request
was that I needed both host and the TLS state. If you pass in the host
then you might as well pass in the TLS state, e.g.
func normalizeHost(host string, tls bool) string {
host = strings.ToLower(host)
if !tls && strings.HasSuffix(host, ":80") {
return host[:len(host)-len(":80")]
}
if tls && strings.HasSuffix(host, ":443") {
return host[:len(host)-len(":443")]
}
return host
}
...
normalizeHost(req.Host, req.TLS != 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.
Yup, good call.
@@ -33,6 +33,9 @@ type Target struct { | |||
// URL is the endpoint the service instance listens on | |||
URL *url.URL | |||
|
|||
// RedirectCode is the HTTP status code used for redirects. |
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.
// RedirectCode is the HTTP status code used for redirects.
// When set to a value > 0 the client is redirected to the target url.
route/route.go
Outdated
@@ -68,6 +69,7 @@ func (r *Route) addTarget(service string, targetURL *url.URL, fixedWeight float6 | |||
t.StripPath = opts["strip"] | |||
t.TLSSkipVerify = opts["tlsskipverify"] == "true" | |||
t.Host = opts["host"] | |||
t.RedirectCode, _ = strconv.Atoi(opts["redirect"]) |
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 log the error or catch this somewhere sooner and log it there?
registry/consul/parse_test.go
Outdated
{ | ||
tag: "p-www.bar.com:80/foo https://www.bar.com/ redirect=302", | ||
route: "www.bar.com:80/foo", | ||
opts: "https://www.bar.com/ redirect=302", |
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.
Ah, I forgot that we need to be able to specify the redirect target. How about this?
redirect=302,http://www.bar.com/
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.
Sure I can do that syntax for the consul service tags.
admin/api/routes.go
Outdated
@@ -62,6 +62,9 @@ func (h *RoutesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { | |||
Rate1: tg.Timer.Rate1(), | |||
Pct99: tg.Timer.Percentile(0.99), | |||
} | |||
if tg.RedirectCode != 0 { |
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.
old syntax
registry/consul/service.go
Outdated
@@ -119,6 +119,8 @@ func serviceConfig(client *api.Client, name string, passing map[string]bool, tag | |||
dst := "http://" + addr + "/" | |||
for _, o := range strings.Fields(opts) { | |||
switch { | |||
case strings.Contains(o, "://"): |
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 would then also become more obvious by looking for redirect=xxx
if opts["redirect"] != "" { | ||
t.RedirectCode, err = strconv.Atoi(opts["redirect"]) | ||
if err != nil { | ||
log.Printf("[ERROR] redirect status code should be numeric in 3xx range. Got: %s", opts["redirect"]) |
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.
Ideally these kind of errors should prevent the route from being added, but that would take some refactoring.
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 could be added the route parser but then it would need to treat opts not as an opaque k/v string. I can have a look at this if and when I write a better parser.
You missed one spot:
|
I made the requested changes but we're not done yet unfortunately. As I mentioned in an earlier comment the integration test fails due to the way the |
Yeah, I'm not sure that matters since the target URL should always be absolute. There is no path mangling in fabio since the routing table is usually generated and the |
I think the other thing that bugs me a bit but for which I don't have a good answer right now is the continued divergence between the syntax of the
I need to think about this a bit more. |
That isn't something that I'd want to address in this PR anyway but I'd like to keep in mind. One approach could be to write a template instead of a special syntax.
Then you'd have to learn only one syntax. |
I'm not so sure we always want it to be absolute. Suppose you have these routes
If you hit |
How do other load balancers address that? |
Don't know.. I'll do a bit of research. |
Thx. That'd be nice. It's getting a bit late here in NL. Going to hit the pillow since the kids get up early. We're getting there. |
I can comment on how it's done with HAProxy where we do our
We have that syntax in our main frontend that binds on both 80 and 443 and redirects any request coming in with a When I request |
We could add some magic with a terminating slash or not.
|
The pseudo variable would be very flexible and is also less ambiguous than magic based on trailing vs non-trailing slash paths. |
I really like the pseudo-variable idea |
Pushed changes. Have a close look at http_proxy because I rearranged some code in there since I am no longer using the |
@ctlajoie I will. This looks decent so far but I'll let it bake for a couple more days since I want to think a bit more about edge cases. If me or you can't think of anything major then I'll merge this early next week (Mon or Tue). Sounds good? |
Sounds good. I'm actually using it right now in a semi-production environment (accessed by people working in my office) so it's getting some real-world testing. |
Even better. |
The only thing I can think of that this implementation does not cover would be complex URL rewriting based on regexps (like mod_rewrite). I think it's fair to say that's out of scope. Do you want me to squash everything down to 1 commit before this gets merged? Are there any other changes you want me to make? |
This patch adds support to redirect a request for a matching route to another URL. If the `redirect=<code>` option is set on a route fabio will send a redirect response to the dst address with the given code. The syntax for the `urlprefix-` tag is slightly different since the destination address is usually generated from the service registration stored in Consul. The `$path` pseudo-variable can be used to include the original request URI in the destination target. # redirect /foo to https://www.foo.com/ route add svc /foo https://www.foo.com/ opts "redirect=301" # redirect /foo to https://www.foo.com/ urlprefix-/foo redirect=301,https://www.foo.com/ # redirect /foo to https://www.foo.com/foo urlprefix-/foo redirect=301,https://www.foo.com$path Fixes #87
I've merged this to master. I had to update the demo server to handle the comma in the Thanks a lot for this. This has been a long requested feature. |
@magiconair You might want to know that there was/is a problem with current versions of nomad/consul where if you have Should be fixed in the next nomad release. |
@ctlajoie Thanks. That's good to know. |
NOTE: The syntax described below was not used in the final implementation.
This is my initial attempt to implement the ability to perform redirections (see #87). I decided to indicate a redirect by prefixing the destination with the HTTP status code to be used for redirection, followed by a pipe, followed by the specific URL you want to redirect to. There is no built-in protection against creating redirect loops currently. Here is how you might use it:
Note that these include explicit source ports. To use it on consul services, you have to use multiple service tags.
You can create redirect routes to anywhere.
Looking for feedback.
(Signed CLA)