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:
@@ -2,8 +2,6 @@ package proxy
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
@@ -11,7 +9,7 @@ import (
|
||||
// NormalizeRequestURLPath collapses duplicate slashes and dot-segments in req.URL.Path
|
||||
// (path.Clean) and clears RawPath so strip-prefix routing matches the client path even
|
||||
// when the request line contained "//" (e.g. /api//mtg/health). Without this, the path
|
||||
// may not match /api/{alias} and SingleHostReverseProxy forwards a wrong path upstream.
|
||||
// may not match /api/{alias} and alias routing fails.
|
||||
func NormalizeRequestURLPath(req *http.Request) {
|
||||
if req == nil || req.URL == nil {
|
||||
return
|
||||
@@ -29,78 +27,3 @@ func NormalizeRequestURLPath(req *http.Request) {
|
||||
u.Path = c
|
||||
u.RawPath = ""
|
||||
}
|
||||
|
||||
// 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 + 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()
|
||||
orig := proxy.Director
|
||||
targetQuery := target.RawQuery
|
||||
proxy.Director = func(req *http.Request) {
|
||||
NormalizeRequestURLPath(req)
|
||||
p := req.URL.Path
|
||||
if !strings.HasPrefix(p, stripPrefix) {
|
||||
orig(req)
|
||||
if setAuth != "" {
|
||||
req.Header.Set("Authorization", setAuth)
|
||||
}
|
||||
// Server-side requests carry RequestURI; client RoundTrip rejects it with URL.Host set.
|
||||
req.RequestURI = ""
|
||||
req.Header.Del("Host")
|
||||
req.Host = req.URL.Host
|
||||
return
|
||||
}
|
||||
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
|
||||
req.URL.Path = joined.Path
|
||||
req.URL.RawPath = joined.RawPath
|
||||
req.URL.Opaque = ""
|
||||
// Match Host header to authority; clear stale map entry (e.g. from httptest.NewRequest).
|
||||
req.Header.Del("Host")
|
||||
req.Host = req.URL.Host
|
||||
if targetQuery == "" || req.URL.RawQuery == "" {
|
||||
req.URL.RawQuery = targetQuery + req.URL.RawQuery
|
||||
} else {
|
||||
req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
|
||||
}
|
||||
if setAuth != "" {
|
||||
req.Header.Set("Authorization", setAuth)
|
||||
}
|
||||
req.RequestURI = ""
|
||||
}
|
||||
return proxy
|
||||
}
|
||||
|
||||
func buildUpstreamURL(target *url.URL, pathPrefix, rest string) *url.URL {
|
||||
rel := strings.Trim(pathPrefix, "/")
|
||||
if rest != "" {
|
||||
if rel != "" {
|
||||
rel = rel + "/" + rest
|
||||
} else {
|
||||
rel = rest
|
||||
}
|
||||
}
|
||||
var parts []string
|
||||
for _, seg := range strings.Split(rel, "/") {
|
||||
if seg != "" {
|
||||
parts = append(parts, seg)
|
||||
}
|
||||
}
|
||||
if len(parts) == 0 {
|
||||
out := *target
|
||||
return &out
|
||||
}
|
||||
return target.JoinPath(parts...)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user