feat: publish sanitized custom.97 source
This commit is contained in:
1022 files changed
+69903
-19207
No files matched your search
@@ -0,0 +1,117 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/QuantumNous/new-api/common"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// Coalescing is safe because each snapshot includes the persisted detection
|
||||
// history. Subscribers register before reading their initial snapshot.
|
||||
var groupStatusEvents = struct {
|
||||
sync.Mutex
|
||||
clients map[chan struct{}]struct{}
|
||||
}{clients: make(map[chan struct{}]struct{})}
|
||||
|
||||
func subscribeGroupStatus() (chan struct{}, func(), error) {
|
||||
groupStatusEvents.Lock()
|
||||
defer groupStatusEvents.Unlock()
|
||||
if len(groupStatusEvents.clients) >= 32 {
|
||||
return nil, nil, errors.New("too many status subscribers")
|
||||
}
|
||||
ch := make(chan struct{}, 1)
|
||||
groupStatusEvents.clients[ch] = struct{}{}
|
||||
return ch, func() {
|
||||
groupStatusEvents.Lock()
|
||||
delete(groupStatusEvents.clients, ch)
|
||||
groupStatusEvents.Unlock()
|
||||
}, nil
|
||||
}
|
||||
|
||||
func publishGroupStatusChange() {
|
||||
groupStatusEvents.Lock()
|
||||
defer groupStatusEvents.Unlock()
|
||||
for ch := range groupStatusEvents.clients {
|
||||
select {
|
||||
case ch <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func StreamGroupStatus(c *gin.Context) {
|
||||
streamGroupStatus(c, func(ctx context.Context) (any, error) {
|
||||
results, err := currentGroupMonitors(ctx, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := range results {
|
||||
for j := range results[i].Monitors {
|
||||
monitor := &results[i].Monitors[j]
|
||||
monitor.Name, monitor.Alias, monitor.Hidden = monitor.Group, "", false
|
||||
}
|
||||
}
|
||||
return gin.H{"success": true, "data": results}, nil
|
||||
})
|
||||
}
|
||||
|
||||
func streamGroupStatus(c *gin.Context, snapshot func(context.Context) (any, error)) {
|
||||
changed, unsubscribe, err := subscribeGroupStatus()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusTooManyRequests, gin.H{"success": false})
|
||||
return
|
||||
}
|
||||
defer unsubscribe()
|
||||
read := func() ([]byte, error) {
|
||||
ctx, cancel := context.WithTimeout(c.Request.Context(), requestTimeout)
|
||||
defer cancel()
|
||||
payload, err := snapshot(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return common.Marshal(payload)
|
||||
}
|
||||
data, err := read()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusServiceUnavailable, gin.H{"success": false})
|
||||
return
|
||||
}
|
||||
c.Header("Content-Type", "text/event-stream")
|
||||
c.Header("Cache-Control", "no-cache, no-transform")
|
||||
c.Header("X-Accel-Buffering", "no")
|
||||
write := func(frame string) error {
|
||||
_ = http.NewResponseController(c.Writer).SetWriteDeadline(time.Now().Add(10 * time.Second))
|
||||
if _, err := fmt.Fprint(c.Writer, frame); err != nil {
|
||||
return err
|
||||
}
|
||||
c.Writer.Flush()
|
||||
return nil
|
||||
}
|
||||
if write("retry: 5000\nevent: status\ndata: "+string(data)+"\n\n") != nil {
|
||||
return
|
||||
}
|
||||
heartbeat := time.NewTicker(20 * time.Second)
|
||||
defer heartbeat.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-c.Request.Context().Done():
|
||||
return
|
||||
case <-heartbeat.C:
|
||||
// Keep proxies alive without querying the database or polling status.
|
||||
if write(": keepalive\n\n") != nil {
|
||||
return
|
||||
}
|
||||
case <-changed:
|
||||
data, err := read()
|
||||
if err != nil || write("event: status\ndata: "+string(data)+"\n\n") != nil {
|
||||
return // Reconnect replays persisted history after transient failures.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user