server: expose Handle/HandleFunc for custom checker routes

Lets plugins register auxiliary endpoints (debug pages, webhooks, UI
assets) on the SDK mux, with TrackWork as an opt-in for the /health
load signal.
This commit is contained in:
nemunaire 2026-04-23 10:12:55 +07:00
commit 0c6a886e82
2 changed files with 50 additions and 11 deletions

View file

@ -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).

View file

@ -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)