Enhance API routing and testing for user endpoints
- Updated the reverse proxy to redirect `GET` requests for `/api/{alias}/users` to `/v1/stats/users`, ensuring compatibility with Telemt builds that handle these requests differently.
- Added tests to verify that `GET` and `HEAD` requests to `/api/{alias}/users` are correctly rewritten, while `POST` requests and user-specific retrievals remain unchanged.
- Improved documentation in GATEWAY_RUN.md to clarify the behavior of the API routing and the importance of using the correct base URL.
This commit is contained in:
@@ -9,7 +9,11 @@ import (
|
||||
|
||||
// NewReverseProxy builds a reverse proxy to target base URL with path rewriting:
|
||||
// stripPrefix (/api/{alias}) + pathPrefix (/v1) + remainder, joined onto target via url.JoinPath
|
||||
// (e.g. https://host/api/ + v1 + users → https://host/api/v1/users).
|
||||
// (e.g. https://host/api/ + v1 + health → https://host/v1/health).
|
||||
//
|
||||
// Compatibility: GET/HEAD .../api/{alias}/users (list only, no extra path segment) is sent upstream as
|
||||
// /v1/stats/users. Some Telemt builds treat GET /v1/users incorrectly (e.g. 400) while /v1/stats/users works.
|
||||
// POST .../users (create) and GET .../users/{username} are unchanged.
|
||||
func NewReverseProxy(target *url.URL, stripPrefix, pathPrefix string, setAuth string) *httputil.ReverseProxy {
|
||||
proxy := httputil.NewSingleHostReverseProxy(target)
|
||||
proxy.Transport = DirectTransport()
|
||||
@@ -30,6 +34,9 @@ func NewReverseProxy(target *url.URL, stripPrefix, pathPrefix string, setAuth st
|
||||
}
|
||||
rest := strings.TrimPrefix(p, stripPrefix)
|
||||
rest = strings.TrimPrefix(rest, "/")
|
||||
if (req.Method == http.MethodGet || req.Method == http.MethodHead) && rest == "users" {
|
||||
rest = "stats/users"
|
||||
}
|
||||
joined := buildUpstreamURL(target, pathPrefix, rest)
|
||||
req.URL.Scheme = joined.Scheme
|
||||
req.URL.Host = joined.Host
|
||||
|
||||
@@ -75,6 +75,81 @@ func TestDirectorRewritesPathWithAPIBasePath(t *testing.T) {
|
||||
assertSameURL(t, cap.got.URL, want)
|
||||
}
|
||||
|
||||
func TestDirectorGETUsersListUsesStatsUsers(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
|
||||
|
||||
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)
|
||||
|
||||
if cap.got == nil {
|
||||
t.Fatal("no outgoing request captured")
|
||||
}
|
||||
want, err := url.Parse("http://127.0.0.1:9/v1/stats/users")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertSameURL(t, cap.got.URL, want)
|
||||
}
|
||||
|
||||
func TestDirectorPOSTUsersCreateNotRewritten(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
|
||||
|
||||
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)
|
||||
|
||||
if cap.got == nil {
|
||||
t.Fatal("no outgoing request captured")
|
||||
}
|
||||
want, err := url.Parse("http://127.0.0.1:9/v1/users")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertSameURL(t, cap.got.URL, want)
|
||||
}
|
||||
|
||||
func TestDirectorGETUsersByNameNotRewritten(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
|
||||
|
||||
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)
|
||||
|
||||
if cap.got == nil {
|
||||
t.Fatal("no outgoing request captured")
|
||||
}
|
||||
want, err := url.Parse("http://127.0.0.1:9/v1/users/alice")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertSameURL(t, cap.got.URL, want)
|
||||
}
|
||||
|
||||
func TestDirectorRewritesNestedStatsUsers(t *testing.T) {
|
||||
target, err := url.Parse("http://127.0.0.1:9/api/")
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user