Skip to content

Commit

Permalink
Implement redirection capability
Browse files Browse the repository at this point in the history
  • Loading branch information
ctlajoie committed Nov 29, 2017
1 parent 7b04e10 commit cf30c15
Show file tree
Hide file tree
Showing 9 changed files with 84 additions and 7 deletions.
3 changes: 3 additions & 0 deletions admin/api/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
ar.Dst = fmt.Sprintf("%d|%s", tg.RedirectCode, tg.URL)
}
routes = append(routes, ar)
}
}
Expand Down
39 changes: 39 additions & 0 deletions proxy/http_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,45 @@ func TestProxyHost(t *testing.T) {
})
}

func TestRedirect(t *testing.T) {
routes := "route add mock /foo http://a.com/abc opts \"redirect=301\"\n"
routes += "route add mock /bar http://b.com/ opts \"redirect=302\"\n"
tbl, _ := route.NewTable(routes)

proxy := httptest.NewServer(&HTTPProxy{
Transport: http.DefaultTransport,
Lookup: func(r *http.Request) *route.Target {
return tbl.Lookup(r, "", route.Picker["rr"], route.Matcher["prefix"])
},
})
defer proxy.Close()

tests := []struct {
req string
wantCode int
wantLoc string
}{
{req: "/foo", wantCode: 301, wantLoc: "http://a.com/abc"},
{req: "/bar", wantCode: 302, wantLoc: "http://b.com"},
}

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 TestProxyLogOutput(t *testing.T) {
// build a format string from all log fields and one header field
fields := []string{"header.X-Foo:$header.X-Foo"}
Expand Down
9 changes: 9 additions & 0 deletions proxy/http_proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,15 @@ func (p *HTTPProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
r.Header.Set(p.Config.RequestID, id())
}

if t.RedirectCode != 0 {
http.Redirect(w, r, targetURL.String(), t.RedirectCode)
if t.Timer != nil {
t.Timer.Update(0)
}
metrics.DefaultRegistry.GetTimer(key(t.RedirectCode)).Update(0)
return
}

upgrade, accept := r.Header.Get("Upgrade"), r.Header.Get("Accept")

tr := p.Transport
Expand Down
6 changes: 6 additions & 0 deletions registry/consul/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ func TestParseTag(t *testing.T) {
route: "xx/Yy",
ok: true,
},
{
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",
ok: true,
},
}

for i, tt := range tests {
Expand Down
2 changes: 2 additions & 0 deletions registry/consul/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -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, "://"):
dst = o
case o == "proto=tcp":
dst = "tcp://" + addr
case o == "proto=https":
Expand Down
11 changes: 9 additions & 2 deletions route/route.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"net/url"
"reflect"
"sort"
"strconv"
"strings"

"github.com/fabiolb/fabio/metrics"
Expand Down Expand Up @@ -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"])
}

r.Targets = append(r.Targets, t)
Expand Down Expand Up @@ -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 {
s += fmt.Sprintf(" %d|%s", t.RedirectCode, t.URL)
} else {
s += fmt.Sprintf(" %s", t.URL)
}
if addWeight {
s += fmt.Sprintf(" weight %2.4f", t.Weight)
} else if t.FixedWeight > 0 {
Expand Down Expand Up @@ -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)
if dynamic < 0 {
dynamic = 0
}
Expand Down
9 changes: 5 additions & 4 deletions route/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
host = strings.ToLower(host)
if req.TLS == nil && strings.HasSuffix(host, ":80") {
return host[:len(host)-len(":80")]
}
Expand All @@ -287,9 +287,10 @@ func normalizeHost(req *http.Request) string {
// matchingHosts returns all keys (host name patterns) from the
// routing table which match the normalized request hostname.
func (t Table) matchingHosts(req *http.Request) (hosts []string) {
host := normalizeHost(req)
host := normalizeHost(req.Host, req)
for pattern := range t {
if glob.Glob(pattern, host) {
normpat := normalizeHost(pattern, req)
if glob.Glob(normpat, host) {
hosts = append(hosts, pattern)
}
}
Expand Down
9 changes: 8 additions & 1 deletion route/table_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
targetURLs[i] = fmt.Sprintf("%d|%s", tg.RedirectCode, targetURLs[i])
}
}

// count how often the 'url' from 'route add svc <path> <url>'
Expand Down Expand Up @@ -477,7 +480,7 @@ func TestNormalizeHost(t *testing.T) {
}

for i, tt := range tests {
if got, want := normalizeHost(tt.req), tt.host; got != want {
if got, want := normalizeHost(tt.req.Host, tt.req), tt.host; got != want {
t.Errorf("%d: got %v want %v", i, got, want)
}
}
Expand All @@ -495,6 +498,7 @@ func TestTableLookup(t *testing.T) {
route add svc z.abc.com/foo/ http://foo.com:3100
route add svc *.abc.com/ http://foo.com:4000
route add svc *.abc.com/foo/ http://foo.com:5000
route add svc xyz.com:80/ https://xyz.com
`

tbl, err := NewTable(s)
Expand Down Expand Up @@ -539,6 +543,9 @@ func TestTableLookup(t *testing.T) {

// exact match has precedence over glob match
{&http.Request{Host: "z.abc.com", URL: mustParse("/foo/")}, "http://foo.com:3100"},

// explicit port on route
{&http.Request{Host: "xyz.com", URL: mustParse("/")}, "https://xyz.com"},
}

for i, tt := range tests {
Expand Down
3 changes: 3 additions & 0 deletions route/target.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
RedirectCode int

// FixedWeight is the weight assigned to this target.
// If the value is 0 the targets weight is dynamic.
FixedWeight float64
Expand Down

0 comments on commit cf30c15

Please sign in to comment.