diff --git a/README.md b/README.md index ed5f3f6..7f47eb8 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,32 @@ go get git.happydns.org/checker-sdk-go/checker See [checker-dummy](https://git.happydns.org/checker-dummy) for a fully working, documented template. +## Extending the server + +`checker.Server` exposes the standard SDK routes (`/health`, `/collect`, +and, depending on the provider's optional interfaces, `/definition`, +`/evaluate`, `/report`). Plugins that need to serve auxiliary endpoints +(debug pages, webhooks, custom UI assets, …) can register them on the +same mux: + +```go +srv := checker.NewServer(provider) + +srv.HandleFunc("GET /debug/state", func(w http.ResponseWriter, r *http.Request) { + // … +}) + +// Opt a custom route into the in-flight / load-average signal +// reported on /health: +srv.Handle("POST /webhook", srv.TrackWork(myWebhookHandler)) + +log.Fatal(srv.ListenAndServe(":8080")) +``` + +Patterns that collide with built-in routes panic at registration — +pick non-overlapping paths. Custom handlers are not wrapped by the +load-tracking middleware unless you opt in via `TrackWork`. + ## License Apache License 2.0. See [LICENSE](LICENSE) and [NOTICE](NOTICE). diff --git a/checker/server.go b/checker/server.go index 444c118..6163497 100644 --- a/checker/server.go +++ b/checker/server.go @@ -111,19 +111,21 @@ func NewServer(provider ObservationProvider) *Server { } s.mux = http.NewServeMux() s.mux.HandleFunc("GET /health", s.handleHealth) - s.mux.Handle("POST /collect", s.trackWork(http.HandlerFunc(s.handleCollect))) + s.mux.Handle("POST /collect", s.TrackWork(http.HandlerFunc(s.handleCollect))) if dp, ok := provider.(CheckerDefinitionProvider); ok { - s.definition = dp.Definition() - s.definition.BuildRulesInfo() - s.mux.HandleFunc("GET /definition", s.handleDefinition) - s.mux.Handle("POST /evaluate", s.trackWork(http.HandlerFunc(s.handleEvaluate))) + if def := dp.Definition(); def != nil { + s.definition = def + s.definition.BuildRulesInfo() + s.mux.HandleFunc("GET /definition", s.handleDefinition) + s.mux.Handle("POST /evaluate", s.TrackWork(http.HandlerFunc(s.handleEvaluate))) + } } if _, ok := provider.(CheckerHTMLReporter); ok { - s.mux.Handle("POST /report", s.trackWork(http.HandlerFunc(s.handleReport))) + s.mux.Handle("POST /report", s.TrackWork(http.HandlerFunc(s.handleReport))) } else if _, ok := provider.(CheckerMetricsReporter); ok { - s.mux.Handle("POST /report", s.trackWork(http.HandlerFunc(s.handleReport))) + s.mux.Handle("POST /report", s.TrackWork(http.HandlerFunc(s.handleReport))) } go s.runSampler(ctx) @@ -137,6 +139,18 @@ func (s *Server) Handler() http.Handler { return requestLogger(s.mux) } +// Handle registers an auxiliary handler on the server's mux. Must be called +// before ListenAndServe or Handler(). Custom handlers are not tracked by +// TrackWork; wrap them explicitly if you want them counted in /health load. +func (s *Server) Handle(pattern string, handler http.Handler) { + s.mux.Handle(pattern, handler) +} + +// HandleFunc is the http.HandlerFunc-flavoured counterpart of Handle. +func (s *Server) HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) { + s.mux.HandleFunc(pattern, handler) +} + // ListenAndServe starts the HTTP server on the given address. // // ListenAndServe does not stop the background load-average sampler on return; @@ -158,10 +172,9 @@ func (s *Server) Close() error { return nil } -// trackWork wraps a handler with in-flight and total-request accounting. -// It is applied only to "work" endpoints (/collect, /evaluate, /report) so -// that /health polling traffic does not pollute the load signal. -func (s *Server) trackWork(next http.Handler) http.Handler { +// TrackWork wraps a handler with in-flight and total-request accounting, +// opting custom routes into the load signal reported on /health. +func (s *Server) TrackWork(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { s.inFlight.Add(1) s.totalRequests.Add(1)