1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: Apache-2.0
15 // Func is a health-check function: it returns nil when healthy, an
17 type Func func() error
19 // Routes is a map of URI path to health-check function.
20 type Routes map[string]Func
22 // Handler is an http.Handler that responds to authenticated
23 // health-check requests with JSON responses like {"health":"OK"} or
24 // {"health":"ERROR","error":"error text"}.
26 // Fields of a Handler should not be changed after the Handler is
32 // Authentication token. If empty, all requests will return 404.
35 // Route prefix, typically "/_health/".
38 // Map of URI paths to health-check Func. The prefix is
39 // omitted: Routes["foo"] is the health check invoked by a
40 // request to "{Prefix}/foo".
42 // If "ping" is not listed here, it will be added
43 // automatically and will always return a "healthy" response.
46 // If non-nil, Log is called after handling each request. The
47 // error argument is nil if the request was successfully
48 // authenticated and served, even if the health check itself
50 Log func(*http.Request, error)
53 // ServeHTTP implements http.Handler.
54 func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
55 h.setupOnce.Do(h.setup)
59 func (h *Handler) setup() {
60 h.mux = http.NewServeMux()
62 if !strings.HasSuffix(prefix, "/") {
65 for name, fn := range h.Routes {
66 h.mux.Handle(prefix+name, h.healthJSON(fn))
68 if _, ok := h.Routes["ping"]; !ok {
69 h.mux.Handle(prefix+"ping", h.healthJSON(func() error { return nil }))
74 healthyBody = []byte(`{"health":"OK"}` + "\n")
75 errNotFound = errors.New(http.StatusText(http.StatusNotFound))
76 errUnauthorized = errors.New(http.StatusText(http.StatusUnauthorized))
77 errForbidden = errors.New(http.StatusText(http.StatusForbidden))
80 func (h *Handler) healthJSON(fn Func) http.Handler {
81 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
89 http.Error(w, "disabled", http.StatusNotFound)
91 } else if ah := r.Header.Get("Authorization"); ah == "" {
92 http.Error(w, "authorization required", http.StatusUnauthorized)
94 } else if ah != "Bearer "+h.Token {
95 http.Error(w, "authorization error", http.StatusForbidden)
97 } else if err = fn(); err == nil {
98 w.Header().Set("Content-Type", "application/json")
101 w.Header().Set("Content-Type", "application/json")
102 enc := json.NewEncoder(w)
103 err = enc.Encode(map[string]string{
105 "error": err.Error(),