diff --git a/middleware/proxy/proxy.go b/middleware/proxy/proxy.go index fcb00abe..3efcf603 100644 --- a/middleware/proxy/proxy.go +++ b/middleware/proxy/proxy.go @@ -57,6 +57,10 @@ func (uh *UpstreamHost) Down() bool { return uh.CheckDown(uh) } +// tryDuration is how long to try upstream hosts; failures result in +// immediate retries until this duration ends or we get a nil host. +var tryDuration = 60 * time.Second + // ServeHTTP satisfies the middleware.Handler interface. func (p Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { @@ -68,7 +72,7 @@ func (p Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { // Since Select() should give us "up" hosts, keep retrying // hosts until timeout (or until we get a nil host). - for time.Now().Sub(start) < (60 * time.Second) { + for time.Now().Sub(start) < tryDuration { host := upstream.Select() if host == nil { return http.StatusBadGateway, errUnreachable diff --git a/middleware/proxy/proxy_test.go b/middleware/proxy/proxy_test.go index 55abe2dc..18e2034b 100644 --- a/middleware/proxy/proxy_test.go +++ b/middleware/proxy/proxy_test.go @@ -4,10 +4,13 @@ import ( "bufio" "bytes" "io" + "io/ioutil" + "log" "net" "net/http" "net/http/httptest" "net/url" + "os" "strings" "testing" "time" @@ -15,6 +18,70 @@ import ( "golang.org/x/net/websocket" ) +func init() { + tryDuration = 50 * time.Millisecond // prevent tests from hanging +} + +func TestReverseProxy(t *testing.T) { + log.SetOutput(ioutil.Discard) + defer log.SetOutput(os.Stderr) + + var requestReceived bool + backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + requestReceived = true + w.Write([]byte("Hello, client")) + })) + defer backend.Close() + + // set up proxy + p := &Proxy{ + Upstreams: []Upstream{newFakeUpstream(backend.URL, false)}, + } + + // create request and response recorder + r, err := http.NewRequest("GET", "/", nil) + if err != nil { + t.Fatalf("Failed to create request: %v", err) + } + w := httptest.NewRecorder() + + p.ServeHTTP(w, r) + + if !requestReceived { + t.Error("Expected backend to receive request, but it didn't") + } +} + +func TestReverseProxyInsecureSkipVerify(t *testing.T) { + log.SetOutput(ioutil.Discard) + defer log.SetOutput(os.Stderr) + + var requestReceived bool + backend := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + requestReceived = true + w.Write([]byte("Hello, client")) + })) + defer backend.Close() + + // set up proxy + p := &Proxy{ + Upstreams: []Upstream{newFakeUpstream(backend.URL, true)}, + } + + // create request and response recorder + r, err := http.NewRequest("GET", "/", nil) + if err != nil { + t.Fatalf("Failed to create request: %v", err) + } + w := httptest.NewRecorder() + + p.ServeHTTP(w, r) + + if !requestReceived { + t.Error("Even with insecure HTTPS, expected backend to receive request, but it didn't") + } +} + func TestWebSocketReverseProxyServeHTTPHandler(t *testing.T) { // No-op websocket backend simply allows the WS connection to be // accepted then it will be immediately closed. Perfect for testing. @@ -93,18 +160,24 @@ func TestWebSocketReverseProxyFromWSClient(t *testing.T) { } } -// newWebSocketTestProxy returns a test proxy that will -// redirect to the specified backendAddr. The function -// also sets up the rules/environment for testing WebSocket -// proxy. -func newWebSocketTestProxy(backendAddr string) *Proxy { - return &Proxy{ - Upstreams: []Upstream{&fakeUpstream{name: backendAddr}}, +func newFakeUpstream(name string, insecure bool) *fakeUpstream { + uri, _ := url.Parse(name) + u := &fakeUpstream{ + name: name, + host: &UpstreamHost{ + Name: name, + ReverseProxy: NewSingleHostReverseProxy(uri, ""), + }, } + if insecure { + u.host.ReverseProxy.Transport = InsecureTransport + } + return u } type fakeUpstream struct { name string + host *UpstreamHost } func (u *fakeUpstream) From() string { @@ -112,6 +185,32 @@ func (u *fakeUpstream) From() string { } func (u *fakeUpstream) Select() *UpstreamHost { + return u.host +} + +func (u *fakeUpstream) IsAllowedPath(requestPath string) bool { + return true +} + +// newWebSocketTestProxy returns a test proxy that will +// redirect to the specified backendAddr. The function +// also sets up the rules/environment for testing WebSocket +// proxy. +func newWebSocketTestProxy(backendAddr string) *Proxy { + return &Proxy{ + Upstreams: []Upstream{&fakeWsUpstream{name: backendAddr}}, + } +} + +type fakeWsUpstream struct { + name string +} + +func (u *fakeWsUpstream) From() string { + return "/" +} + +func (u *fakeWsUpstream) Select() *UpstreamHost { uri, _ := url.Parse(u.name) return &UpstreamHost{ Name: u.name, @@ -122,7 +221,7 @@ func (u *fakeUpstream) Select() *UpstreamHost { } } -func (u *fakeUpstream) IsAllowedPath(requestPath string) bool { +func (u *fakeWsUpstream) IsAllowedPath(requestPath string) bool { return true }