2026-04-04 15:26:26 +08:00

91 lines
2.1 KiB
Go

package httpserver
import (
"context"
"encoding/json"
"errors"
"log/slog"
"net/http"
"sync/atomic"
"time"
)
// Server 提供 /health 和 /ready HTTP 探针。
type Server struct {
appName string
addr string
shutdownTimeout time.Duration
logger *slog.Logger
ready atomic.Bool
}
// New 创建探针 HTTP 服务。
func New(appName string, addr string, shutdownTimeout time.Duration, logger *slog.Logger) *Server {
server := &Server{
appName: appName,
addr: addr,
shutdownTimeout: shutdownTimeout,
logger: logger,
}
server.ready.Store(true)
return server
}
// Run 启动 HTTP 服务并在退出时优雅关闭。
func (s *Server) Run(ctx context.Context) error {
mux := http.NewServeMux()
mux.HandleFunc("/health", s.handleHealth)
mux.HandleFunc("/heath", s.handleHealth)
mux.HandleFunc("/ready", s.handleReady)
httpServer := &http.Server{
Addr: s.addr,
Handler: mux,
ReadHeaderTimeout: 5 * time.Second,
}
go func() {
<-ctx.Done()
s.ready.Store(false)
shutdownCtx, cancel := context.WithTimeout(context.Background(), s.shutdownTimeout)
defer cancel()
_ = httpServer.Shutdown(shutdownCtx)
}()
s.logger.Info("pay http server started", "addr", s.addr)
err := httpServer.ListenAndServe()
if errors.Is(err, http.ErrServerClosed) {
return nil
}
return err
}
func (s *Server) handleHealth(w http.ResponseWriter, _ *http.Request) {
writeJSON(w, http.StatusOK, map[string]string{
"status": "ok",
"service": s.appName,
})
}
func (s *Server) handleReady(w http.ResponseWriter, _ *http.Request) {
if !s.ready.Load() {
writeJSON(w, http.StatusServiceUnavailable, map[string]string{
"code": "not_ready",
"message": "service is shutting down",
})
return
}
writeJSON(w, http.StatusOK, map[string]string{
"status": "ready",
"service": s.appName,
})
}
func writeJSON(w http.ResponseWriter, statusCode int, payload any) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(statusCode)
_ = json.NewEncoder(w).Encode(payload)
}