// Package health 提供轻量 HTTP 健康探针,给无 HTTP 端口的后端服务(dispatcher / mcp-go) // 补上 k8s/LB 能直接探的 /healthz(liveness) 与 /readyz(readiness)。 // // 此前这两个服务只有 NATS ServeHealth 应答器,编排器无法对它们做 HTTP 探测、只能靠 gateway // 经 NATS 代探。这里给它们各起一个极小的 HTTP 服务。 package health import ( "context" "log" "net/http" "time" ) // Serve 起一个健康探针 HTTP 服务: // - /healthz 恒 200(liveness:进程能应答即存活); // - /readyz 由 ready() 决定 200/503(readiness:依赖就绪才导流)。 // // addr 为空则不启动、返回 no-op(本地无端口需求时)。返回 shutdown 供优雅停机调用。 func Serve(service, addr string, ready func() bool) func(context.Context) { if addr == "" { return func(context.Context) {} } srv := &http.Server{Addr: addr, Handler: Handler(service, ready), ReadHeaderTimeout: 5 * time.Second} go func() { log.Printf("[%s] health probe on %s (/healthz /readyz)", service, addr) if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Printf("[%s] health probe listen: %v", service, err) } }() return func(ctx context.Context) { _ = srv.Shutdown(ctx) } } // Handler 返回探针路由(/healthz 恒 200,/readyz 由 ready 决定),便于单测与自定义挂载。 func Handler(service string, ready func() bool) http.Handler { mux := http.NewServeMux() mux.HandleFunc("/healthz", func(w http.ResponseWriter, _ *http.Request) { writeJSON(w, http.StatusOK, `{"status":"ok","service":"`+service+`"}`) }) mux.HandleFunc("/readyz", func(w http.ResponseWriter, _ *http.Request) { if ready == nil || ready() { writeJSON(w, http.StatusOK, `{"status":"ready"}`) return } writeJSON(w, http.StatusServiceUnavailable, `{"status":"not_ready"}`) }) return mux } func writeJSON(w http.ResponseWriter, code int, body string) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(code) _, _ = w.Write([]byte(body)) }