154 lines
3.8 KiB
Go
154 lines
3.8 KiB
Go
package srv
|
|
|
|
import (
|
|
"context"
|
|
"net"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
|
"github.com/rs/zerolog"
|
|
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
|
"go.opentelemetry.io/otel/metric"
|
|
"go.opentelemetry.io/otel/trace"
|
|
|
|
"gitea.libretechconsulting.com/rmcguire/go-app/pkg/config"
|
|
"gitea.libretechconsulting.com/rmcguire/go-app/pkg/otel"
|
|
)
|
|
|
|
var (
|
|
httpMeter metric.Meter
|
|
httpTracer trace.Tracer
|
|
defReadTimeout = 10 * time.Second
|
|
defWriteTimeout = 10 * time.Second
|
|
defIdleTimeout = 15 * time.Second
|
|
)
|
|
|
|
type HTTPFunc struct {
|
|
Path string
|
|
HandlerFunc http.HandlerFunc
|
|
}
|
|
|
|
func prepHTTPServer(ctx context.Context, handleFuncs []HTTPFunc, hcFuncs ...HealthCheckFunc) *http.Server {
|
|
var (
|
|
cfg = config.MustFromCtx(ctx)
|
|
l = zerolog.Ctx(ctx)
|
|
mux = &http.ServeMux{}
|
|
)
|
|
|
|
// NOTE: Wraps handle func with otelhttp handler and
|
|
// inserts route tag
|
|
otelHandleFunc := func(pattern string, handlerFunc func(http.ResponseWriter, *http.Request)) {
|
|
handler := otelhttp.WithRouteTag(pattern, http.HandlerFunc(handlerFunc))
|
|
mux.Handle(pattern, handler) // Associate pattern with handler
|
|
}
|
|
|
|
healthChecks := handleHealthCheckFunc(ctx, hcFuncs...)
|
|
otelHandleFunc("/health", healthChecks)
|
|
otelHandleFunc("/", healthChecks)
|
|
|
|
for _, f := range handleFuncs {
|
|
otelHandleFunc(f.Path, f.HandlerFunc)
|
|
}
|
|
|
|
// Prometheus metrics endpoint
|
|
if cfg.OTEL.PrometheusEnabled {
|
|
mux.Handle(cfg.OTEL.PrometheusPath, promhttp.Handler())
|
|
l.Info().Str("prometheusPath", cfg.OTEL.PrometheusPath).
|
|
Msg("mounted prometheus metrics endpoint")
|
|
}
|
|
|
|
// Add OTEL, skip health-check spans
|
|
// NOTE: Add any other span exclusions here
|
|
handler := otelhttp.NewHandler(mux, "/",
|
|
otelhttp.WithFilter(func(r *http.Request) bool {
|
|
switch r.URL.Path {
|
|
case "/health":
|
|
return false
|
|
case cfg.OTEL.PrometheusPath:
|
|
return false
|
|
default:
|
|
return true
|
|
}
|
|
}))
|
|
|
|
// Set timeouts from defaults, override
|
|
// with config timeouts if set
|
|
readTimeout := defReadTimeout
|
|
writeTimeout := defWriteTimeout
|
|
idleTimeout := defIdleTimeout
|
|
|
|
rT, wT, iT := cfg.HTTP.Timeouts()
|
|
if rT != nil {
|
|
readTimeout = *rT
|
|
}
|
|
if wT != nil {
|
|
writeTimeout = *wT
|
|
}
|
|
if iT != nil {
|
|
idleTimeout = *iT
|
|
}
|
|
|
|
// Inject logging middleware
|
|
if cfg.HTTP.LogRequests {
|
|
handler = loggingMiddleware(ctx, handler)
|
|
}
|
|
|
|
return &http.Server{
|
|
Addr: cfg.HTTP.Listen,
|
|
ReadTimeout: readTimeout,
|
|
WriteTimeout: writeTimeout,
|
|
IdleTimeout: idleTimeout,
|
|
Handler: handler,
|
|
BaseContext: func(_ net.Listener) context.Context {
|
|
return ctx
|
|
},
|
|
}
|
|
}
|
|
|
|
// Returns a shutdown func and a done channel if the
|
|
// server aborts abnormally. Panics on error.
|
|
func MustInitHTTPServer(ctx context.Context, funcs []HTTPFunc, hcFuncs ...HealthCheckFunc) (
|
|
func(context.Context) error, <-chan interface{},
|
|
) {
|
|
shutdownFunc, doneChan, err := InitHTTPServer(ctx, funcs, hcFuncs...)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return shutdownFunc, doneChan
|
|
}
|
|
|
|
// Returns a shutdown func and a done channel if the
|
|
// server aborts abnormally. Returns error on failure to start
|
|
func InitHTTPServer(ctx context.Context, funcs []HTTPFunc, hcFuncs ...HealthCheckFunc) (
|
|
func(context.Context) error, <-chan interface{}, error,
|
|
) {
|
|
l := zerolog.Ctx(ctx)
|
|
doneChan := make(chan interface{})
|
|
|
|
var server *http.Server
|
|
|
|
httpMeter = otel.GetMeter(ctx, "http")
|
|
httpTracer = otel.GetTracer(ctx, "http")
|
|
|
|
server = prepHTTPServer(ctx, funcs, hcFuncs...)
|
|
|
|
go func() {
|
|
l.Debug().Msg("HTTP Server Started")
|
|
err := server.ListenAndServe()
|
|
if err != nil && err != http.ErrServerClosed {
|
|
l.Err(err).Msg("HTTP server error")
|
|
} else {
|
|
l.Info().Msg("HTTP server shut down")
|
|
}
|
|
doneChan <- nil
|
|
}()
|
|
|
|
// Shut down http server with a deadline
|
|
return func(shutdownCtx context.Context) error {
|
|
l.Debug().Msg("stopping http server")
|
|
server.Shutdown(shutdownCtx)
|
|
return nil
|
|
}, doneChan, nil
|
|
}
|