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:
parent
15455e5a7e
commit
22dfb140d0
5 changed files with 61 additions and 30 deletions
|
@ -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(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
@ -1418,11 +1420,12 @@ func newFakeUpstream(name string, insecure bool, timeout time.Duration) *fakeUps
|
||||||
}
|
}
|
||||||
|
|
||||||
type fakeUpstream struct {
|
type fakeUpstream struct {
|
||||||
name string
|
name string
|
||||||
host *UpstreamHost
|
host *UpstreamHost
|
||||||
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 }
|
||||||
|
@ -1474,10 +1478,11 @@ func newPrefixedWebSocketTestProxy(backendAddr string, prefix string) *Proxy {
|
||||||
}
|
}
|
||||||
|
|
||||||
type fakeWsUpstream struct {
|
type fakeWsUpstream struct {
|
||||||
name string
|
name string
|
||||||
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}"},
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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},
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue