Files
Denozordec 0aa440477e
Publish telemt-api gateway Docker image / test (push) Successful in 29s
Publish telemt-api gateway Docker image / build-and-push (push) Successful in 2m17s
Refactor Mihomo proxy to support WebSocket upgrades and enhance alias forwarding
- Introduced a new function `newAliasForward` to handle alias remapping for users, improving the flexibility of the proxy.
- Updated `NewMihomoForward` to differentiate between HTTP and WebSocket requests, ensuring proper handling of both types.
- Enhanced comments for clarity on the proxy behavior and request handling, improving maintainability.
2026-03-31 01:13:36 +07:00

139 lines
3.6 KiB
Go

package proxy
import (
"fmt"
"io"
"net/http"
"net/textproto"
"net/url"
"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 {
return newAliasForward(target, stripPrefix, pathPrefix, auth, rt, errHandler, true)
}
// newAliasForward is the shared HTTP forwarder. remapUsers maps top-level "users" → "stats/users"
// for Telemt; Mihomo must pass false so /users is not rewritten.
func newAliasForward(
target *url.URL,
stripPrefix, pathPrefix, auth string,
rt http.RoundTripper,
errHandler func(http.ResponseWriter, *http.Request, error),
remapUsers bool,
) 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 remapUsers && (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",
}