diff --git a/admin/api/routes.go b/admin/api/routes.go index c3193831d..10c2ac373 100644 --- a/admin/api/routes.go +++ b/admin/api/routes.go @@ -43,12 +43,12 @@ func (h *RoutesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { var routes []apiRoute for _, host := range hosts { for _, tr := range t[host] { - var opts []string - for k, v := range tr.Opts { - opts = append(opts, k+"="+v) - } - for _, tg := range tr.Targets { + var opts []string + for k, v := range tg.Opts { + opts = append(opts, k+"="+v) + } + ar := apiRoute{ Service: tg.Service, Host: tr.Host, diff --git a/proxy/http_integration_test.go b/proxy/http_integration_test.go index 83076abe4..ff3627398 100644 --- a/proxy/http_integration_test.go +++ b/proxy/http_integration_test.go @@ -117,12 +117,20 @@ func TestProxyStripsPath(t *testing.T) { } } -// TestProxyHost func TestProxyHost(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, r.Host) })) + // create a static route table so that we can see the effect + // of round robin distribution. The other tests generate the + // route table on the fly since order does not matter to them. + routes := "route add mock /hostdst http://a.com/ opts \"host=dst\"\n" + routes += "route add mock /hostcustom http://a.com/ opts \"host=foo.com\"\n" + routes += "route add mock /hostcustom http://b.com/ opts \"host=bar.com\"\n" + routes += "route add mock / http://a.com/" + tbl, _ := route.NewTable(routes) + proxy := httptest.NewServer(&HTTPProxy{ Transport: &http.Transport{ Dial: func(network, addr string) (net.Conn, error) { @@ -131,10 +139,6 @@ func TestProxyHost(t *testing.T) { }, }, Lookup: func(r *http.Request) *route.Target { - routes := "route add mock /hostdst http://a.com/ opts \"host=dst\"\n" - routes += "route add mock /hostcustom http://a.com/ opts \"host=foo.com\"\n" - routes += "route add mock / http://a.com/" - tbl, _ := route.NewTable(routes) return tbl.Lookup(r, "", route.Picker["rr"], route.Matcher["prefix"]) }, }) @@ -151,9 +155,26 @@ func TestProxyHost(t *testing.T) { } proxyHost := proxy.URL[len("http://"):] + + // test that for 'host=dst' the Host header is set to the hostname of the + // target, in this case 'a.com' t.Run("host eq dst", func(t *testing.T) { check(t, "/hostdst", "a.com") }) - t.Run("host is custom", func(t *testing.T) { check(t, "/hostcustom", "foo.com") }) + + // test that without a 'host' option no Host header is set t.Run("no host", func(t *testing.T) { check(t, "/", proxyHost) }) + + // 1. Test that a host header is set when the 'host' option is used. + // + // 2. Test that the host header is set per target, i.e. that different + // targets can have different 'host' options. + // + // The proxy is configured to use "rr" (round-robin) distribution + // for the requests. Therefore, requests to '/hostcustom' will be + // sent to the two different targets in alternating order. + t.Run("host is custom", func(t *testing.T) { + check(t, "/hostcustom", "foo.com") + check(t, "/hostcustom", "bar.com") + }) } func TestProxyLogOutput(t *testing.T) { diff --git a/route/picker_test.go b/route/picker_test.go index 9b4a15649..c55d77308 100644 --- a/route/picker_test.go +++ b/route/picker_test.go @@ -21,8 +21,8 @@ func mustParse(rawurl string) *url.URL { func TestRndPicker(t *testing.T) { r := &Route{Host: "www.bar.com", Path: "/foo"} - r.addTarget("svc", fooDotCom, 0, nil) - r.addTarget("svc", barDotCom, 0, nil) + r.addTarget("svc", fooDotCom, 0, nil, nil) + r.addTarget("svc", barDotCom, 0, nil, nil) tests := []struct { rnd int @@ -45,8 +45,8 @@ func TestRndPicker(t *testing.T) { func TestRRPicker(t *testing.T) { r := &Route{Host: "www.bar.com", Path: "/foo"} - r.addTarget("svc", fooDotCom, 0, nil) - r.addTarget("svc", barDotCom, 0, nil) + r.addTarget("svc", fooDotCom, 0, nil, nil) + r.addTarget("svc", barDotCom, 0, nil, nil) tests := []*url.URL{fooDotCom, barDotCom, fooDotCom, barDotCom, fooDotCom, barDotCom} diff --git a/route/route.go b/route/route.go index ad410197d..152c4d30e 100644 --- a/route/route.go +++ b/route/route.go @@ -26,9 +26,6 @@ type Route struct { // Path is the path prefix from a request uri Path string - // Opts is the raw route options - Opts map[string]string - // Targets contains the list of URLs Targets []*Target @@ -40,7 +37,7 @@ type Route struct { total uint64 } -func (r *Route) addTarget(service string, targetURL *url.URL, fixedWeight float64, tags []string) { +func (r *Route) addTarget(service string, targetURL *url.URL, fixedWeight float64, tags []string, opts map[string]string) { if fixedWeight < 0 { fixedWeight = 0 } @@ -61,15 +58,16 @@ func (r *Route) addTarget(service string, targetURL *url.URL, fixedWeight float6 t := &Target{ Service: service, Tags: tags, + Opts: opts, URL: targetURL, FixedWeight: fixedWeight, Timer: ServiceRegistry.GetTimer(name), TimerName: name, } - if r.Opts != nil { - t.StripPath = r.Opts["strip"] - t.TLSSkipVerify = r.Opts["tlsskipverify"] == "true" - t.Host = r.Opts["host"] + if opts != nil { + t.StripPath = opts["strip"] + t.TLSSkipVerify = opts["tlsskipverify"] == "true" + t.Host = opts["host"] } r.Targets = append(r.Targets, t) @@ -145,16 +143,16 @@ func (r *Route) TargetConfig(t *Target, addWeight bool) string { if len(t.Tags) > 0 { s += fmt.Sprintf(" tags %q", strings.Join(t.Tags, ",")) } - if len(r.Opts) > 0 { + if len(t.Opts) > 0 { var keys []string - for k := range r.Opts { + for k := range t.Opts { keys = append(keys, k) } sort.Strings(keys) var vals []string for _, k := range keys { - vals = append(vals, k+"="+r.Opts[k]) + vals = append(vals, k+"="+t.Opts[k]) } s += fmt.Sprintf(" opts \"%s\"", strings.Join(vals, " ")) } diff --git a/route/table.go b/route/table.go index bd6c3aa22..054185eb5 100644 --- a/route/table.go +++ b/route/table.go @@ -153,20 +153,20 @@ func (t Table) addRoute(d *RouteDef) error { switch { // add new host case t[host] == nil: - r := &Route{Host: host, Path: path, Opts: d.Opts} - r.addTarget(d.Service, targetURL, d.Weight, d.Tags) + r := &Route{Host: host, Path: path} + r.addTarget(d.Service, targetURL, d.Weight, d.Tags, d.Opts) t[host] = Routes{r} // add new route to existing host case t[host].find(path) == nil: - r := &Route{Host: host, Path: path, Opts: d.Opts} - r.addTarget(d.Service, targetURL, d.Weight, d.Tags) + r := &Route{Host: host, Path: path} + r.addTarget(d.Service, targetURL, d.Weight, d.Tags, d.Opts) t[host] = append(t[host], r) sort.Sort(t[host]) // add new target to existing route default: - t[host].find(path).addTarget(d.Service, targetURL, d.Weight, d.Tags) + t[host].find(path).addTarget(d.Service, targetURL, d.Weight, d.Tags, d.Opts) } return nil diff --git a/route/table_test.go b/route/table_test.go index fa35806b7..c631cad37 100644 --- a/route/table_test.go +++ b/route/table_test.go @@ -46,6 +46,28 @@ func TestTableParse(t *testing.T) { }, }, + {"1 service, 1 prefix with option", + []string{ + `route add svc-a / http://aaa.com/ opts "strip=/foo"`, + `route add svc-b / http://bbb.com/ opts "strip=/bar"`, + }, + []string{ + `route add svc-a / http://aaa.com/ weight 0.5000 opts "strip=/foo"`, + `route add svc-b / http://bbb.com/ weight 0.5000 opts "strip=/bar"`, + }, + }, + + {"1 service, 1 prefix, 2 instances with different options", + []string{ + `route add svc-a / http://aaa.com/ opts "strip=/foo"`, + `route add svc-b / http://bbb.com/ opts "strip=/bar"`, + }, + []string{ + `route add svc-a / http://aaa.com/ weight 0.5000 opts "strip=/foo"`, + `route add svc-b / http://bbb.com/ weight 0.5000 opts "strip=/bar"`, + }, + }, + {"2 service, 1 prefix", []string{ `route add svc-a / http://aaa.com/`, diff --git a/route/target.go b/route/target.go index 6b4db5fdb..14b0a3a45 100644 --- a/route/target.go +++ b/route/target.go @@ -13,6 +13,9 @@ type Target struct { // Tags are the list of tags for this target Tags []string + // Opts is the raw options for the target. + Opts map[string]string + // StripPath will be removed from the front of the outgoing // request path StripPath string