Files
clawgo/pkg/nodes/registry_server.go

108 lines
2.8 KiB
Go

package nodes
import (
"context"
"encoding/json"
"fmt"
"net/http"
"strings"
"time"
)
type RegistryServer struct {
addr string
token string
mgr *Manager
server *http.Server
}
func NewRegistryServer(host string, port int, token string, mgr *Manager) *RegistryServer {
addr := strings.TrimSpace(host)
if addr == "" {
addr = "0.0.0.0"
}
if port <= 0 {
port = 7788
}
return &RegistryServer{addr: fmt.Sprintf("%s:%d", addr, port), token: strings.TrimSpace(token), mgr: mgr}
}
func (s *RegistryServer) Start(ctx context.Context) error {
if s.mgr == nil {
return nil
}
mux := http.NewServeMux()
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("ok"))
})
mux.HandleFunc("/nodes/register", s.handleRegister)
mux.HandleFunc("/nodes/heartbeat", s.handleHeartbeat)
s.server = &http.Server{Addr: s.addr, Handler: mux}
go func() {
<-ctx.Done()
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_ = s.server.Shutdown(shutdownCtx)
}()
go func() { _ = s.server.ListenAndServe() }()
return nil
}
func (s *RegistryServer) handleRegister(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
if !s.checkAuth(r) {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
var n NodeInfo
if err := json.NewDecoder(r.Body).Decode(&n); err != nil {
http.Error(w, "invalid json", http.StatusBadRequest)
return
}
if strings.TrimSpace(n.ID) == "" {
http.Error(w, "id required", http.StatusBadRequest)
return
}
s.mgr.Upsert(n)
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]interface{}{"ok": true, "id": n.ID})
}
func (s *RegistryServer) handleHeartbeat(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
if !s.checkAuth(r) {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
var body struct{ ID string `json:"id"` }
if err := json.NewDecoder(r.Body).Decode(&body); err != nil || strings.TrimSpace(body.ID) == "" {
http.Error(w, "id required", http.StatusBadRequest)
return
}
n, ok := s.mgr.Get(body.ID)
if !ok {
http.Error(w, "node not found", http.StatusNotFound)
return
}
n.LastSeenAt = time.Now().UTC()
n.Online = true
s.mgr.Upsert(n)
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]interface{}{"ok": true, "id": body.ID})
}
func (s *RegistryServer) checkAuth(r *http.Request) bool {
if s.token == "" {
return true
}
auth := strings.TrimSpace(r.Header.Get("Authorization"))
return auth == "Bearer "+s.token
}