Files
telemt-api/internal/proxy/mihomo_test.go
T
Denozordec c3a0a771a3
Publish telemt-api gateway Docker image / test (push) Successful in 25s
Publish telemt-api gateway Docker image / build-and-push (push) Successful in 2m18s
Enhance Mihomo WebSocket functionality and testing for connections
- Added support for WebSocket connections to the `/connections` endpoint, allowing for improved handling of WebSocket requests.
- Updated the `useMihomoWebSocketTunnel` function to accept `Sec-WebSocket-Key` for `/connections`, enhancing compatibility with various proxies.
- Introduced new test cases in `mihomo_test.go` to validate WebSocket tunnel behavior for connections, ensuring comprehensive coverage.
- Enhanced the `applyMihomoWSDialAuth` function to manage token handling for WebSocket connections, improving security and functionality.
- Updated Svelte components to implement native WebSocket loops for connections, providing real-time data updates and improved user experience.
2026-03-31 11:54:18 +07:00

152 lines
4.8 KiB
Go

package proxy
import (
"context"
"net/http"
"net/http/httptest"
"net/url"
"testing"
)
func TestMihomoForwardRewritesPathAndAuth(t *testing.T) {
target, err := url.Parse("http://127.0.0.1:9090")
if err != nil {
t.Fatal(err)
}
cap := &captureTransport{}
h := NewMihomoForward(target, "/api/mtg/mihomo", "Bearer testsecret", cap, nil)
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "http://gw/api/mtg/mihomo/proxies", nil)
if err != nil {
t.Fatal(err)
}
req.Header.Set("Authorization", "Bearer client-should-not-forward")
req.Header.Set("Host", "public-gateway.example:8888")
h.ServeHTTP(httptest.NewRecorder(), req)
if cap.got == nil {
t.Fatal("no outgoing request captured")
}
if got, want := cap.got.Host, "127.0.0.1:9090"; got != want {
t.Fatalf("Host: got %q want %q (upstream must not see client Host)", got, want)
}
if got := cap.got.Header.Get("Authorization"); got != "Bearer testsecret" {
t.Fatalf("Authorization: got %q want Bearer testsecret", got)
}
want, err := url.Parse("http://127.0.0.1:9090/proxies")
if err != nil {
t.Fatal(err)
}
assertSameURL(t, cap.got.URL, want)
}
func TestUseMihomoWebSocketTunnel(t *testing.T) {
t.Run("gorilla IsWebSocketUpgrade", func(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/api/x/mihomo/traffic", nil)
r.Header.Set("Connection", "Upgrade")
r.Header.Set("Upgrade", "websocket")
r.Header.Set("Sec-WebSocket-Version", "13")
r.Header.Set("Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ==")
if !useMihomoWebSocketTunnel("traffic", r) {
t.Fatal("expected tunnel for full WS handshake")
}
})
t.Run("traffic with key only (no Connection token)", func(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/api/x/mihomo/traffic", nil)
r.Header.Set("Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ==")
if !useMihomoWebSocketTunnel("traffic", r) {
t.Fatal("expected tunnel for traffic+Sec-WebSocket-Key")
}
})
t.Run("memory with key only", func(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/api/x/mihomo/memory", nil)
r.Header.Set("Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ==")
if !useMihomoWebSocketTunnel("memory", r) {
t.Fatal("expected tunnel for memory+Sec-WebSocket-Key")
}
})
t.Run("traffic streaming GET without key", func(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/api/x/mihomo/traffic", nil)
if useMihomoWebSocketTunnel("traffic", r) {
t.Fatal("plain GET /traffic must use HTTP forwarder")
}
})
t.Run("proxies path never tunnel", func(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/api/x/mihomo/proxies", nil)
r.Header.Set("Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ==")
if useMihomoWebSocketTunnel("proxies", r) {
t.Fatal("proxies must not use WS tunnel")
}
})
t.Run("connections with key only", func(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/api/x/mihomo/connections", nil)
r.Header.Set("Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ==")
if !useMihomoWebSocketTunnel("connections", r) {
t.Fatal("expected tunnel for connections+Sec-WebSocket-Key")
}
})
t.Run("connections REST GET without key", func(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/api/x/mihomo/connections", nil)
if useMihomoWebSocketTunnel("connections", r) {
t.Fatal("plain GET /connections must use HTTP forwarder")
}
})
}
func TestApplyMihomoWSDialAuth(t *testing.T) {
t.Run("connections strips Bearer for token query", func(t *testing.T) {
u, _ := url.Parse("http://127.0.0.1:9090/connections")
hdr := applyMihomoWSDialAuth("connections", u, "Bearer mysecret")
if hdr.Get("Authorization") != "" {
t.Fatal("connections WS must not set Authorization")
}
if u.RawQuery != "token=mysecret" {
t.Fatalf("query: %q", u.RawQuery)
}
})
t.Run("connections raw token", func(t *testing.T) {
u, _ := url.Parse("http://127.0.0.1:9090/connections")
_ = applyMihomoWSDialAuth("connections", u, "rawonly")
if u.RawQuery != "token=rawonly" {
t.Fatalf("query: %q", u.RawQuery)
}
})
t.Run("traffic uses Authorization", func(t *testing.T) {
u, _ := url.Parse("http://127.0.0.1:9090/traffic")
hdr := applyMihomoWSDialAuth("traffic", u, "Bearer t")
if hdr.Get("Authorization") != "Bearer t" {
t.Fatalf("Authorization: %q", hdr.Get("Authorization"))
}
})
}
func TestMihomoWebSocketURL(t *testing.T) {
u, err := url.Parse("http://172.20.0.2:9090/traffic?q=1")
if err != nil {
t.Fatal(err)
}
s, err := mihomoWebSocketURL(u)
if err != nil {
t.Fatal(err)
}
if want := "ws://172.20.0.2:9090/traffic?q=1"; s != want {
t.Fatalf("got %q want %q", s, want)
}
u2, _ := url.Parse("https://example.com/mem")
s2, err := mihomoWebSocketURL(u2)
if err != nil {
t.Fatal(err)
}
if want := "wss://example.com/mem"; s2 != want {
t.Fatalf("got %q want %q", s2, want)
}
}