erste module drin.
zwischenstand: Architektur aktualisiert, TOFU implementiert,
This commit is contained in:
1 parent
9d5789c5d7
commit
c8df9d7651
41 files changed
+6455
-36
No files matched your search
@@ -0,0 +1,78 @@
|
||||
// Package stream implementiert den langlebigen Kanal vom Sultan-Server zum
|
||||
// Agenten als Server-Sent Events (SSE) über eine ausgehende HTTP-Verbindung
|
||||
// des Agenten.
|
||||
//
|
||||
// Der Server (Hub) hält pro Knoten einen offenen Stream, schickt Events sofort
|
||||
// und sendet in Ruhephasen alle 45s die SSE-Kommentarzeile ":\n\n" als
|
||||
// Keepalive, damit NAT-/Conntrack-Einträge nicht verfallen. Der Agent (Client)
|
||||
// liest den Stream, erkennt einen toten Server, wenn zu lange gar nichts
|
||||
// ankommt (auch kein Keepalive), und verbindet sich mit exponentiellem
|
||||
// Backoff neu.
|
||||
//
|
||||
// Die Gegenrichtung (Agent -> Server, z. B. service_state) ist nicht Teil
|
||||
// dieses Streams; sie läuft über eigene Requests.
|
||||
package stream
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// EventType ist der Typ eines Events gemäß Spec Abschnitt 4.2.
|
||||
type EventType string
|
||||
|
||||
const (
|
||||
EventServiceState EventType = "service_state"
|
||||
EventLeaseRenew EventType = "lease_renew"
|
||||
EventNodeCommand EventType = "node_command"
|
||||
EventError EventType = "error"
|
||||
)
|
||||
|
||||
// ErrInvalidEvent meldet ein Event, das sich nicht senden oder lesen lässt.
|
||||
var ErrInvalidEvent = errors.New("stream: ungültiges Event")
|
||||
|
||||
// Event ist die Nachricht auf dem Stream.
|
||||
type Event struct {
|
||||
ID string `json:"event_id"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Type EventType `json:"type"`
|
||||
NodeID string `json:"node_id"`
|
||||
Payload json.RawMessage `json:"payload,omitempty"`
|
||||
}
|
||||
|
||||
// NewEvent erzeugt ein Event mit neuer ID, aktuellem Zeitstempel und der als
|
||||
// JSON serialisierten payload (nil ergibt keine Payload). NodeID setzt der Hub
|
||||
// beim Senden.
|
||||
func NewEvent(t EventType, payload any) (Event, error) {
|
||||
if t == "" {
|
||||
return Event{}, fmt.Errorf("%w: Typ fehlt", ErrInvalidEvent)
|
||||
}
|
||||
ev := Event{ID: newEventID(), Timestamp: time.Now().Unix(), Type: t}
|
||||
if payload != nil {
|
||||
b, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return Event{}, fmt.Errorf("%w: %v", ErrInvalidEvent, err)
|
||||
}
|
||||
ev.Payload = b
|
||||
}
|
||||
return ev, nil
|
||||
}
|
||||
|
||||
func newEventID() string {
|
||||
var b [16]byte
|
||||
rand.Read(b[:])
|
||||
return "evt_" + hex.EncodeToString(b[:])
|
||||
}
|
||||
|
||||
// encode erzeugt den SSE-Rahmen für ev.
|
||||
func encode(ev Event) ([]byte, error) {
|
||||
data, err := json.Marshal(ev)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %v", ErrInvalidEvent, err)
|
||||
}
|
||||
return fmt.Appendf(nil, "id: %s\nevent: %s\ndata: %s\n\n", ev.ID, ev.Type, data), nil
|
||||
}
|
||||
Reference in new issue
Block a user