Refactor reverse proxy path handling and enhance documentation. Updated reverse.go to improve upstream URL construction and added a new test for nested API routes in reverse_test.go. Revised GATEWAY_RUN.md to clarify API path behavior and configuration requirements for Nginx. Adjusted comments in config.example.yaml for better understanding of HTTPS and API prefix usage.
Publish telemt-api gateway Docker image / test (push) Failing after 36s
Publish telemt-api gateway Docker image / build-and-push (push) Has been skipped

This commit is contained in:
Denozordec
2026-03-30 00:21:16 +07:00
parent 16b1d8148d
commit 5a9b402220
4 changed files with 52 additions and 28 deletions
+27 -27
View File
@@ -4,13 +4,12 @@ import (
"net/http"
"net/http/httputil"
"net/url"
"path"
"strings"
)
// NewReverseProxy builds a reverse proxy to target base URL with path rewriting:
// stripPrefix (/api/{alias}) + pathPrefix (/v1) + remainder, prepended to target.Path
// (so base_url https://host/api/ yields upstream /api/v1/...).
// stripPrefix (/api/{alias}) + pathPrefix (/v1) + remainder, joined onto target via url.JoinPath
// (e.g. https://host/api/ + v1 + users → https://host/api/v1/users).
func NewReverseProxy(target *url.URL, stripPrefix, pathPrefix string, setAuth string) *httputil.ReverseProxy {
proxy := httputil.NewSingleHostReverseProxy(target)
orig := proxy.Director
@@ -26,11 +25,14 @@ func NewReverseProxy(target *url.URL, stripPrefix, pathPrefix string, setAuth st
}
rest := strings.TrimPrefix(p, stripPrefix)
rest = strings.TrimPrefix(rest, "/")
upPath := buildUpstreamPath(target.Path, pathPrefix, rest)
req.URL.Scheme = target.Scheme
req.URL.Host = target.Host
req.URL.Path = upPath
req.URL.RawPath = ""
joined := buildUpstreamURL(target, pathPrefix, rest)
req.URL.Scheme = joined.Scheme
req.URL.Host = joined.Host
req.URL.Path = joined.Path
req.URL.RawPath = joined.RawPath
req.URL.Opaque = ""
// Client must send the same Host as the TLS SNI / nginx server_name.
req.Host = joined.Host
if targetQuery == "" || req.URL.RawQuery == "" {
req.URL.RawQuery = targetQuery + req.URL.RawQuery
} else {
@@ -43,26 +45,24 @@ func NewReverseProxy(target *url.URL, stripPrefix, pathPrefix string, setAuth st
return proxy
}
func buildUpstreamPath(targetPath, pathPrefix, rest string) string {
base := strings.TrimSuffix(targetPath, "/")
p := strings.Trim(pathPrefix, "/")
r := strings.Trim(rest, "/")
var segs []string
if base != "" {
segs = append(segs, base)
func buildUpstreamURL(target *url.URL, pathPrefix, rest string) *url.URL {
rel := strings.Trim(pathPrefix, "/")
if rest != "" {
if rel != "" {
rel = rel + "/" + rest
} else {
rel = rest
}
}
if p != "" {
segs = append(segs, p)
var parts []string
for _, seg := range strings.Split(rel, "/") {
if seg != "" {
parts = append(parts, seg)
}
}
if r != "" {
segs = append(segs, r)
if len(parts) == 0 {
out := *target
return &out
}
if len(segs) == 0 {
return "/"
}
out := path.Join(segs...)
if !strings.HasPrefix(out, "/") {
out = "/" + out
}
return out
return target.JoinPath(parts...)
}
+21
View File
@@ -45,3 +45,24 @@ func TestReverseProxyPathRewriteWithAPIBasePath(t *testing.T) {
t.Fatalf("status %d", rec.Code)
}
}
func TestReverseProxyPathNestedStatsUsers(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/api/v1/stats/users" {
t.Fatalf("path %q", r.URL.Path)
}
w.WriteHeader(http.StatusOK)
}))
defer srv.Close()
up, err := url.Parse(srv.URL + "/api/")
if err != nil {
t.Fatal(err)
}
rp := NewReverseProxy(up, "/api/gt2", "/v1", "")
req := httptest.NewRequest(http.MethodGet, "/api/gt2/stats/users", nil)
rec := httptest.NewRecorder()
rp.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status %d", rec.Code)
}
}