Files
worker/internal/webapp/handlers_peer.go
Gleb Tv 2c7a0236da feat: publish standalone worker
Separate worker packaging and service lifecycle from the control plane.
2026-07-13 17:55:14 +03:00

60 строки
2.2 KiB
Go

package webapp
import (
"encoding/json"
"net/http"
"time"
)
// peerStatusResponse is the JSON returned at GET /api/peer/status.
// The shape matches distworker.PeerStatus exactly so the peer
// poller on the other end can decode it without a separate
// type. Kept here as a local view to keep the webapp package free
// of any concrete dependency on the distworker peer types; the
// fields are JSON-stable.
//
// Up == nil means "no probe has run yet" so peer workers that
// query this endpoint right after boot do not get a misleading
// "true" verdict while the selfcheck is still spinning up.
type peerStatusResponse struct {
WorkerID string `json:"worker_id"`
Up *bool `json:"up"`
ObservedAt *time.Time `json:"observed_at"`
}
// handlePeerStatus serves the most recent local selfcheck verdict
// to peer workers over HTTP. The endpoint is intentionally
// unauthenticated for now: a worker with basic auth configured
// (WORKER_LOGIN / WORKER_PASSWORD) still exposes the verdict
// because the path lives outside the /web/api/* prefix that the
// basic-auth middleware gates. This matches the
// "keep simple for local trusted workers if no peer auth exists
// yet" directive in
// docs/distributed/worker-to-worker-raft.md (slice 1).
//
// The endpoint never returns a 5xx: a runner that has not yet
// produced a verdict simply returns {"up": null, ...} so the
// peer poller can keep the slot in cache as "unknown" instead of
// treating the absence as a hard failure.
func (s *Server) handlePeerStatus(w http.ResponseWriter, _ *http.Request) {
resp := peerStatusResponse{}
if s.deps.Runner != nil {
up, at := s.deps.Runner.MasterStatus()
resp.WorkerID = s.deps.Runner.WorkerID()
resp.Up = up
if !at.IsZero() {
// Copy the timestamp so callers see a value
// (json omitempty is not used on purpose: an
// explicit zero time communicates "no probe" and
// an RFC3339 string communicates "probed at").
atCopy := at.UTC()
resp.ObservedAt = &atCopy
}
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusOK)
if err := json.NewEncoder(w).Encode(resp); err != nil {
s.deps.Logger.Printf("peer status encode: %v", err)
}
}