Refactor reverse proxy and enhance API routing
- Replaced the existing reverse proxy implementation with a new alias forwarding mechanism, improving path handling and request normalization. - Updated the gateway to utilize the new forwarding approach, ensuring consistent handling of API requests and proper error management. - Enhanced tests to validate the new routing behavior, including handling of double slashes and user endpoint requests. - Improved documentation in GATEWAY_RUN.md to clarify the updated API routing and configuration requirements.
This commit is contained in:
@@ -9,9 +9,6 @@ import (
|
||||
)
|
||||
|
||||
// captureTransport records the outgoing request and returns 200 without dialing.
|
||||
// httptest.ResponseRecorder + ReverseProxy + real httptest.Server can yield flaky
|
||||
// or environment-dependent failures (proxy env, request-line quirks); we assert
|
||||
// Director output directly instead.
|
||||
type captureTransport struct {
|
||||
got *http.Request
|
||||
}
|
||||
@@ -25,21 +22,20 @@ func (c *captureTransport) RoundTrip(req *http.Request) (*http.Response, error)
|
||||
}, nil
|
||||
}
|
||||
|
||||
func TestDirectorDoubleSlashPathMatchesStripPrefix(t *testing.T) {
|
||||
func TestForwardDoubleSlashPathMatchesStripPrefix(t *testing.T) {
|
||||
target, err := url.Parse("http://127.0.0.1:9")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cap := &captureTransport{}
|
||||
rp := NewReverseProxy(target, "/api/mtg", "/v1", "")
|
||||
rp.Transport = cap
|
||||
h := NewAliasForward(target, "/api/mtg", "/v1", "", cap, nil)
|
||||
|
||||
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "http://127.0.0.1:9/", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req.URL.Path = "/api//mtg/health"
|
||||
rp.ServeHTTP(httptest.NewRecorder(), req)
|
||||
h.ServeHTTP(httptest.NewRecorder(), req)
|
||||
|
||||
if cap.got == nil {
|
||||
t.Fatal("no outgoing request captured")
|
||||
@@ -51,20 +47,19 @@ func TestDirectorDoubleSlashPathMatchesStripPrefix(t *testing.T) {
|
||||
assertSameURL(t, cap.got.URL, want)
|
||||
}
|
||||
|
||||
func TestDirectorRewritesPath(t *testing.T) {
|
||||
func TestForwardRewritesPath(t *testing.T) {
|
||||
target, err := url.Parse("http://127.0.0.1:9")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cap := &captureTransport{}
|
||||
rp := NewReverseProxy(target, "/api/main_srv", "/v1", "")
|
||||
rp.Transport = cap
|
||||
h := NewAliasForward(target, "/api/main_srv", "/v1", "", cap, nil)
|
||||
|
||||
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "http://127.0.0.1:9/api/main_srv/health", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rp.ServeHTTP(httptest.NewRecorder(), req)
|
||||
h.ServeHTTP(httptest.NewRecorder(), req)
|
||||
|
||||
if cap.got == nil {
|
||||
t.Fatal("no outgoing request captured")
|
||||
@@ -76,20 +71,19 @@ func TestDirectorRewritesPath(t *testing.T) {
|
||||
assertSameURL(t, cap.got.URL, want)
|
||||
}
|
||||
|
||||
func TestDirectorRewritesPathWithAPIBasePath(t *testing.T) {
|
||||
func TestForwardRewritesPathWithAPIBasePath(t *testing.T) {
|
||||
target, err := url.Parse("http://127.0.0.1:9/api/")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cap := &captureTransport{}
|
||||
rp := NewReverseProxy(target, "/api/main_srv", "/v1", "")
|
||||
rp.Transport = cap
|
||||
h := NewAliasForward(target, "/api/main_srv", "/v1", "", cap, nil)
|
||||
|
||||
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "http://127.0.0.1:9/api/main_srv/health", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rp.ServeHTTP(httptest.NewRecorder(), req)
|
||||
h.ServeHTTP(httptest.NewRecorder(), req)
|
||||
|
||||
if cap.got == nil {
|
||||
t.Fatal("no outgoing request captured")
|
||||
@@ -101,20 +95,19 @@ func TestDirectorRewritesPathWithAPIBasePath(t *testing.T) {
|
||||
assertSameURL(t, cap.got.URL, want)
|
||||
}
|
||||
|
||||
func TestDirectorGETUsersListUsesStatsUsers(t *testing.T) {
|
||||
func TestForwardGETUsersListUsesStatsUsers(t *testing.T) {
|
||||
target, err := url.Parse("http://127.0.0.1:9")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cap := &captureTransport{}
|
||||
rp := NewReverseProxy(target, "/api/mtg", "/v1", "")
|
||||
rp.Transport = cap
|
||||
h := NewAliasForward(target, "/api/mtg", "/v1", "", cap, nil)
|
||||
|
||||
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "http://127.0.0.1:9/api/mtg/users", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rp.ServeHTTP(httptest.NewRecorder(), req)
|
||||
h.ServeHTTP(httptest.NewRecorder(), req)
|
||||
|
||||
if cap.got == nil {
|
||||
t.Fatal("no outgoing request captured")
|
||||
@@ -126,20 +119,19 @@ func TestDirectorGETUsersListUsesStatsUsers(t *testing.T) {
|
||||
assertSameURL(t, cap.got.URL, want)
|
||||
}
|
||||
|
||||
func TestDirectorPOSTUsersCreateNotRewritten(t *testing.T) {
|
||||
func TestForwardPOSTUsersCreateNotRewritten(t *testing.T) {
|
||||
target, err := url.Parse("http://127.0.0.1:9")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cap := &captureTransport{}
|
||||
rp := NewReverseProxy(target, "/api/mtg", "/v1", "")
|
||||
rp.Transport = cap
|
||||
h := NewAliasForward(target, "/api/mtg", "/v1", "", cap, nil)
|
||||
|
||||
req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, "http://127.0.0.1:9/api/mtg/users", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rp.ServeHTTP(httptest.NewRecorder(), req)
|
||||
h.ServeHTTP(httptest.NewRecorder(), req)
|
||||
|
||||
if cap.got == nil {
|
||||
t.Fatal("no outgoing request captured")
|
||||
@@ -151,20 +143,19 @@ func TestDirectorPOSTUsersCreateNotRewritten(t *testing.T) {
|
||||
assertSameURL(t, cap.got.URL, want)
|
||||
}
|
||||
|
||||
func TestDirectorGETUsersByNameNotRewritten(t *testing.T) {
|
||||
func TestForwardGETUsersByNameNotRewritten(t *testing.T) {
|
||||
target, err := url.Parse("http://127.0.0.1:9")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cap := &captureTransport{}
|
||||
rp := NewReverseProxy(target, "/api/mtg", "/v1", "")
|
||||
rp.Transport = cap
|
||||
h := NewAliasForward(target, "/api/mtg", "/v1", "", cap, nil)
|
||||
|
||||
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "http://127.0.0.1:9/api/mtg/users/alice", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rp.ServeHTTP(httptest.NewRecorder(), req)
|
||||
h.ServeHTTP(httptest.NewRecorder(), req)
|
||||
|
||||
if cap.got == nil {
|
||||
t.Fatal("no outgoing request captured")
|
||||
@@ -176,20 +167,19 @@ func TestDirectorGETUsersByNameNotRewritten(t *testing.T) {
|
||||
assertSameURL(t, cap.got.URL, want)
|
||||
}
|
||||
|
||||
func TestDirectorRewritesNestedStatsUsers(t *testing.T) {
|
||||
func TestForwardNestedStatsUsers(t *testing.T) {
|
||||
target, err := url.Parse("http://127.0.0.1:9/api/")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cap := &captureTransport{}
|
||||
rp := NewReverseProxy(target, "/api/gt2", "/v1", "")
|
||||
rp.Transport = cap
|
||||
h := NewAliasForward(target, "/api/gt2", "/v1", "", cap, nil)
|
||||
|
||||
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "http://127.0.0.1:9/api/gt2/stats/users", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rp.ServeHTTP(httptest.NewRecorder(), req)
|
||||
h.ServeHTTP(httptest.NewRecorder(), req)
|
||||
|
||||
if cap.got == nil {
|
||||
t.Fatal("no outgoing request captured")
|
||||
@@ -201,9 +191,32 @@ func TestDirectorRewritesNestedStatsUsers(t *testing.T) {
|
||||
assertSameURL(t, cap.got.URL, want)
|
||||
}
|
||||
|
||||
// TestBuildUpstreamURLRoundTrip checks that a URL built like the Director does is
|
||||
// accepted by net/http against httptest (no ReverseProxy). Isolates JoinPath + server.
|
||||
func TestBuildUpstreamURLRoundTrip(t *testing.T) {
|
||||
func TestForwardOutgoingHostIsUpstreamAuthority(t *testing.T) {
|
||||
target, err := url.Parse("http://172.20.0.3:9091")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cap := &captureTransport{}
|
||||
h := NewAliasForward(target, "/api/mtg", "/v1", "", cap, nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "http://public.example/api/mtg/health", nil)
|
||||
req.Host = "public.example:8888"
|
||||
req.Header.Set("Host", "public.example:8888")
|
||||
h.ServeHTTP(httptest.NewRecorder(), req)
|
||||
if cap.got == nil {
|
||||
t.Fatal("no outgoing request captured")
|
||||
}
|
||||
if cap.got.URL.Host != "172.20.0.3:9091" {
|
||||
t.Fatalf("URL.Host=%q", cap.got.URL.Host)
|
||||
}
|
||||
if cap.got.Host != "172.20.0.3:9091" {
|
||||
t.Fatalf("Request.Host=%q want 172.20.0.3:9091", cap.got.Host)
|
||||
}
|
||||
if h := cap.got.Header.Get("Host"); h != "" {
|
||||
t.Fatalf("Header Host should be empty (use Request.Host); got %q", h)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJoinPathPrefixRoundTrip(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/v1/health" {
|
||||
http.Error(w, "bad path", http.StatusBadRequest)
|
||||
@@ -217,7 +230,7 @@ func TestBuildUpstreamURLRoundTrip(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
joined := buildUpstreamURL(base, "/v1", "health")
|
||||
joined := JoinPathPrefix(base, "/v1", "health")
|
||||
req, err := http.NewRequest(http.MethodGet, joined.String(), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -237,7 +250,6 @@ func assertSameURL(t *testing.T, got, want *url.URL) {
|
||||
if got == nil || want == nil {
|
||||
t.Fatalf("nil URL: got=%v want=%v", got, want)
|
||||
}
|
||||
// JoinPath vs url.Parse can differ in Path vs RawPath while String() is identical.
|
||||
if got.String() == want.String() {
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user