Files
mfd-monitor-agent/internal/httpapi/server.go
codex de3fd88948 ✨ feat(agent): 添加 token 鉴权和流程文档
* 默认监听调整为 0.0.0.0:8080,并要求查询参数 token
* 支持首次 run 自动生成含随机 token 的配置文件
* 补充 HTTP 鉴权、配置校验和相关测试
* 完善代码注释,覆盖缓存、上报、服务和平台采集路径
* 新增架构与开发流程文档,补充一键构建脚本
2026-05-22 06:46:21 +00:00

94 lines
2.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)
}