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.
This commit is contained in:
@@ -17,6 +17,18 @@ func NewAliasForward(
|
|||||||
stripPrefix, pathPrefix, auth string,
|
stripPrefix, pathPrefix, auth string,
|
||||||
rt http.RoundTripper,
|
rt http.RoundTripper,
|
||||||
errHandler func(http.ResponseWriter, *http.Request, error),
|
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 {
|
) http.Handler {
|
||||||
if errHandler == nil {
|
if errHandler == nil {
|
||||||
errHandler = defaultForwardErrorHandler
|
errHandler = defaultForwardErrorHandler
|
||||||
@@ -35,7 +47,7 @@ func NewAliasForward(
|
|||||||
}
|
}
|
||||||
rest := strings.TrimPrefix(p, stripPrefix)
|
rest := strings.TrimPrefix(p, stripPrefix)
|
||||||
rest = strings.TrimPrefix(rest, "/")
|
rest = strings.TrimPrefix(rest, "/")
|
||||||
if (r.Method == http.MethodGet || r.Method == http.MethodHead) && rest == "users" {
|
if remapUsers && (r.Method == http.MethodGet || r.Method == http.MethodHead) && rest == "users" {
|
||||||
rest = "stats/users"
|
rest = "stats/users"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,15 +8,42 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewMihomoForward proxies /api/{alias}/mihomo/... to Mihomo external-controller (REST + WebSocket).
|
// NewMihomoForward proxies /api/{alias}/mihomo/... to Mihomo external-controller.
|
||||||
// Используется Rewrite (Go 1.20+): очищается RequestURI и задаётся абсолютный URL — иначе строгий upstream
|
//
|
||||||
// и WebSocket upgrade могут отвечать 400 (см. аналогично Telemt в forward.go).
|
// REST и обычные GET/POST идут тем же путём, что и NewAliasForward (http.NewRequest + RoundTrip):
|
||||||
|
// httputil.ReverseProxy в таком режиме часто даёт 400 на строгих upstream (в т.ч. Mihomo), хотя curl к ним работает.
|
||||||
|
// Только WebSocket (traffic, memory, …) остаётся на ReverseProxy + Rewrite.
|
||||||
func NewMihomoForward(
|
func NewMihomoForward(
|
||||||
target *url.URL,
|
target *url.URL,
|
||||||
stripPrefix string,
|
stripPrefix string,
|
||||||
auth string,
|
auth string,
|
||||||
rt http.RoundTripper,
|
rt http.RoundTripper,
|
||||||
errHandler func(http.ResponseWriter, *http.Request, error),
|
errHandler func(http.ResponseWriter, *http.Request, error),
|
||||||
|
) http.Handler {
|
||||||
|
httpH := newAliasForward(target, stripPrefix, "", auth, rt, errHandler, false)
|
||||||
|
wsH := newMihomoWebSocketReverseProxy(target, stripPrefix, auth, rt, errHandler)
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if isWebSocketUpgrade(r) {
|
||||||
|
wsH.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
httpH.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func isWebSocketUpgrade(r *http.Request) bool {
|
||||||
|
if r == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return strings.EqualFold(r.Header.Get("Upgrade"), "websocket")
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMihomoWebSocketReverseProxy(
|
||||||
|
target *url.URL,
|
||||||
|
stripPrefix string,
|
||||||
|
auth string,
|
||||||
|
rt http.RoundTripper,
|
||||||
|
errHandler func(http.ResponseWriter, *http.Request, error),
|
||||||
) http.Handler {
|
) http.Handler {
|
||||||
if errHandler == nil {
|
if errHandler == nil {
|
||||||
errHandler = defaultForwardErrorHandler
|
errHandler = defaultForwardErrorHandler
|
||||||
@@ -26,7 +53,6 @@ func NewMihomoForward(
|
|||||||
}
|
}
|
||||||
rp := &httputil.ReverseProxy{
|
rp := &httputil.ReverseProxy{
|
||||||
Rewrite: func(pr *httputil.ProxyRequest) {
|
Rewrite: func(pr *httputil.ProxyRequest) {
|
||||||
// Нормализуем Out, не In (контракт httputil.ProxyRequest: In не трогать).
|
|
||||||
NormalizeRequestURLPath(pr.Out)
|
NormalizeRequestURLPath(pr.Out)
|
||||||
p := pr.Out.URL.Path
|
p := pr.Out.URL.Path
|
||||||
if !strings.HasPrefix(p, stripPrefix) {
|
if !strings.HasPrefix(p, stripPrefix) {
|
||||||
@@ -38,8 +64,6 @@ func NewMihomoForward(
|
|||||||
du.RawQuery = pr.Out.URL.RawQuery
|
du.RawQuery = pr.Out.URL.RawQuery
|
||||||
out := pr.Out
|
out := pr.Out
|
||||||
out.URL = &du
|
out.URL = &du
|
||||||
// Как в NewAliasForward: не оставлять Host клиента ни в поле, ни в Header —
|
|
||||||
// иначе строгий upstream (в т.ч. Mihomo) часто отвечает 400.
|
|
||||||
out.Header.Del("Host")
|
out.Header.Del("Host")
|
||||||
out.Host = du.Host
|
out.Host = du.Host
|
||||||
out.RequestURI = ""
|
out.RequestURI = ""
|
||||||
@@ -51,8 +75,8 @@ func NewMihomoForward(
|
|||||||
out.Header.Set("Authorization", auth)
|
out.Header.Set("Authorization", auth)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Transport: rt,
|
Transport: rt,
|
||||||
FlushInterval: -1,
|
FlushInterval: -1,
|
||||||
ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) {
|
ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) {
|
||||||
errHandler(w, r, err)
|
errHandler(w, r, err)
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user