101 lines
2.7 KiB
Go
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)
|
|
}
|