rmm2/agent-windows/ws/client.go

151 lines
3.1 KiB
Go

package ws
import (
"crypto/tls"
"fmt"
"log"
"net/url"
"time"
"github.com/gorilla/websocket"
)
type Client struct {
backendURL string
agentID string
apiKey string
insecure bool
conn *websocket.Conn
handler *Handler
reconnectWait time.Duration
}
func NewClient(backendURL, agentID, apiKey string, insecure bool, handler *Handler) *Client {
return &Client{
backendURL: backendURL,
agentID: agentID,
apiKey: apiKey,
insecure: insecure,
handler: handler,
reconnectWait: 5 * time.Second,
}
}
func (c *Client) Run(stop <-chan struct{}) {
for {
select {
case <-stop:
return
default:
}
if err := c.connect(stop); err != nil {
log.Printf("WS-Verbindung fehlgeschlagen: %v — erneuter Versuch in %v", err, c.reconnectWait)
select {
case <-stop:
return
case <-time.After(c.reconnectWait):
}
if c.reconnectWait < 60*time.Second {
c.reconnectWait *= 2
}
} else {
c.reconnectWait = 5 * time.Second
}
}
}
func (c *Client) connect(stop <-chan struct{}) error {
u, err := url.Parse(c.backendURL)
if err != nil {
return err
}
switch u.Scheme {
case "https":
u.Scheme = "wss"
case "http":
u.Scheme = "ws"
}
u.Path = "/api/v1/agent/ws"
q := u.Query()
q.Set("agent_id", c.agentID)
q.Set("api_key", c.apiKey)
u.RawQuery = q.Encode()
dialer := websocket.DefaultDialer
if c.insecure {
dialer = &websocket.Dialer{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
HandshakeTimeout: 10 * time.Second,
}
}
conn, _, err := dialer.Dial(u.String(), nil)
if err != nil {
return fmt.Errorf("Dial fehlgeschlagen: %w", err)
}
defer conn.Close()
c.conn = conn
log.Printf("WS verbunden mit %s", c.backendURL)
conn.SetReadDeadline(time.Now().Add(70 * time.Second))
conn.SetPongHandler(func(string) error {
conn.SetReadDeadline(time.Now().Add(70 * time.Second))
return nil
})
done := make(chan struct{})
// Ping-Loop: alle 30s einen Ping senden um Verbindung am Leben zu halten
go func() {
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
if err := conn.WriteMessage(websocket.PingMessage, nil); err != nil {
return
}
case <-done:
return
case <-stop:
return
}
}
}()
go func() {
defer close(done)
for {
_, msg, err := conn.ReadMessage()
if err != nil {
if !websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseGoingAway) {
log.Printf("WS Read-Fehler: %v", err)
}
return
}
if resp := c.handler.Handle(msg); resp != nil {
conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
conn.WriteMessage(websocket.TextMessage, resp)
}
}
}()
select {
case <-stop:
conn.WriteMessage(websocket.CloseMessage,
websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
return nil
case <-done:
return fmt.Errorf("Verbindung getrennt")
}
}
func (c *Client) SendMessage(data []byte) error {
if c.conn == nil {
return fmt.Errorf("nicht verbunden")
}
return c.conn.WriteMessage(websocket.TextMessage, data)
}