rara/packages/go/httpserver/server.go
toki fdc86c7ff4
Some checks are pending
ci / validate (push) Waiting to run
initial commit
2026-07-18 18:41:17 +09:00

101 lines
2.7 KiB
Go

package httpserver
import (
"context"
"errors"
"net"
"net/http"
"time"
"git.toki-labs.com/toki/rara/packages/go/config"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"go.uber.org/fx"
"go.uber.org/zap"
)
type Readiness func(context.Context) error
type Params struct {
fx.In
Lifecycle fx.Lifecycle
Config config.Config
Logger *zap.Logger
Registry *prometheus.Registry
Readiness Readiness `optional:"true"`
}
func NewMux() *http.ServeMux {
return http.NewServeMux()
}
func Start(params Params, mux *http.ServeMux) {
readiness := params.Readiness
if readiness == nil {
readiness = func(context.Context) error { return nil }
}
mux.HandleFunc("GET /healthz", func(writer http.ResponseWriter, _ *http.Request) {
writeJSON(writer, http.StatusOK, []byte(`{"status":"ok"}`))
})
mux.HandleFunc("GET /readyz", func(writer http.ResponseWriter, request *http.Request) {
ctx, cancel := context.WithTimeout(request.Context(), 2*time.Second)
defer cancel()
if err := readiness(ctx); err != nil {
writeJSON(writer, http.StatusServiceUnavailable, []byte(`{"status":"unavailable"}`))
return
}
writeJSON(writer, http.StatusOK, []byte(`{"status":"ready"}`))
})
mux.Handle("GET /metrics", promhttp.HandlerFor(params.Registry, promhttp.HandlerOpts{}))
server := &http.Server{
Addr: params.Config.Service.Listen,
Handler: requestLogger(params.Logger, mux),
ReadHeaderTimeout: 10 * time.Second,
}
var listener net.Listener
params.Lifecycle.Append(fx.Hook{
OnStart: func(context.Context) error {
var err error
listener, err = net.Listen("tcp", params.Config.Service.Listen)
if err != nil {
return err
}
params.Logger.Info(
"HTTP server listening",
zap.String("service", params.Config.Service.Name),
zap.String("address", params.Config.Service.Listen),
)
go func() {
if err := server.Serve(listener); err != nil && !errors.Is(err, http.ErrServerClosed) {
params.Logger.Error("HTTP server stopped unexpectedly", zap.Error(err))
}
}()
return nil
},
OnStop: func(ctx context.Context) error {
return server.Shutdown(ctx)
},
})
}
func requestLogger(logger *zap.Logger, next http.Handler) http.Handler {
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
started := time.Now()
next.ServeHTTP(writer, request)
logger.Debug(
"HTTP request",
zap.String("method", request.Method),
zap.String("path", request.URL.Path),
zap.Duration("duration", time.Since(started)),
)
})
}
func writeJSON(writer http.ResponseWriter, status int, body []byte) {
writer.Header().Set("Content-Type", "application/json")
writer.WriteHeader(status)
_, _ = writer.Write(body)
}