Some checks failed
Go / build (.exe, 386, windows, windows-386) (push) Has been cancelled
Go / build (.exe, amd64, windows, windows-amd64) (push) Has been cancelled
Go / build (.exe, arm64, windows, windows-arm64) (push) Has been cancelled
Go / build (386, freebsd, freebsd-386) (push) Has been cancelled
Go / build (386, linux, linux-386) (push) Has been cancelled
Go / build (386, netbsd, netbsd-386) (push) Has been cancelled
Go / build (386, openbsd, openbsd-386) (push) Has been cancelled
Go / build (386, plan9, plan9-386) (push) Has been cancelled
Go / build (amd64, darwin, darwin-amd64) (push) Has been cancelled
Go / build (amd64, dragonfly, dragonfly-amd64) (push) Has been cancelled
Go / build (amd64, freebsd, freebsd-amd64) (push) Has been cancelled
Go / build (amd64, illumos, illumos-amd64) (push) Has been cancelled
Go / build (amd64, linux, linux-amd64) (push) Has been cancelled
Go / build (amd64, netbsd, netbsd-amd64) (push) Has been cancelled
Go / build (amd64, openbsd, openbsd-amd64) (push) Has been cancelled
Go / build (amd64, plan9, plan9-amd64) (push) Has been cancelled
Go / build (amd64, solaris, solaris-amd64) (push) Has been cancelled
Go / build (arm, 6, linux, linux-armv6) (push) Has been cancelled
Go / build (arm, 7, linux, linux-armv7) (push) Has been cancelled
Go / build (arm, freebsd, freebsd-arm) (push) Has been cancelled
Go / build (arm, netbsd, netbsd-arm) (push) Has been cancelled
Go / build (arm, openbsd, openbsd-arm) (push) Has been cancelled
Go / build (arm, plan9, plan9-arm) (push) Has been cancelled
Go / build (arm64, darwin, darwin-arm64) (push) Has been cancelled
Go / build (arm64, freebsd, freebsd-arm64) (push) Has been cancelled
Go / build (arm64, linux, linux-arm64) (push) Has been cancelled
Go / build (arm64, netbsd, netbsd-arm64) (push) Has been cancelled
Go / build (arm64, openbsd, openbsd-arm64) (push) Has been cancelled
Go / build (loong64, linux, linux-loong64) (push) Has been cancelled
Go / build (mips, linux, linux-mips) (push) Has been cancelled
Go / build (mips64, linux, linux-mips64) (push) Has been cancelled
Go / build (mips64le, linux, linux-mips64le) (push) Has been cancelled
Go / build (mipsle, linux, linux-mipsle) (push) Has been cancelled
Go / build (ppc64, aix, aix-ppc64) (push) Has been cancelled
Go / build (ppc64, linux, linux-ppc64) (push) Has been cancelled
Go / build (ppc64, openbsd, openbsd-ppc64) (push) Has been cancelled
Go / build (ppc64le, linux, linux-ppc64le) (push) Has been cancelled
Go / build (riscv64, freebsd, freebsd-riscv64) (push) Has been cancelled
Go / build (riscv64, linux, linux-riscv64) (push) Has been cancelled
Go / build (riscv64, openbsd, openbsd-riscv64) (push) Has been cancelled
Go / build (s390x, linux, linux-s390x) (push) Has been cancelled
Docker Image / docker (push) Has been cancelled
Go / merge-artifacts (push) Has been cancelled
125 lines
3.0 KiB
Go
125 lines
3.0 KiB
Go
// cmd/gateway/websocket.go 把 WebSocket 会话适配为 net.Conn,让浏览器客户端复用网关请求路径。
|
||
|
||
package main
|
||
|
||
import (
|
||
"errors"
|
||
"fmt"
|
||
"io"
|
||
"net/http"
|
||
"sync"
|
||
"time"
|
||
|
||
"github.com/gorilla/websocket"
|
||
"github.com/rs/zerolog/log"
|
||
)
|
||
|
||
var upgrader = websocket.Upgrader{
|
||
CheckOrigin: func(r *http.Request) bool {
|
||
// 当前网关把 WebSocket 当作传输层入口,先允许所有来源;生产暴露时应在反向代理层收紧来源。
|
||
return true
|
||
},
|
||
}
|
||
|
||
type (
|
||
// WebSocket 连接适配器,实现 net.Conn 接口
|
||
webSocketConn struct {
|
||
*websocket.Conn
|
||
reader io.Reader
|
||
}
|
||
)
|
||
|
||
// WebSocket 处理函数
|
||
func handleWebSocket(w http.ResponseWriter, r *http.Request) {
|
||
// 升级 HTTP 连接为 WebSocket
|
||
conn, err := upgrader.Upgrade(w, r, nil)
|
||
if err != nil {
|
||
log.Err(err).Msg("Failed to upgrade WebSocket connection")
|
||
return
|
||
}
|
||
defer conn.Close()
|
||
|
||
gatewayMetrics.WebSocketConnectionStarted()
|
||
// WebSocket 连接包装为 net.Conn 后进入同一个 handleRequest,复用插件、路由和转发逻辑。
|
||
handleRequest(&webSocketConn{Conn: conn})
|
||
}
|
||
|
||
func (w *webSocketConn) Read(b []byte) (n int, err error) {
|
||
for {
|
||
if w.reader != nil {
|
||
// 当前消息帧没读完前持续从同一个 reader 读取,模拟流式 net.Conn。
|
||
n, err = w.reader.Read(b)
|
||
if errors.Is(err, io.EOF) {
|
||
w.reader = nil
|
||
if n > 0 {
|
||
return n, nil
|
||
}
|
||
continue
|
||
}
|
||
return n, err
|
||
}
|
||
|
||
messageType, reader, err := w.NextReader()
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
if messageType != websocket.BinaryMessage && messageType != websocket.TextMessage {
|
||
// 控制帧不进入 Minecraft 协议流。
|
||
continue
|
||
}
|
||
w.reader = reader
|
||
}
|
||
}
|
||
|
||
func (w *webSocketConn) Write(b []byte) (n int, err error) {
|
||
// 每次 Write 输出一个二进制 WebSocket 消息,保持与 Minecraft packet 边界无关的字节流语义。
|
||
writer, err := w.NextWriter(websocket.BinaryMessage)
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
|
||
n, err = writer.Write(b)
|
||
closeErr := writer.Close()
|
||
if err != nil {
|
||
return n, err
|
||
}
|
||
if closeErr != nil {
|
||
return n, closeErr
|
||
}
|
||
if n != len(b) {
|
||
return n, io.ErrShortWrite
|
||
}
|
||
|
||
return n, nil
|
||
}
|
||
|
||
func (w *webSocketConn) SetDeadline(t time.Time) error {
|
||
if err := w.SetReadDeadline(t); err != nil {
|
||
return err
|
||
}
|
||
return w.SetWriteDeadline(t)
|
||
}
|
||
|
||
func newWebSocketHandler() http.Handler {
|
||
mux := http.NewServeMux()
|
||
// 路径来自运行态服务配置,允许管理端把 WebSocket 入口挂到子路径。
|
||
mux.HandleFunc(normalizedWebSocketPath(), handleWebSocket)
|
||
return mux
|
||
}
|
||
|
||
// 启动 WebSocket 服务器
|
||
func runWebSocket(wg *sync.WaitGroup) {
|
||
if wg != nil {
|
||
defer wg.Done()
|
||
}
|
||
|
||
port := normalizedWebSocketPort()
|
||
path := normalizedWebSocketPath()
|
||
|
||
log.Info().Int("port", port).Str("path", path).Msg("Starting WebSocket server")
|
||
server := &http.Server{Addr: fmt.Sprintf(":%d", port), Handler: newWebSocketHandler()}
|
||
if err := server.ListenAndServe(); err != nil {
|
||
log.Fatal().Err(err).Msg("Failed to start WebSocket server")
|
||
}
|
||
}
|