refactor: use path plus header for browser dialer upgrade

Agent-Logs-Url: https://github.com/XTLS/Xray-core/sessions/33541da0-fdc4-4415-b7c4-7183f01b762e

Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-04-26 15:43:35 +00:00
committed by GitHub
parent c48c475256
commit 57253b736d
2 changed files with 18 additions and 17 deletions
+16 -15
View File
@@ -18,7 +18,6 @@ import (
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/platform" "github.com/xtls/xray-core/common/platform"
"github.com/xtls/xray-core/common/uuid"
) )
//go:embed dialer.html //go:embed dialer.html
@@ -35,6 +34,8 @@ var sockoptDialers map[string]*dialerInstance
var dialerServers map[string]*dialerServer var dialerServers map[string]*dialerServer
var mu sync.RWMutex var mu sync.RWMutex
const browserDialerSubprotocol = "browser-dialer"
var upgrader = &websocket.Upgrader{ var upgrader = &websocket.Upgrader{
ReadBufferSize: 0, ReadBufferSize: 0,
WriteBufferSize: 0, WriteBufferSize: 0,
@@ -56,14 +57,12 @@ type webSocketExtra struct {
type dialerInstance struct { type dialerInstance struct {
conns chan *websocket.Conn conns chan *websocket.Conn
pagePath string pagePath string
wsPath string
page []byte page []byte
} }
type dialerServer struct { type dialerServer struct {
server *http.Server server *http.Server
pageRoutes map[string]*dialerInstance pageRoutes map[string]*dialerInstance
wsRoutes map[string]*dialerInstance
} }
type browserDialerAddress struct { type browserDialerAddress struct {
@@ -105,16 +104,10 @@ func parseBrowserDialerAddress(addr string) (*browserDialerAddress, bool) {
} }
func newDialerInstance(path string) *dialerInstance { func newDialerInstance(path string) *dialerInstance {
token := uuid.New()
csrfToken := token.String()
escapedCsrfToken := url.PathEscape(csrfToken)
wsPath := path + "/" + escapedCsrfToken
page := bytes.ReplaceAll(webpage, []byte("dialerPath"), []byte(strings.TrimPrefix(path, "/"))) page := bytes.ReplaceAll(webpage, []byte("dialerPath"), []byte(strings.TrimPrefix(path, "/")))
page = bytes.ReplaceAll(page, []byte("csrfToken"), []byte(escapedCsrfToken))
dialer := &dialerInstance{ dialer := &dialerInstance{
conns: make(chan *websocket.Conn, 256), conns: make(chan *websocket.Conn, 256),
pagePath: path, pagePath: path,
wsPath: wsPath,
page: page, page: page,
} }
return dialer return dialer
@@ -123,19 +116,28 @@ func newDialerInstance(path string) *dialerInstance {
func newDialerServer(listenAddr string) *dialerServer { func newDialerServer(listenAddr string) *dialerServer {
dialer := &dialerServer{ dialer := &dialerServer{
pageRoutes: make(map[string]*dialerInstance), pageRoutes: make(map[string]*dialerInstance),
wsRoutes: make(map[string]*dialerInstance),
} }
dialer.server = &http.Server{ dialer.server = &http.Server{
Addr: listenAddr, Addr: listenAddr,
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
mu.RLock() mu.RLock()
wsDialer := dialer.wsRoutes[r.URL.Path]
pageDialer := dialer.pageRoutes[r.URL.Path] pageDialer := dialer.pageRoutes[r.URL.Path]
mu.RUnlock() mu.RUnlock()
if wsDialer != nil { if pageDialer != nil && websocket.IsWebSocketUpgrade(r) {
if conn, err := upgrader.Upgrade(w, r, nil); err == nil { ok := false
wsDialer.conns <- conn for _, protocol := range websocket.Subprotocols(r) {
if protocol == browserDialerSubprotocol {
ok = true
break
}
}
if !ok {
closeConnection(w)
return
}
if conn, err := upgrader.Upgrade(w, r, http.Header{"Sec-WebSocket-Protocol": []string{browserDialerSubprotocol}}); err == nil {
pageDialer.conns <- conn
} else { } else {
errors.LogError(context.Background(), "Browser dialer http upgrade unexpected error: ", err) errors.LogError(context.Background(), "Browser dialer http upgrade unexpected error: ", err)
} }
@@ -224,7 +226,6 @@ func getDialerByAddress(addr string) *dialerInstance {
dialer := newDialerInstance(parsed.path) dialer := newDialerInstance(parsed.path)
sockoptDialers[key] = dialer sockoptDialers[key] = dialer
server.pageRoutes[dialer.pagePath] = dialer server.pageRoutes[dialer.pagePath] = dialer
server.wsRoutes[dialer.wsPath] = dialer
mu.Unlock() mu.Unlock()
if startServer { if startServer {
@@ -10,7 +10,7 @@
// Enable a much more aggressive JIT for performance gains // Enable a much more aggressive JIT for performance gains
// Copyright (c) 2021 XRAY. Mozilla Public License 2.0. // Copyright (c) 2021 XRAY. Mozilla Public License 2.0.
let url = "ws://" + window.location.host + "/dialerPath/csrfToken"; let url = "ws://" + window.location.host + "/dialerPath";
let clientIdleCount = 0; let clientIdleCount = 0;
let upstreamGetCount = 0; let upstreamGetCount = 0;
let upstreamWsCount = 0; let upstreamWsCount = 0;
@@ -67,7 +67,7 @@
} }
clientIdleCount += 1; clientIdleCount += 1;
console.log("Prepare", url); console.log("Prepare", url);
let ws = new WebSocket(url); let ws = new WebSocket(url, "browser-dialer");
// arraybuffer is significantly faster in chrome than default // arraybuffer is significantly faster in chrome than default
// blob, tested with chrome 123 // blob, tested with chrome 123
ws.binaryType = "arraybuffer"; ws.binaryType = "arraybuffer";