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:
@@ -0,0 +1,125 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// NewAliasForward proxies /api/{alias}/… to Telemt using http.NewRequest(fullURL)+RoundTrip,
|
||||
// matching aggregate server-side calls. httputil.ReverseProxy can produce request lines that
|
||||
// strict origin servers reject with 400; this path matches a working curl to base_url.
|
||||
func NewAliasForward(
|
||||
target *url.URL,
|
||||
stripPrefix, pathPrefix, auth string,
|
||||
rt http.RoundTripper,
|
||||
errHandler func(http.ResponseWriter, *http.Request, error),
|
||||
) http.Handler {
|
||||
if errHandler == nil {
|
||||
errHandler = defaultForwardErrorHandler
|
||||
}
|
||||
if rt == nil {
|
||||
rt = http.DefaultTransport
|
||||
}
|
||||
targetQuery := target.RawQuery
|
||||
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
NormalizeRequestURLPath(r)
|
||||
p := r.URL.Path
|
||||
if !strings.HasPrefix(p, stripPrefix) {
|
||||
errHandler(w, r, fmt.Errorf("path %q: missing strip prefix %q", p, stripPrefix))
|
||||
return
|
||||
}
|
||||
rest := strings.TrimPrefix(p, stripPrefix)
|
||||
rest = strings.TrimPrefix(rest, "/")
|
||||
if (r.Method == http.MethodGet || r.Method == http.MethodHead) && rest == "users" {
|
||||
rest = "stats/users"
|
||||
}
|
||||
|
||||
outURL := JoinPathPrefix(target, pathPrefix, rest)
|
||||
u := *outURL
|
||||
if targetQuery == "" || r.URL.RawQuery == "" {
|
||||
u.RawQuery = targetQuery + r.URL.RawQuery
|
||||
} else {
|
||||
u.RawQuery = targetQuery + "&" + r.URL.RawQuery
|
||||
}
|
||||
outURL = &u
|
||||
|
||||
outReq, err := http.NewRequestWithContext(r.Context(), r.Method, outURL.String(), r.Body)
|
||||
if err != nil {
|
||||
errHandler(w, r, err)
|
||||
return
|
||||
}
|
||||
if r.ContentLength >= 0 {
|
||||
outReq.ContentLength = r.ContentLength
|
||||
}
|
||||
outReq.Header = cloneHeader(r.Header)
|
||||
removeConnectionHeaders(outReq.Header)
|
||||
// Do not forward the client's Host (e.g. mtg.ivx.su:8888). Upstream must see the
|
||||
// authority from base_url (e.g. 172.20.0.3:9091); mismatch often yields 400 from strict stacks.
|
||||
outReq.Header.Del("Host")
|
||||
outReq.Host = outURL.Host
|
||||
if auth != "" {
|
||||
outReq.Header.Set("Authorization", auth)
|
||||
}
|
||||
|
||||
resp, err := rt.RoundTrip(outReq)
|
||||
if err != nil {
|
||||
errHandler(w, r, err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
removeConnectionHeaders(resp.Header)
|
||||
for k, vv := range resp.Header {
|
||||
for _, v := range vv {
|
||||
w.Header().Add(k, v)
|
||||
}
|
||||
}
|
||||
w.WriteHeader(resp.StatusCode)
|
||||
_, _ = io.Copy(w, resp.Body)
|
||||
})
|
||||
}
|
||||
|
||||
func defaultForwardErrorHandler(w http.ResponseWriter, _ *http.Request, _ error) {
|
||||
http.Error(w, "bad gateway", http.StatusBadGateway)
|
||||
}
|
||||
|
||||
func cloneHeader(h http.Header) http.Header {
|
||||
h2 := make(http.Header, len(h))
|
||||
for k, vv := range h {
|
||||
cp := make([]string, len(vv))
|
||||
copy(cp, vv)
|
||||
h2[k] = cp
|
||||
}
|
||||
return h2
|
||||
}
|
||||
|
||||
// removeConnectionHeaders mirrors net/http/httputil.ReverseProxy hop-by-hop handling.
|
||||
func removeConnectionHeaders(h http.Header) {
|
||||
if v := h.Get("Connection"); v != "" {
|
||||
for _, f := range strings.Split(v, ",") {
|
||||
if f = textproto.TrimString(f); f != "" {
|
||||
h.Del(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, k := range hopHeaders {
|
||||
h.Del(k)
|
||||
}
|
||||
}
|
||||
|
||||
var hopHeaders = []string{
|
||||
"Connection",
|
||||
"Proxy-Connection",
|
||||
"Keep-Alive",
|
||||
"Proxy-Authenticate",
|
||||
"Proxy-Authorization",
|
||||
"Te",
|
||||
"Trailer",
|
||||
"Trailers",
|
||||
"Transfer-Encoding",
|
||||
"Upgrade",
|
||||
}
|
||||
Reference in New Issue
Block a user