mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-02 06:56:36 +08:00
122 lines
2.8 KiB
Go
122 lines
2.8 KiB
Go
package relay
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"log/slog"
|
||
"time"
|
||
|
||
"openflare-relay/internal/config"
|
||
"openflare-relay/internal/frps"
|
||
"openflare-relay/internal/heartbeat"
|
||
"openflare-relay/internal/httpclient"
|
||
"openflare-relay/internal/state"
|
||
"openflare-relay/internal/wsclient"
|
||
"openflare/service"
|
||
)
|
||
|
||
type Runner struct {
|
||
Config *config.Config
|
||
StateStore *state.Store
|
||
HeartbeatService *heartbeat.Service
|
||
FrpsManager *frps.Manager
|
||
WebSocketService *wsclient.Client
|
||
HttpClient *httpclient.Client
|
||
}
|
||
|
||
func (r *Runner) Run(ctx context.Context) error {
|
||
// Start heartbeat loop in background
|
||
go r.HeartbeatService.Run(ctx)
|
||
|
||
// WebSocket reconnection loop
|
||
for {
|
||
select {
|
||
case <-ctx.Done():
|
||
r.FrpsManager.Stop()
|
||
return ctx.Err()
|
||
default:
|
||
}
|
||
|
||
conn, err := r.WebSocketService.Connect(ctx)
|
||
if err != nil {
|
||
slog.Error("relay ws connect failed, will retry", "error", err)
|
||
r.sleepContext(ctx, 5*time.Second)
|
||
continue
|
||
}
|
||
|
||
r.handleConnection(ctx, conn)
|
||
_ = conn.Close()
|
||
slog.Info("relay ws connection closed, reconnecting...")
|
||
r.sleepContext(ctx, 2*time.Second)
|
||
}
|
||
}
|
||
|
||
func (r *Runner) handleConnection(ctx context.Context, conn *wsclient.Connection) {
|
||
// Send pings at 2× heartbeat interval to keep the server-side read deadline
|
||
// from expiring (server closes the WS if no data arrives within ~30 s).
|
||
pingInterval := r.Config.HeartbeatInterval.Duration() * 2
|
||
pingTicker := time.NewTicker(pingInterval)
|
||
defer pingTicker.Stop()
|
||
|
||
messages := make(chan service.WSMessage, 8)
|
||
readDone := make(chan error, 1)
|
||
go func() {
|
||
for {
|
||
msg, err := conn.Receive()
|
||
if err != nil {
|
||
readDone <- err
|
||
return
|
||
}
|
||
select {
|
||
case messages <- msg:
|
||
case <-ctx.Done():
|
||
readDone <- ctx.Err()
|
||
return
|
||
}
|
||
}
|
||
}()
|
||
|
||
for {
|
||
select {
|
||
case <-ctx.Done():
|
||
return
|
||
case err := <-readDone:
|
||
slog.Error("relay ws receive failed", "error", err)
|
||
return
|
||
case <-pingTicker.C:
|
||
if err := conn.SendPing(); err != nil {
|
||
slog.Error("relay ws send ping failed", "error", err)
|
||
return
|
||
}
|
||
case msg := <-messages:
|
||
switch msg.Type {
|
||
case "ping":
|
||
_ = conn.SendPong()
|
||
case "pong":
|
||
slog.Debug("relay ws pong received")
|
||
case "relay_config":
|
||
payloadBytes, ok := msg.Payload.(json.RawMessage)
|
||
if !ok {
|
||
slog.Error("invalid relay_config payload type")
|
||
continue
|
||
}
|
||
var cfg service.RelayConfig
|
||
if err := json.Unmarshal(payloadBytes, &cfg); err != nil {
|
||
slog.Error("failed to unmarshal relay_config", "error", err)
|
||
continue
|
||
}
|
||
r.FrpsManager.UpdateConfig(&cfg)
|
||
default:
|
||
slog.Debug("ignored unknown ws message type", "type", msg.Type)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
func (r *Runner) sleepContext(ctx context.Context, d time.Duration) {
|
||
select {
|
||
case <-ctx.Done():
|
||
case <-time.After(d):
|
||
}
|
||
}
|