-
-
Notifications
You must be signed in to change notification settings - Fork 109
/
handler.go
225 lines (196 loc) · 5.83 KB
/
handler.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package healthx
import (
"net/http"
"github.com/ory/herodot"
)
const (
// AliveCheckPath is the path where information about the life state of the instance is provided.
AliveCheckPath = "/health/alive"
// ReadyCheckPath is the path where information about the ready state of the instance is provided.
ReadyCheckPath = "/health/ready"
// VersionPath is the path where information about the software version of the instance is provided.
VersionPath = "/version"
)
// RoutesToObserve returns a string of all the available routes of this module.
func RoutesToObserve() []string {
return []string{
AliveCheckPath,
ReadyCheckPath,
VersionPath,
}
}
// ReadyChecker should return an error if the component is not ready yet.
type ReadyChecker func(r *http.Request) error
// ReadyCheckers is a map of ReadyCheckers.
type ReadyCheckers map[string]ReadyChecker
// NoopReadyChecker is always ready.
func NoopReadyChecker() error {
return nil
}
// Handler handles HTTP requests to health and version endpoints.
type Handler struct {
H herodot.Writer
VersionString string
ReadyChecks ReadyCheckers
}
type options struct {
middleware func(http.Handler) http.Handler
}
type Options func(*options)
// NewHandler instantiates a handler.
func NewHandler(
h herodot.Writer,
version string,
readyChecks ReadyCheckers,
) *Handler {
return &Handler{
H: h,
VersionString: version,
ReadyChecks: readyChecks,
}
}
type router interface {
Handler(method, path string, handler http.Handler)
}
// SetHealthRoutes registers this handler's routes for health checking.
func (h *Handler) SetHealthRoutes(r router, shareErrors bool, opts ...Options) {
o := &options{}
aliveHandler := h.Alive()
readyHandler := h.Ready(shareErrors)
for _, opt := range opts {
opt(o)
}
if o.middleware != nil {
aliveHandler = o.middleware(aliveHandler)
readyHandler = o.middleware(readyHandler)
}
r.Handler("GET", AliveCheckPath, aliveHandler)
r.Handler("GET", ReadyCheckPath, readyHandler)
}
// SetVersionRoutes registers this handler's routes for health checking.
func (h *Handler) SetVersionRoutes(r router, opts ...Options) {
o := &options{}
versionHandler := h.Version()
for _, opt := range opts {
opt(o)
}
if o.middleware != nil {
versionHandler = o.middleware(versionHandler)
}
r.Handler("GET", VersionPath, versionHandler)
}
// Alive returns an ok status if the instance is ready to handle HTTP requests.
//
// swagger:route GET /health/alive health isInstanceAlive
//
// # Check alive status
//
// This endpoint returns a 200 status code when the HTTP server is up running.
// This status does currently not include checks whether the database connection is working.
//
// If the service supports TLS Edge Termination, this endpoint does not require the
// `X-Forwarded-Proto` header to be set.
//
// Be aware that if you are running multiple nodes of this service, the health status will never
// refer to the cluster state, only to a single instance.
//
// Produces:
// - application/json
// - text/plain
//
// Responses:
// 200: healthStatus
// default: unexpectedError
func (h *Handler) Alive() http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
h.H.Write(rw, r, &swaggerHealthStatus{
Status: "ok",
})
})
}
// swagger:model unexpectedError
//
//nolint:deadcode,unused
//lint:ignore U1000 Used to generate Swagger and OpenAPI definitions
type unexpectedError string
// Ready returns an ok status if the instance is ready to handle HTTP requests and all ReadyCheckers are ok.
//
// swagger:route GET /health/ready health isInstanceReady
//
// # Check readiness status
//
// This endpoint returns a 200 status code when the HTTP server is up running and the environment dependencies (e.g.
// the database) are responsive as well.
//
// If the service supports TLS Edge Termination, this endpoint does not require the
// `X-Forwarded-Proto` header to be set.
//
// Be aware that if you are running multiple nodes of this service, the health status will never
// refer to the cluster state, only to a single instance.
//
// Produces:
// - application/json
// - text/plain
//
// Responses:
// 200: healthStatus
// 503: healthNotReadyStatus
// default: unexpectedError
func (h *Handler) Ready(shareErrors bool) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
var notReady = swaggerNotReadyStatus{
Errors: map[string]string{},
}
for n, c := range h.ReadyChecks {
if err := c(r); err != nil {
if shareErrors {
notReady.Errors[n] = err.Error()
} else {
notReady.Errors[n] = "error may contain sensitive information and was obfuscated"
}
}
}
if len(notReady.Errors) > 0 {
h.H.WriteErrorCode(rw, r, http.StatusServiceUnavailable, ¬Ready)
return
}
h.H.Write(rw, r, &swaggerHealthStatus{
Status: "ok",
})
})
}
// Version returns this service's versions.
//
// swagger:route GET /version version getVersion
//
// # Get service version
//
// This endpoint returns the service version typically notated using semantic versioning.
//
// If the service supports TLS Edge Termination, this endpoint does not require the
// `X-Forwarded-Proto` header to be set.
//
// Be aware that if you are running multiple nodes of this service, the health status will never
// refer to the cluster state, only to a single instance.
//
// Produces:
// - application/json
//
// Responses:
// 200: version
func (h *Handler) Version() http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
h.H.Write(rw, r, &swaggerVersion{
Version: h.VersionString,
})
})
}
// WithMiddleware accepts a http.Handler to be run on the
// route handlers
func WithMiddleware(h func(http.Handler) http.Handler) Options {
return func(o *options) {
o.middleware = h
}
}