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