- Enhanced `GATEWAY_RUN.md` with detailed debugging steps for handling **400** errors in Mihomo routes, clarifying the configuration requirements and common pitfalls. - Updated comments in `internal/proxy/mihomo.go` to reflect changes in error handling and proxy behavior, linking to the new debugging section in the documentation. - Modified the Svelte component logic to ensure proper filtering of selectable groups, improving the user interface for managing Mihomo proxies.
107 lines
3.1 KiB
Go
107 lines
3.1 KiB
Go
package proxy
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httputil"
|
|
"net/url"
|
|
"strings"
|
|
)
|
|
|
|
// NewMihomoForward proxies /api/{alias}/mihomo/... to Mihomo external-controller.
|
|
//
|
|
// REST и обычные GET/POST идут тем же путём, что и NewAliasForward (http.NewRequest + RoundTrip):
|
|
// httputil.ReverseProxy для всего Mihomo часто давал 400 на upstream при том, что curl к контроллеру работал.
|
|
// Только WebSocket (traffic, memory, …) остаётся на ReverseProxy + Rewrite.
|
|
// Чеклист при повторении проблемы: docs/GATEWAY_RUN.md#mihomo-debug-400
|
|
func NewMihomoForward(
|
|
target *url.URL,
|
|
stripPrefix string,
|
|
auth string,
|
|
rt http.RoundTripper,
|
|
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 {
|
|
if errHandler == nil {
|
|
errHandler = defaultForwardErrorHandler
|
|
}
|
|
if rt == nil {
|
|
rt = http.DefaultTransport
|
|
}
|
|
rp := &httputil.ReverseProxy{
|
|
Rewrite: func(pr *httputil.ProxyRequest) {
|
|
NormalizeRequestURLPath(pr.Out)
|
|
p := pr.Out.URL.Path
|
|
if !strings.HasPrefix(p, stripPrefix) {
|
|
return
|
|
}
|
|
rest := strings.TrimPrefix(strings.TrimPrefix(p, stripPrefix), "/")
|
|
dest := JoinPathPrefix(target, "/", rest)
|
|
du := *dest
|
|
du.RawQuery = pr.Out.URL.RawQuery
|
|
out := pr.Out
|
|
out.URL = &du
|
|
out.Header.Del("Host")
|
|
out.Host = du.Host
|
|
out.RequestURI = ""
|
|
out.Proto = "HTTP/1.1"
|
|
out.ProtoMajor = 1
|
|
out.ProtoMinor = 1
|
|
out.Header.Del("Authorization")
|
|
if auth != "" {
|
|
out.Header.Set("Authorization", auth)
|
|
}
|
|
},
|
|
Transport: rt,
|
|
FlushInterval: -1,
|
|
ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) {
|
|
errHandler(w, r, err)
|
|
},
|
|
}
|
|
return rp
|
|
}
|
|
|
|
// MihomoMetaJSON returns a JSON body for GET .../mihomo/meta (display URL without credentials).
|
|
func MihomoMetaJSON(target *url.URL) []byte {
|
|
display := strings.TrimSuffix(target.String(), "/")
|
|
b, _ := json.Marshal(map[string]any{
|
|
"ok": true,
|
|
"controller_base": display,
|
|
})
|
|
return b
|
|
}
|
|
|
|
// MihomoJSONError writes a JSON error for Mihomo routes when proxy is not configured.
|
|
func MihomoJSONError(w http.ResponseWriter, code, msg string) {
|
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
w.WriteHeader(http.StatusNotFound)
|
|
_ = json.NewEncoder(w).Encode(map[string]any{
|
|
"ok": false,
|
|
"error": map[string]string{"code": code, "message": msg},
|
|
})
|
|
}
|