package httpapi import ( "crypto/subtle" "encoding/json" "net/http" "mfd-monitor-agent/internal/status" ) // Provider 向 HTTP 处理器提供最新缓存的 HostStatus。 type Provider interface { // Get 返回最新快照及快照是否已就绪。 Get() (status.HostStatus, bool) } // StaticProvider 是用于测试的固定快照提供者。 type StaticProvider struct { Snapshot status.HostStatus OK bool } // Get 返回配置的静态快照与就绪标志。 func (p StaticProvider) Get() (status.HostStatus, bool) { return p.Snapshot, p.OK } // New 创建提供 health 与 status 端点的 HTTP API 处理器;token 用于查询参数 ?token= 鉴权。 // 两个端点都只读取 provider 缓存,不会在请求路径上触发实时系统扫描。 func New(provider Provider, token string) http.Handler { mux := http.NewServeMux() mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return } snapshot, ok := provider.Get() if !ok { writeJSON(w, http.StatusServiceUnavailable, map[string]any{ "status": "starting", }) return } writeJSON(w, http.StatusOK, map[string]any{ "status": "ok", "version": snapshot.Agent.Version, "snapshot_at": snapshot.Agent.SnapshotAt, "snapshot_status": snapshot.Agent.SnapshotStatus, }) }) mux.HandleFunc("/status", func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return } snapshot, ok := provider.Get() if !ok { http.Error(w, "snapshot not ready", http.StatusServiceUnavailable) return } writeJSON(w, http.StatusOK, snapshot) }) return withQueryTokenAuth(token, mux) } // withQueryTokenAuth 校验 GET 请求中的 token 查询参数,失败时返回 401。 func withQueryTokenAuth(expected string, next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // 空 token 一律拒绝,配合配置校验避免无鉴权暴露主机状态。 if !queryTokenValid(r.URL.Query().Get("token"), expected) { writeJSON(w, http.StatusUnauthorized, map[string]string{ "error": "unauthorized", }) return } next.ServeHTTP(w, r) }) } // queryTokenValid 以恒定时间比较查询参数 token 与配置值,避免时序侧信道。 func queryTokenValid(got, want string) bool { if got == "" || want == "" { return false } return subtle.ConstantTimeCompare([]byte(got), []byte(want)) == 1 } // writeJSON 以给定状态码将 value 序列化为 JSON 响应;编码失败时连接会被动结束。 func writeJSON(w http.ResponseWriter, code int, value any) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(code) _ = json.NewEncoder(w).Encode(value) }