mirror of
https://github.com/truewhile/MeBox.git
synced 2026-11-12 19:34:24 +08:00
91 lines
2.2 KiB
Go
91 lines
2.2 KiB
Go
// Package handler — WebSocket endpoint.
|
||
//
|
||
// Administrators connect to /api/ws?token=... with their REST access token.
|
||
// Operator events arrive as {"topic":"...","payload":{...}}.
|
||
package handler
|
||
|
||
import (
|
||
"encoding/json"
|
||
"net/http"
|
||
"net/url"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
"github.com/google/uuid"
|
||
"github.com/gorilla/websocket"
|
||
|
||
"github.com/truewhile/MeBox/internal/service"
|
||
)
|
||
|
||
var wsUpgrader = websocket.Upgrader{
|
||
ReadBufferSize: 1024,
|
||
WriteBufferSize: 1024,
|
||
// 同源校验:浏览器跨站页面虽读不到 ?token=,但可能借 cookie 通道
|
||
// (extractToken 接受 msgo_access_token cookie)发起跨站 WebSocket
|
||
// 劫持。放行同源与非浏览器客户端(不发 Origin 头的 App/脚本),
|
||
// 拒绝跨站 Origin。
|
||
CheckOrigin: func(r *http.Request) bool {
|
||
origin := strings.TrimSpace(r.Header.Get("Origin"))
|
||
if origin == "" {
|
||
return true
|
||
}
|
||
u, err := url.Parse(origin)
|
||
if err != nil || u.Host == "" {
|
||
return false
|
||
}
|
||
return strings.EqualFold(u.Host, r.Host)
|
||
},
|
||
}
|
||
|
||
func wsHandler(svc *service.Container) gin.HandlerFunc {
|
||
return func(c *gin.Context) {
|
||
conn, err := wsUpgrader.Upgrade(c.Writer, c.Request, nil)
|
||
if err != nil {
|
||
return
|
||
}
|
||
defer conn.Close()
|
||
|
||
id := uuid.NewString()
|
||
sub := svc.WSHub.Subscribe(id, nil)
|
||
defer svc.WSHub.Unsubscribe(id)
|
||
|
||
// Reading processes WebSocket control frames and detects disconnects.
|
||
done := make(chan struct{})
|
||
conn.SetReadLimit(64 << 10)
|
||
go func() {
|
||
defer close(done)
|
||
for {
|
||
if _, _, err := conn.NextReader(); err != nil {
|
||
_ = conn.Close()
|
||
return
|
||
}
|
||
}
|
||
}()
|
||
|
||
// Writer: drain hub events into the socket with a periodic ping.
|
||
ticker := time.NewTicker(30 * time.Second)
|
||
defer ticker.Stop()
|
||
for {
|
||
select {
|
||
case <-done:
|
||
return
|
||
case ev, ok := <-sub.Out:
|
||
if !ok {
|
||
return
|
||
}
|
||
data, _ := json.Marshal(ev)
|
||
_ = conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
|
||
if err := conn.WriteMessage(websocket.TextMessage, data); err != nil {
|
||
return
|
||
}
|
||
case <-ticker.C:
|
||
_ = conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
|
||
if err := conn.WriteMessage(websocket.PingMessage, nil); err != nil {
|
||
return
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|