Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4d259fa58 | ||
|
|
9caa4e0cf9 |
+1
-1
@@ -76,7 +76,7 @@ esac
|
||||
# sing-box 核心在 NexusGate 仓的专用发行里(见下方 SINGBOX_BASE)。
|
||||
# 注意:本脚本是 V2 版——只装 sing-box 运行时;主控未升 V2 前不要用它
|
||||
# 重装节点(节点升级走面板一键更新,不需要本脚本)。
|
||||
RELEASE_BASE="https://git.881218.xyz/rogue/3x-ui-agent/releases/download/V2.0.0"
|
||||
RELEASE_BASE="https://git.881218.xyz/rogue/3x-ui-agent/releases/download/V2.0.1"
|
||||
XUI_URL="${XUI_URL:-${RELEASE_BASE}/x-ui-linux-${BIN_ARCH}}"
|
||||
|
||||
ROOT="${XUI_ROOT:-}"
|
||||
|
||||
@@ -211,10 +211,12 @@ func (pc *PublicProbeController) nodeMetrics(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, resp)
|
||||
}
|
||||
|
||||
// liveWS is the hub's browser stream: a snapshot frame every 2 s,
|
||||
// text by default, binary gzip frames for the anonymous audience when
|
||||
// opened with ?gzip. Audience eligibility is re-checked every tick —
|
||||
// a socket outlives both the session and the public-page switch.
|
||||
// liveWS is the hub's browser stream: a snapshot frame on the
|
||||
// operator's fast-report cadence (V2.0.2; the hub's fixed 2 s tick
|
||||
// made a 1 s agent cadence invisible up front), text by default,
|
||||
// binary gzip frames for the anonymous audience when opened with
|
||||
// ?gzip. Audience eligibility is re-checked every tick — a socket
|
||||
// outlives both the session and the public-page switch.
|
||||
func (pc *PublicProbeController) liveWS(c *gin.Context) {
|
||||
authed := probeAuthed(c) && !fromOtherOrigin(c)
|
||||
if !authed && !service.ProbePublicPageEnabled() {
|
||||
@@ -280,18 +282,19 @@ func (pc *PublicProbeController) liveWS(c *gin.Context) {
|
||||
return conn.WriteMessage(frameType, frame) == nil
|
||||
}
|
||||
|
||||
// The first frame goes out immediately (hub's interval ticks at
|
||||
// once), then one per 2 s tick.
|
||||
// The first frame goes out immediately; later frames follow the
|
||||
// fast-report cadence, re-read after every frame so a settings
|
||||
// change lands on a live socket without a reconnect.
|
||||
if !send() {
|
||||
return
|
||||
}
|
||||
ticker := time.NewTicker(2 * time.Second)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
timer := time.NewTimer(time.Duration(service.ProbeFastIntervalSec()) * time.Second)
|
||||
select {
|
||||
case <-done:
|
||||
timer.Stop()
|
||||
return
|
||||
case <-ticker.C:
|
||||
case <-timer.C:
|
||||
if !send() {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
ws "github.com/gorilla/websocket"
|
||||
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/web/service"
|
||||
)
|
||||
|
||||
// V2.0.2: /api/ws frame cadence follows the operator's fast-report
|
||||
// interval (probe_fast_interval) instead of the hub's fixed 2 s tick.
|
||||
// A mid-stream settings change re-paces a live socket: the timer
|
||||
// already in flight still fires on the old cadence, the next one is
|
||||
// armed from the re-read setting.
|
||||
func TestProbeWebSocketCadenceFollowsFastInterval(t *testing.T) {
|
||||
rig := newProbeTestRig(t)
|
||||
_, _ = rig.seedAgent(t, "CadenceNode", true)
|
||||
rig.setSetting(t, "public_page", "on")
|
||||
rig.setSetting(t, "probe_fast_interval", "1")
|
||||
service.InvalidateProbeSnapshot()
|
||||
|
||||
srv := httptest.NewServer(rig.engine)
|
||||
defer srv.Close()
|
||||
wsURL := "ws" + strings.TrimPrefix(srv.URL, "http") + "/api/ws"
|
||||
|
||||
conn, _, err := ws.DefaultDialer.Dial(wsURL, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("dial: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
readAt := func() time.Time {
|
||||
t.Helper()
|
||||
_ = conn.SetReadDeadline(time.Now().Add(10 * time.Second))
|
||||
if _, _, err := conn.ReadMessage(); err != nil {
|
||||
t.Fatalf("ws read: %v", err)
|
||||
}
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
_ = readAt() // first frame: immediate
|
||||
prev := readAt()
|
||||
for i := 0; i < 2; i++ {
|
||||
now := readAt()
|
||||
if gap := now.Sub(prev); gap < 500*time.Millisecond || gap > 2500*time.Millisecond {
|
||||
t.Fatalf("frame gap at 1 s cadence: %v", gap)
|
||||
}
|
||||
prev = now
|
||||
}
|
||||
|
||||
// Change to 3 s mid-stream: one in-flight 1 s frame, then the
|
||||
// stream settles onto the new cadence without a reconnect.
|
||||
rig.setSetting(t, "probe_fast_interval", "3")
|
||||
prev = readAt()
|
||||
now := readAt()
|
||||
if gap := now.Sub(prev); gap < 2200*time.Millisecond || gap > 6*time.Second {
|
||||
t.Fatalf("frame gap after change to 3 s: %v", gap)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user