gitlab-org--gitlab-foss/workhorse/proxy_test.go

170 lines
5.8 KiB
Go

package main
import (
"bytes"
"fmt"
"io"
"net"
"net/http"
"net/http/httptest"
"net/url"
"regexp"
"testing"
"time"
"gitlab.com/gitlab-org/gitlab/workhorse/internal/badgateway"
"gitlab.com/gitlab-org/gitlab/workhorse/internal/helper"
"gitlab.com/gitlab-org/gitlab/workhorse/internal/proxy"
"gitlab.com/gitlab-org/gitlab/workhorse/internal/testhelper"
"gitlab.com/gitlab-org/gitlab/workhorse/internal/upstream/roundtripper"
"github.com/stretchr/testify/require"
)
const testVersion = "123"
func newProxy(url string, rt http.RoundTripper, opts ...func(*proxy.Proxy)) *proxy.Proxy {
parsedURL := helper.URLMustParse(url)
if rt == nil {
rt = roundtripper.NewTestBackendRoundTripper(parsedURL)
}
return proxy.NewProxy(parsedURL, testVersion, rt, opts...)
}
func TestProxyRequest(t *testing.T) {
inboundURL, err := url.Parse("https://explicitly.set.host/url/path")
require.NoError(t, err, "parse inbound url")
urlRegexp := regexp.MustCompile(fmt.Sprintf(`%s\z`, inboundURL.Path))
ts := testhelper.TestServerWithHandler(urlRegexp, func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, "POST", r.Method, "method")
require.Equal(t, "test", r.Header.Get("Custom-Header"), "custom header")
require.Equal(t, testVersion, r.Header.Get("Gitlab-Workhorse"), "version header")
require.Equal(t, inboundURL.Host, r.Host, "sent host header")
require.Empty(t, r.Header.Get("X-Forwarded-Host"), "X-Forwarded-Host header")
require.Empty(t, r.Header.Get("Forwarded"), "Forwarded header")
require.Regexp(
t,
regexp.MustCompile(`\A1`),
r.Header.Get("Gitlab-Workhorse-Proxy-Start"),
"expect Gitlab-Workhorse-Proxy-Start to start with 1",
)
body, err := io.ReadAll(r.Body)
require.NoError(t, err, "read body")
require.Equal(t, "REQUEST", string(body), "body contents")
w.Header().Set("Custom-Response-Header", "test")
w.WriteHeader(202)
fmt.Fprint(w, "RESPONSE")
})
httpRequest, err := http.NewRequest("POST", inboundURL.String(), bytes.NewBufferString("REQUEST"))
require.NoError(t, err)
httpRequest.Header.Set("Custom-Header", "test")
w := httptest.NewRecorder()
newProxy(ts.URL, nil).ServeHTTP(w, httpRequest)
require.Equal(t, 202, w.Code)
testhelper.RequireResponseBody(t, w, "RESPONSE")
require.Equal(t, "test", w.Header().Get("Custom-Response-Header"), "custom response header")
}
func TestProxyWithForcedTargetHostHeader(t *testing.T) {
var tsUrl *url.URL
inboundURL, err := url.Parse("https://explicitly.set.host/url/path")
require.NoError(t, err, "parse upstream url")
urlRegexp := regexp.MustCompile(fmt.Sprintf(`%s\z`, inboundURL.Path))
ts := testhelper.TestServerWithHandler(urlRegexp, func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, tsUrl.Host, r.Host, "upstream host header")
require.Equal(t, inboundURL.Host, r.Header.Get("X-Forwarded-Host"), "X-Forwarded-Host header")
require.Equal(t, fmt.Sprintf("host=%s", inboundURL.Host), r.Header.Get("Forwarded"), "Forwarded header")
_, err := w.Write([]byte(`ok`))
require.NoError(t, err, "write ok response")
})
tsUrl, err = url.Parse(ts.URL)
require.NoError(t, err, "parse testserver URL")
httpRequest, err := http.NewRequest("POST", inboundURL.String(), nil)
require.NoError(t, err)
w := httptest.NewRecorder()
testProxy := newProxy(ts.URL, nil, proxy.WithForcedTargetHostHeader())
testProxy.ServeHTTP(w, httpRequest)
testhelper.RequireResponseBody(t, w, "ok")
}
func TestProxyWithCustomHeaders(t *testing.T) {
ts := testhelper.TestServerWithHandler(regexp.MustCompile(`/url/path\z`), func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, "value", r.Header.Get("Custom-Header"), "custom proxy header")
require.Equal(t, testVersion, r.Header.Get("Gitlab-Workhorse"), "version header")
_, err := w.Write([]byte(`ok`))
require.NoError(t, err, "write ok response")
})
httpRequest, err := http.NewRequest("POST", ts.URL+"/url/path", nil)
require.NoError(t, err)
w := httptest.NewRecorder()
testProxy := newProxy(ts.URL, nil, proxy.WithCustomHeaders(map[string]string{"Custom-Header": "value"}))
testProxy.ServeHTTP(w, httpRequest)
testhelper.RequireResponseBody(t, w, "ok")
}
func TestProxyError(t *testing.T) {
httpRequest, err := http.NewRequest("POST", "/url/path", bytes.NewBufferString("REQUEST"))
require.NoError(t, err)
httpRequest.Header.Set("Custom-Header", "test")
w := httptest.NewRecorder()
newProxy("http://localhost:655575/", nil).ServeHTTP(w, httpRequest)
require.Equal(t, 502, w.Code)
require.Regexp(t, regexp.MustCompile("dial tcp:.*invalid port.*"), w.Body.String(), "response body")
}
func TestProxyReadTimeout(t *testing.T) {
ts := testhelper.TestServerWithHandler(nil, func(w http.ResponseWriter, r *http.Request) {
time.Sleep(time.Minute)
})
httpRequest, err := http.NewRequest("POST", "http://localhost/url/path", nil)
require.NoError(t, err)
rt := badgateway.NewRoundTripper(false, &http.Transport{
Proxy: http.ProxyFromEnvironment,
Dial: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
TLSHandshakeTimeout: 10 * time.Second,
ResponseHeaderTimeout: time.Millisecond,
})
p := newProxy(ts.URL, rt)
w := httptest.NewRecorder()
p.ServeHTTP(w, httpRequest)
require.Equal(t, 502, w.Code)
testhelper.RequireResponseBody(t, w, "GitLab is not responding")
}
func TestProxyHandlerTimeout(t *testing.T) {
ts := testhelper.TestServerWithHandler(nil,
http.TimeoutHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
time.Sleep(time.Second)
}), time.Millisecond, "Request took too long").ServeHTTP,
)
httpRequest, err := http.NewRequest("POST", "http://localhost/url/path", nil)
require.NoError(t, err)
w := httptest.NewRecorder()
newProxy(ts.URL, nil).ServeHTTP(w, httpRequest)
require.Equal(t, 503, w.Code)
testhelper.RequireResponseBody(t, w, "Request took too long")
}