From 4086ae4039799ac7b887249fae70bef7d152f902 Mon Sep 17 00:00:00 2001 From: Holt Wilkins <5665043+holtwilkins@users.noreply.github.com> Date: Tue, 4 Sep 2018 10:44:15 +1000 Subject: [PATCH] Add $host pseudo variable This adds support for $host, which behaves similarly to $path. The idea with this is to be able to create a global redirect of anything received on, say, http to the https equivalent of what you were trying to hit. --- proxy/http_integration_test.go | 41 +++++++++++++++++++++++++- route/table.go | 1 + route/target.go | 3 ++ route/target_test.go | 54 ++++++++++++++++++++++++++++++++-- 4 files changed, 96 insertions(+), 3 deletions(-) diff --git a/proxy/http_integration_test.go b/proxy/http_integration_test.go index 453d7fdb8..ddc6c4c4c 100644 --- a/proxy/http_integration_test.go +++ b/proxy/http_integration_test.go @@ -262,7 +262,46 @@ func TestProxyHost(t *testing.T) { }) } -func TestRedirect(t *testing.T) { +func TestHostRedirect(t *testing.T) { + routes := "route add https-redir *:80 https://$host$path opts \"redirect=301\"\n" + + tbl, _ := route.NewTable(routes) + + proxy := httptest.NewServer(&HTTPProxy{ + Transport: http.DefaultTransport, + Lookup: func(r *http.Request) *route.Target { + r.Host = "c.com" + return tbl.Lookup(r, "", route.Picker["rr"], route.Matcher["prefix"]) + }, + }) + defer proxy.Close() + + tests := []struct { + req string + wantCode int + wantLoc string + }{ + {req: "/baz", wantCode: 301, wantLoc: "https://c.com/baz"}, + } + + http.DefaultClient.CheckRedirect = func(req *http.Request, via []*http.Request) error { + // do not follow redirects + return http.ErrUseLastResponse + } + + for _, tt := range tests { + resp, _ := mustGet(proxy.URL + tt.req) + if resp.StatusCode != tt.wantCode { + t.Errorf("got status code %d, want %d", resp.StatusCode, tt.wantCode) + } + gotLoc, _ := resp.Location() + if gotLoc.String() != tt.wantLoc { + t.Errorf("got location %s, want %s", gotLoc, tt.wantLoc) + } + } +} + +func TestPathRedirect(t *testing.T) { routes := "route add mock / http://a.com/$path opts \"redirect=301\"\n" routes += "route add mock /foo http://a.com/abc opts \"redirect=301\"\n" routes += "route add mock /bar http://b.com/$path opts \"redirect=302 strip=/bar\"\n" diff --git a/route/table.go b/route/table.go index bb689dcff..08d278401 100644 --- a/route/table.go +++ b/route/table.go @@ -359,6 +359,7 @@ func (t Table) Lookup(req *http.Request, trace string, pick picker, match matche for _, h := range hosts { if target = t.lookup(h, req.URL.Path, trace, pick, match); target != nil { if target.RedirectCode != 0 { + req.URL.Host = req.Host target.BuildRedirectURL(req.URL) // build redirect url and cache in target if target.RedirectURL.Scheme == req.Header.Get("X-Forwarded-Proto") && target.RedirectURL.Host == req.Host && diff --git a/route/target.go b/route/target.go index db9e5244b..08a1e36dc 100644 --- a/route/target.go +++ b/route/target.go @@ -85,4 +85,7 @@ func (t *Target) BuildRedirectURL(requestURL *url.URL) { if t.RedirectURL.Path == "" { t.RedirectURL.Path = "/" } + if strings.Contains(t.RedirectURL.Host, "$host") { + t.RedirectURL.Host = strings.Replace(t.RedirectURL.Host, "$host", requestURL.Host, 1) + } } diff --git a/route/target_test.go b/route/target_test.go index 21ad2bc42..0eed8a0bb 100644 --- a/route/target_test.go +++ b/route/target_test.go @@ -32,6 +32,16 @@ func TestTarget_BuildRedirectURL(t *testing.T) { {req: "/?aaa=1", want: "http://bar.com/a/b/c?foo=bar"}, }, }, + { // simple http -> https redirect with static path + route: "route add redirect *:80/ https://$host/", + tests: []routeTest{ + {req: "/", want: "https://foo.com/"}, + {req: "/abc", want: "https://foo.com/"}, + {req: "/a/b/c", want: "https://foo.com/"}, + {req: "/?aaa=1", want: "https://foo.com/"}, + {req: "/abc/?aaa=1", want: "https://foo.com/"}, + }, + }, { // simple redirect to corresponding path route: "route add svc / http://bar.com/$path", tests: []routeTest{ @@ -42,7 +52,17 @@ func TestTarget_BuildRedirectURL(t *testing.T) { {req: "/abc/?aaa=1", want: "http://bar.com/abc/?aaa=1"}, }, }, - { // same as above but without / before $path + { // simple http -> https redirect to corresponding host & path + route: "route add redirect *:80/ https://$host/$path", + tests: []routeTest{ + {req: "/", want: "https://foo.com/"}, + {req: "/abc", want: "https://foo.com/abc"}, + {req: "/a/b/c", want: "https://foo.com/a/b/c"}, + {req: "/?aaa=1", want: "https://foo.com/?aaa=1"}, + {req: "/abc/?aaa=1", want: "https://foo.com/abc/?aaa=1"}, + }, + }, + { // simple redirect to corresponding path without / before $path route: "route add svc / http://bar.com$path", tests: []routeTest{ {req: "/", want: "http://bar.com/"}, @@ -52,6 +72,16 @@ func TestTarget_BuildRedirectURL(t *testing.T) { {req: "/abc/?aaa=1", want: "http://bar.com/abc/?aaa=1"}, }, }, + { // simple http -> https redirect to corresponding host & path without / before $path + route: "route add redirect *:80/ https://$host$path", + tests: []routeTest{ + {req: "/", want: "https://foo.com/"}, + {req: "/abc", want: "https://foo.com/abc"}, + {req: "/a/b/c", want: "https://foo.com/a/b/c"}, + {req: "/?aaa=1", want: "https://foo.com/?aaa=1"}, + {req: "/abc/?aaa=1", want: "https://foo.com/abc/?aaa=1"}, + }, + }, { // arbitrary subdir on target with $path at end route: "route add svc / http://bar.com/bbb/$path", tests: []routeTest{ @@ -62,7 +92,17 @@ func TestTarget_BuildRedirectURL(t *testing.T) { {req: "/abc/?aaa=1", want: "http://bar.com/bbb/abc/?aaa=1"}, }, }, - { // same as above but without / before $path + { // http -> https redir to corresonding host w/ arbitrary subdir on target with $path at end + route: "route add redirect *:80/ https://$host/bbb/$path", + tests: []routeTest{ + {req: "/", want: "https://foo.com/bbb/"}, + {req: "/abc", want: "https://foo.com/bbb/abc"}, + {req: "/a/b/c", want: "https://foo.com/bbb/a/b/c"}, + {req: "/?aaa=1", want: "https://foo.com/bbb/?aaa=1"}, + {req: "/abc/?aaa=1", want: "https://foo.com/bbb/abc/?aaa=1"}, + }, + }, + { // arbitrary subdir on target with $path at end but without / before $path route: "route add svc / http://bar.com/bbb$path", tests: []routeTest{ {req: "/", want: "http://bar.com/bbb/"}, @@ -72,6 +112,16 @@ func TestTarget_BuildRedirectURL(t *testing.T) { {req: "/abc/?aaa=1", want: "http://bar.com/bbb/abc/?aaa=1"}, }, }, + { // http -> https redir to corresonding host w/ arbitrary subdir on target with $path at end but without / before $path + route: "route add redirect *:80/ https://$host/bbb$path", + tests: []routeTest{ + {req: "/", want: "https://foo.com/bbb/"}, + {req: "/abc", want: "https://foo.com/bbb/abc"}, + {req: "/a/b/c", want: "https://foo.com/bbb/a/b/c"}, + {req: "/?aaa=1", want: "https://foo.com/bbb/?aaa=1"}, + {req: "/abc/?aaa=1", want: "https://foo.com/bbb/abc/?aaa=1"}, + }, + }, { // strip prefix route: "route add svc /stripme http://bar.com/$path opts \"strip=/stripme\"", tests: []routeTest{