Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4d259fa58 | ||
|
|
9caa4e0cf9 |
+1
-1
@@ -76,7 +76,7 @@ esac
|
|||||||
# sing-box 核心在 NexusGate 仓的专用发行里(见下方 SINGBOX_BASE)。
|
# sing-box 核心在 NexusGate 仓的专用发行里(见下方 SINGBOX_BASE)。
|
||||||
# 注意:本脚本是 V2 版——只装 sing-box 运行时;主控未升 V2 前不要用它
|
# 注意:本脚本是 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}}"
|
XUI_URL="${XUI_URL:-${RELEASE_BASE}/x-ui-linux-${BIN_ARCH}}"
|
||||||
|
|
||||||
ROOT="${XUI_ROOT:-}"
|
ROOT="${XUI_ROOT:-}"
|
||||||
|
|||||||
@@ -211,10 +211,12 @@ func (pc *PublicProbeController) nodeMetrics(c *gin.Context) {
|
|||||||
c.JSON(http.StatusOK, resp)
|
c.JSON(http.StatusOK, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// liveWS is the hub's browser stream: a snapshot frame every 2 s,
|
// liveWS is the hub's browser stream: a snapshot frame on the
|
||||||
// text by default, binary gzip frames for the anonymous audience when
|
// operator's fast-report cadence (V2.0.2; the hub's fixed 2 s tick
|
||||||
// opened with ?gzip. Audience eligibility is re-checked every tick —
|
// made a 1 s agent cadence invisible up front), text by default,
|
||||||
// a socket outlives both the session and the public-page switch.
|
// 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) {
|
func (pc *PublicProbeController) liveWS(c *gin.Context) {
|
||||||
authed := probeAuthed(c) && !fromOtherOrigin(c)
|
authed := probeAuthed(c) && !fromOtherOrigin(c)
|
||||||
if !authed && !service.ProbePublicPageEnabled() {
|
if !authed && !service.ProbePublicPageEnabled() {
|
||||||
@@ -280,18 +282,19 @@ func (pc *PublicProbeController) liveWS(c *gin.Context) {
|
|||||||
return conn.WriteMessage(frameType, frame) == nil
|
return conn.WriteMessage(frameType, frame) == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// The first frame goes out immediately (hub's interval ticks at
|
// The first frame goes out immediately; later frames follow the
|
||||||
// once), then one per 2 s tick.
|
// fast-report cadence, re-read after every frame so a settings
|
||||||
|
// change lands on a live socket without a reconnect.
|
||||||
if !send() {
|
if !send() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ticker := time.NewTicker(2 * time.Second)
|
|
||||||
defer ticker.Stop()
|
|
||||||
for {
|
for {
|
||||||
|
timer := time.NewTimer(time.Duration(service.ProbeFastIntervalSec()) * time.Second)
|
||||||
select {
|
select {
|
||||||
case <-done:
|
case <-done:
|
||||||
|
timer.Stop()
|
||||||
return
|
return
|
||||||
case <-ticker.C:
|
case <-timer.C:
|
||||||
if !send() {
|
if !send() {
|
||||||
return
|
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