Seed from go-template-repo, renamed to simplexcalc
The template's files at a77fd30, without its history or LICENSE, after script/rename simplexcalc. Model: opus-5-5
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"slices"
|
||||
)
|
||||
|
||||
// HealthResponse is the healthcheck body. It is a typed struct rather
|
||||
// than a map so that the shape is part of the code and a change to it
|
||||
// shows up in a diff: something is always parsing this.
|
||||
type HealthResponse struct {
|
||||
OK bool `json:"ok"`
|
||||
App string `json:"app"`
|
||||
Version string `json:"version"`
|
||||
SchemaVersion int `json:"schema_version"`
|
||||
DatabaseOK bool `json:"database_ok"`
|
||||
SentryEnabled bool `json:"sentry_enabled"`
|
||||
MetricsProtected bool `json:"metrics_protected"`
|
||||
}
|
||||
|
||||
// Healthcheck answers with the process's own view of whether it is
|
||||
// working. It touches the database on purpose: a health endpoint that
|
||||
// only proves the HTTP server is up will report healthy through the
|
||||
// entire outage that matters.
|
||||
//
|
||||
// A failure answers 503, not 200-with-ok-false. Everything that reads
|
||||
// this — a load balancer, a container runtime, a monitoring probe —
|
||||
// looks at the status code first, and several look at nothing else.
|
||||
func (h *Handlers) Healthcheck() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
resp := HealthResponse{
|
||||
App: h.params.Globals.Appname,
|
||||
Version: h.params.Globals.Version,
|
||||
SentryEnabled: h.params.Sentry.Enabled(),
|
||||
MetricsProtected: h.params.Config.MetricsUser != "",
|
||||
}
|
||||
|
||||
versions, err := h.params.Database.AppliedVersions(r.Context())
|
||||
if err == nil {
|
||||
resp.DatabaseOK = true
|
||||
resp.OK = true
|
||||
|
||||
if len(versions) > 0 {
|
||||
resp.SchemaVersion = slices.Max(versions)
|
||||
}
|
||||
} else {
|
||||
h.log.Error("healthcheck: database unreachable", "error", err)
|
||||
}
|
||||
|
||||
status := http.StatusOK
|
||||
if !resp.OK {
|
||||
status = http.StatusServiceUnavailable
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
// A cached healthcheck is a healthcheck that reports the past.
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
w.WriteHeader(status)
|
||||
|
||||
encodeErr := json.NewEncoder(w).Encode(resp)
|
||||
if encodeErr != nil {
|
||||
// The status and headers are already sent, so there is
|
||||
// nothing to answer with; the log is the only record left.
|
||||
h.log.Error("healthcheck: encoding response failed", "error", encodeErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Panic is a route that panics, mounted only when DEBUG is on. It is
|
||||
// how the panic recoverer is exercised by hand in a running process;
|
||||
// the automated proof is in the middleware tests.
|
||||
func (h *Handlers) Panic() http.HandlerFunc {
|
||||
return func(_ http.ResponseWriter, _ *http.Request) {
|
||||
panic("deliberate panic from the debug route")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user