0
Fork 0
mirror of https://github.com/caddyserver/caddy.git synced 2024-12-23 22:27:38 -05:00

proxy: Add new fallback_delay sub-directive (#2309)

* Updates the existing proxy and reverse proxy tests to include a new fallback delay value

* Adds a new fallback_delay sub-directive to the proxy directive and uses it in the creation of single host reverse proxies
This commit is contained in:
Jake Lucas 2018-10-31 03:02:59 +09:00 committed by Matt Holt
parent 15455e5a7e
commit 22dfb140d0
5 changed files with 61 additions and 30 deletions

View file

@ -47,6 +47,12 @@ type Upstream interface {
// Checks if subpath is not an ignored path // Checks if subpath is not an ignored path
AllowedPath(string) bool AllowedPath(string) bool
// Gets the duration of the headstart the first
// connection is given in the Go standard library's
// implementation of "Happy Eyeballs" when DualStack
// is enabled in net.Dialer.
GetFallbackDelay() time.Duration
// Gets how long to try selecting upstream hosts // Gets how long to try selecting upstream hosts
// in the case of cascading failures. // in the case of cascading failures.
GetTryDuration() time.Duration GetTryDuration() time.Duration
@ -195,6 +201,7 @@ func (p Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
host.WithoutPathPrefix, host.WithoutPathPrefix,
http.DefaultMaxIdleConnsPerHost, http.DefaultMaxIdleConnsPerHost,
upstream.GetTimeout(), upstream.GetTimeout(),
upstream.GetFallbackDelay(),
) )
} }

View file

@ -122,7 +122,7 @@ func TestReverseProxy(t *testing.T) {
// set up proxy // set up proxy
p := &Proxy{ p := &Proxy{
Next: httpserver.EmptyNext, // prevents panic in some cases when test fails Next: httpserver.EmptyNext, // prevents panic in some cases when test fails
Upstreams: []Upstream{newFakeUpstream(backend.URL, false, 30*time.Second)}, Upstreams: []Upstream{newFakeUpstream(backend.URL, false, 30*time.Second, 300*time.Millisecond)},
} }
// Create the fake request body. // Create the fake request body.
@ -202,7 +202,7 @@ func TestReverseProxyInsecureSkipVerify(t *testing.T) {
// set up proxy // set up proxy
p := &Proxy{ p := &Proxy{
Next: httpserver.EmptyNext, // prevents panic in some cases when test fails Next: httpserver.EmptyNext, // prevents panic in some cases when test fails
Upstreams: []Upstream{newFakeUpstream(backend.URL, true, 30*time.Second)}, Upstreams: []Upstream{newFakeUpstream(backend.URL, true, 30*time.Second, 300*time.Millisecond)},
} }
// create request and response recorder // create request and response recorder
@ -289,6 +289,7 @@ func TestReverseProxyMaxConnLimit(t *testing.T) {
func TestReverseProxyTimeout(t *testing.T) { func TestReverseProxyTimeout(t *testing.T) {
timeout := 2 * time.Second timeout := 2 * time.Second
fallbackDelay := 300 * time.Millisecond
errorMargin := 100 * time.Millisecond errorMargin := 100 * time.Millisecond
log.SetOutput(ioutil.Discard) log.SetOutput(ioutil.Discard)
defer log.SetOutput(os.Stderr) defer log.SetOutput(os.Stderr)
@ -296,7 +297,7 @@ func TestReverseProxyTimeout(t *testing.T) {
// set up proxy // set up proxy
p := &Proxy{ p := &Proxy{
Next: httpserver.EmptyNext, // prevents panic in some cases when test fails Next: httpserver.EmptyNext, // prevents panic in some cases when test fails
Upstreams: []Upstream{newFakeUpstream("https://8.8.8.8", true, timeout)}, Upstreams: []Upstream{newFakeUpstream("https://8.8.8.8", true, timeout, fallbackDelay)},
} }
// create request and response recorder // create request and response recorder
@ -711,7 +712,7 @@ func TestUpstreamHeadersUpdate(t *testing.T) {
})) }))
defer backend.Close() defer backend.Close()
upstream := newFakeUpstream(backend.URL, false, 30*time.Second) upstream := newFakeUpstream(backend.URL, false, 30*time.Second, 300*time.Millisecond)
upstream.host.UpstreamHeaders = http.Header{ upstream.host.UpstreamHeaders = http.Header{
"Connection": {"{>Connection}"}, "Connection": {"{>Connection}"},
"Upgrade": {"{>Upgrade}"}, "Upgrade": {"{>Upgrade}"},
@ -778,7 +779,7 @@ func TestDownstreamHeadersUpdate(t *testing.T) {
})) }))
defer backend.Close() defer backend.Close()
upstream := newFakeUpstream(backend.URL, false, 30*time.Second) upstream := newFakeUpstream(backend.URL, false, 30*time.Second, 300*time.Millisecond)
upstream.host.DownstreamHeaders = http.Header{ upstream.host.DownstreamHeaders = http.Header{
"+Merge-Me": {"Merge-Value"}, "+Merge-Me": {"Merge-Value"},
"+Add-Me": {"Add-Value"}, "+Add-Me": {"Add-Value"},
@ -918,7 +919,7 @@ func TestHostSimpleProxyNoHeaderForward(t *testing.T) {
// set up proxy // set up proxy
p := &Proxy{ p := &Proxy{
Next: httpserver.EmptyNext, // prevents panic in some cases when test fails Next: httpserver.EmptyNext, // prevents panic in some cases when test fails
Upstreams: []Upstream{newFakeUpstream(backend.URL, false, 30*time.Second)}, Upstreams: []Upstream{newFakeUpstream(backend.URL, false, 30*time.Second, 300*time.Millisecond)},
} }
r := httptest.NewRequest("GET", "/", nil) r := httptest.NewRequest("GET", "/", nil)
@ -1007,7 +1008,7 @@ func TestHostHeaderReplacedUsingForward(t *testing.T) {
})) }))
defer backend.Close() defer backend.Close()
upstream := newFakeUpstream(backend.URL, false, 30*time.Second) upstream := newFakeUpstream(backend.URL, false, 30*time.Second, 300*time.Millisecond)
proxyHostHeader := "test2.com" proxyHostHeader := "test2.com"
upstream.host.UpstreamHeaders = http.Header{"Host": []string{proxyHostHeader}} upstream.host.UpstreamHeaders = http.Header{"Host": []string{proxyHostHeader}}
// set up proxy // set up proxy
@ -1069,7 +1070,7 @@ func basicAuthTestcase(t *testing.T, upstreamUser, clientUser *url.Userinfo) {
p := &Proxy{ p := &Proxy{
Next: httpserver.EmptyNext, Next: httpserver.EmptyNext,
Upstreams: []Upstream{newFakeUpstream(backURL.String(), false, 30*time.Second)}, Upstreams: []Upstream{newFakeUpstream(backURL.String(), false, 30*time.Second, 300*time.Millisecond)},
} }
r, err := http.NewRequest("GET", "/foo", nil) r, err := http.NewRequest("GET", "/foo", nil)
if err != nil { if err != nil {
@ -1204,7 +1205,7 @@ func TestProxyDirectorURL(t *testing.T) {
continue continue
} }
NewSingleHostReverseProxy(targetURL, c.without, 0, 30*time.Second).Director(req) NewSingleHostReverseProxy(targetURL, c.without, 0, 30*time.Second, 300*time.Millisecond).Director(req)
if expect, got := c.expectURL, req.URL.String(); expect != got { if expect, got := c.expectURL, req.URL.String(); expect != got {
t.Errorf("case %d url not equal: expect %q, but got %q", t.Errorf("case %d url not equal: expect %q, but got %q",
i, expect, got) i, expect, got)
@ -1351,7 +1352,7 @@ func TestCancelRequest(t *testing.T) {
// set up proxy // set up proxy
p := &Proxy{ p := &Proxy{
Next: httpserver.EmptyNext, // prevents panic in some cases when test fails Next: httpserver.EmptyNext, // prevents panic in some cases when test fails
Upstreams: []Upstream{newFakeUpstream(backend.URL, false, 30*time.Second)}, Upstreams: []Upstream{newFakeUpstream(backend.URL, false, 30*time.Second, 300*time.Millisecond)},
} }
// setup request with cancel ctx // setup request with cancel ctx
@ -1400,15 +1401,16 @@ func (r *noopReader) Read(b []byte) (int, error) {
return n, nil return n, nil
} }
func newFakeUpstream(name string, insecure bool, timeout time.Duration) *fakeUpstream { func newFakeUpstream(name string, insecure bool, timeout, fallbackDelay time.Duration) *fakeUpstream {
uri, _ := url.Parse(name) uri, _ := url.Parse(name)
u := &fakeUpstream{ u := &fakeUpstream{
name: name, name: name,
from: "/", from: "/",
timeout: timeout, timeout: timeout,
fallbackDelay: fallbackDelay,
host: &UpstreamHost{ host: &UpstreamHost{
Name: name, Name: name,
ReverseProxy: NewSingleHostReverseProxy(uri, "", http.DefaultMaxIdleConnsPerHost, timeout), ReverseProxy: NewSingleHostReverseProxy(uri, "", http.DefaultMaxIdleConnsPerHost, timeout, fallbackDelay),
}, },
} }
if insecure { if insecure {
@ -1423,6 +1425,7 @@ type fakeUpstream struct {
from string from string
without string without string
timeout time.Duration timeout time.Duration
fallbackDelay time.Duration
} }
func (u *fakeUpstream) From() string { func (u *fakeUpstream) From() string {
@ -1437,13 +1440,14 @@ func (u *fakeUpstream) Select(r *http.Request) *UpstreamHost {
} }
u.host = &UpstreamHost{ u.host = &UpstreamHost{
Name: u.name, Name: u.name,
ReverseProxy: NewSingleHostReverseProxy(uri, u.without, http.DefaultMaxIdleConnsPerHost, u.GetTimeout()), ReverseProxy: NewSingleHostReverseProxy(uri, u.without, http.DefaultMaxIdleConnsPerHost, u.GetTimeout(), u.GetFallbackDelay()),
} }
} }
return u.host return u.host
} }
func (u *fakeUpstream) AllowedPath(requestPath string) bool { return true } func (u *fakeUpstream) AllowedPath(requestPath string) bool { return true }
func (u *fakeUpstream) GetFallbackDelay() time.Duration { return 300 * time.Millisecond }
func (u *fakeUpstream) GetTryDuration() time.Duration { return 1 * time.Second } func (u *fakeUpstream) GetTryDuration() time.Duration { return 1 * time.Second }
func (u *fakeUpstream) GetTryInterval() time.Duration { return 250 * time.Millisecond } func (u *fakeUpstream) GetTryInterval() time.Duration { return 250 * time.Millisecond }
func (u *fakeUpstream) GetTimeout() time.Duration { return u.timeout } func (u *fakeUpstream) GetTimeout() time.Duration { return u.timeout }
@ -1478,6 +1482,7 @@ type fakeWsUpstream struct {
without string without string
insecure bool insecure bool
timeout time.Duration timeout time.Duration
fallbackDelay time.Duration
} }
func (u *fakeWsUpstream) From() string { func (u *fakeWsUpstream) From() string {
@ -1488,7 +1493,7 @@ func (u *fakeWsUpstream) Select(r *http.Request) *UpstreamHost {
uri, _ := url.Parse(u.name) uri, _ := url.Parse(u.name)
host := &UpstreamHost{ host := &UpstreamHost{
Name: u.name, Name: u.name,
ReverseProxy: NewSingleHostReverseProxy(uri, u.without, http.DefaultMaxIdleConnsPerHost, u.GetTimeout()), ReverseProxy: NewSingleHostReverseProxy(uri, u.without, http.DefaultMaxIdleConnsPerHost, u.GetTimeout(), u.GetFallbackDelay()),
UpstreamHeaders: http.Header{ UpstreamHeaders: http.Header{
"Connection": {"{>Connection}"}, "Connection": {"{>Connection}"},
"Upgrade": {"{>Upgrade}"}}, "Upgrade": {"{>Upgrade}"}},
@ -1500,6 +1505,7 @@ func (u *fakeWsUpstream) Select(r *http.Request) *UpstreamHost {
} }
func (u *fakeWsUpstream) AllowedPath(requestPath string) bool { return true } func (u *fakeWsUpstream) AllowedPath(requestPath string) bool { return true }
func (u *fakeWsUpstream) GetFallbackDelay() time.Duration { return 300 * time.Millisecond }
func (u *fakeWsUpstream) GetTryDuration() time.Duration { return 1 * time.Second } func (u *fakeWsUpstream) GetTryDuration() time.Duration { return 1 * time.Second }
func (u *fakeWsUpstream) GetTryInterval() time.Duration { return 250 * time.Millisecond } func (u *fakeWsUpstream) GetTryInterval() time.Duration { return 250 * time.Millisecond }
func (u *fakeWsUpstream) GetTimeout() time.Duration { return u.timeout } func (u *fakeWsUpstream) GetTimeout() time.Duration { return u.timeout }
@ -1548,7 +1554,7 @@ func BenchmarkProxy(b *testing.B) {
})) }))
defer backend.Close() defer backend.Close()
upstream := newFakeUpstream(backend.URL, false, 30*time.Second) upstream := newFakeUpstream(backend.URL, false, 30*time.Second, 300*time.Millisecond)
upstream.host.UpstreamHeaders = http.Header{ upstream.host.UpstreamHeaders = http.Header{
"Hostname": {"{hostname}"}, "Hostname": {"{hostname}"},
"Host": {"{host}"}, "Host": {"{host}"},

View file

@ -148,7 +148,7 @@ func singleJoiningSlash(a, b string) string {
// the target request will be for /base/dir. // the target request will be for /base/dir.
// Without logic: target's path is "/", incoming is "/api/messages", // Without logic: target's path is "/", incoming is "/api/messages",
// without is "/api", then the target request will be for /messages. // without is "/api", then the target request will be for /messages.
func NewSingleHostReverseProxy(target *url.URL, without string, keepalive int, timeout time.Duration) *ReverseProxy { func NewSingleHostReverseProxy(target *url.URL, without string, keepalive int, timeout, fallbackDelay time.Duration) *ReverseProxy {
targetQuery := target.RawQuery targetQuery := target.RawQuery
director := func(req *http.Request) { director := func(req *http.Request) {
if target.Scheme == "unix" { if target.Scheme == "unix" {
@ -234,6 +234,9 @@ func NewSingleHostReverseProxy(target *url.URL, without string, keepalive int, t
if timeout != defaultDialer.Timeout { if timeout != defaultDialer.Timeout {
dialer.Timeout = timeout dialer.Timeout = timeout
} }
if fallbackDelay != defaultDialer.FallbackDelay {
dialer.FallbackDelay = fallbackDelay
}
rp := &ReverseProxy{ rp := &ReverseProxy{
Director: director, Director: director,

View file

@ -67,7 +67,7 @@ func TestSingleSRVHostReverseProxy(t *testing.T) {
} }
port := uint16(pp) port := uint16(pp)
rp := NewSingleHostReverseProxy(target, "", http.DefaultMaxIdleConnsPerHost, 30*time.Second) rp := NewSingleHostReverseProxy(target, "", http.DefaultMaxIdleConnsPerHost, 30*time.Second, 300*time.Millisecond)
rp.srvResolver = testResolver{ rp.srvResolver = testResolver{
result: []*net.SRV{ result: []*net.SRV{
{Target: upstream.Hostname(), Port: port, Priority: 1, Weight: 1}, {Target: upstream.Hostname(), Port: port, Priority: 1, Weight: 1},

View file

@ -49,6 +49,7 @@ type staticUpstream struct {
Hosts HostPool Hosts HostPool
Policy Policy Policy Policy
KeepAlive int KeepAlive int
FallbackDelay time.Duration
Timeout time.Duration Timeout time.Duration
FailTimeout time.Duration FailTimeout time.Duration
TryDuration time.Duration TryDuration time.Duration
@ -227,7 +228,7 @@ func (u *staticUpstream) NewHost(host string) (*UpstreamHost, error) {
return nil, err return nil, err
} }
uh.ReverseProxy = NewSingleHostReverseProxy(baseURL, uh.WithoutPathPrefix, u.KeepAlive, u.Timeout) uh.ReverseProxy = NewSingleHostReverseProxy(baseURL, uh.WithoutPathPrefix, u.KeepAlive, u.Timeout, u.FallbackDelay)
if u.insecureSkipVerify { if u.insecureSkipVerify {
uh.ReverseProxy.UseInsecureTransport() uh.ReverseProxy.UseInsecureTransport()
} }
@ -309,6 +310,15 @@ func parseBlock(c *caddyfile.Dispenser, u *staticUpstream, hasSrv bool) error {
arg = c.Val() arg = c.Val()
} }
u.Policy = policyCreateFunc(arg) u.Policy = policyCreateFunc(arg)
case "fallback_delay":
if !c.NextArg() {
return c.ArgErr()
}
dur, err := time.ParseDuration(c.Val())
if err != nil {
return err
}
u.FallbackDelay = dur
case "fail_timeout": case "fail_timeout":
if !c.NextArg() { if !c.NextArg() {
return c.ArgErr() return c.ArgErr()
@ -620,6 +630,11 @@ func (u *staticUpstream) AllowedPath(requestPath string) bool {
return true return true
} }
// GetFallbackDelay returns u.FallbackDelay.
func (u *staticUpstream) GetFallbackDelay() time.Duration {
return u.FallbackDelay
}
// GetTryDuration returns u.TryDuration. // GetTryDuration returns u.TryDuration.
func (u *staticUpstream) GetTryDuration() time.Duration { func (u *staticUpstream) GetTryDuration() time.Duration {
return u.TryDuration return u.TryDuration