check / check (push) Successful in 3m6s
With SENTRY_DSN set, the server initialises sentry-go with the release netwatch-server-<version>, adds the sentryhttp middleware with Repanic as the last router-wide middleware, after the timeout, and flushes Sentry for 2 seconds on shutdown. A DSN Sentry refuses stops the start with an error naming SENTRY_DSN. With it empty, nothing is set up. The metrics middleware stays on the matched routes only, so it runs inside the Sentry middleware rather than before it. Model: opus-5-5
76 lines
2.4 KiB
Go
76 lines
2.4 KiB
Go
package server
|
|
|
|
import (
|
|
"time"
|
|
|
|
sentryhttp "github.com/getsentry/sentry-go/http"
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/go-chi/chi/v5/middleware"
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"github.com/prometheus/client_golang/prometheus/collectors"
|
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
|
)
|
|
|
|
const (
|
|
requestTimeout = 60 * time.Second
|
|
|
|
// maxRequestBodyBytes caps every request body. A route group
|
|
// can mount s.mw.MaxBodyBytes with a smaller value to lower
|
|
// its bound, but cannot raise it: this cap runs first.
|
|
maxRequestBodyBytes int64 = 1 << 20 // 1 MiB
|
|
)
|
|
|
|
// SetupRoutes configures the chi router with middleware and
|
|
// all application routes.
|
|
func (s *Server) SetupRoutes() {
|
|
s.router = chi.NewRouter()
|
|
|
|
s.router.Use(s.mw.Recoverer())
|
|
s.router.Use(middleware.RequestID)
|
|
s.router.Use(s.mw.Logging())
|
|
s.router.Use(s.mw.SecurityHeaders())
|
|
s.router.Use(s.mw.CORS(s.params.Config.CORSAllowedOrigins))
|
|
s.router.Use(s.mw.MaxBodyBytes(maxRequestBodyBytes))
|
|
s.router.Use(middleware.Timeout(requestTimeout))
|
|
|
|
// Sentry reports a panic, then panics again, so that s.mw.Recoverer
|
|
// still answers 500.
|
|
if s.params.Config.SentryDSN != "" {
|
|
s.router.Use(sentryhttp.New(sentryhttp.Options{Repanic: true}).Handle)
|
|
}
|
|
|
|
// The metrics go in a registry of this server's own, not in
|
|
// Prometheus' default one, which takes them only once per process.
|
|
registry := prometheus.NewRegistry()
|
|
registry.MustRegister(
|
|
collectors.NewGoCollector(),
|
|
collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}),
|
|
)
|
|
|
|
// Requests are measured only once chi has matched them to one of
|
|
// these routes, by path and method. The metrics are labelled with
|
|
// both, which any client can make up, so measuring every request
|
|
// would let clients add labels without bound. A Route here would
|
|
// be matched by its path prefix alone, so each path is given in
|
|
// full.
|
|
s.router.Group(func(r chi.Router) {
|
|
// config.New refuses one of the two credentials without the
|
|
// other.
|
|
if s.params.Config.MetricsUsername != "" {
|
|
r.Use(s.mw.Metrics(registry))
|
|
}
|
|
|
|
r.Get("/.well-known/healthcheck", s.h.HandleHealthCheck())
|
|
|
|
r.With(s.mw.RateLimit(s.params.Config.ReportsPerMinute)).
|
|
Post("/api/v1/reports", s.h.HandleReport())
|
|
})
|
|
|
|
if s.params.Config.MetricsUsername != "" {
|
|
s.router.With(s.mw.MetricsAuth()).
|
|
Get("/metrics", promhttp.HandlerFor(
|
|
registry, promhttp.HandlerOpts{},
|
|
).ServeHTTP)
|
|
}
|
|
}
|