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/julienschmidt/httprouter" 27 28 "github.com/ory/herodot" 29 ) 30 31 const ( 32 // AliveCheckPath is the path where information about the life state of the instance is provided. 33 AliveCheckPath = "/health/alive" 34 // ReadyCheckPath is the path where information about the rady state of the instance is provided. 35 ReadyCheckPath = "/health/ready" 36 // VersionPath is the path where information about the software version of the instance is provided. 37 VersionPath = "/version" 38 ) 39 40 // RoutesToObserve returns a string of all the available routes of this module. 41 func RoutesToObserve() []string { 42 return []string{ 43 AliveCheckPath, 44 ReadyCheckPath, 45 VersionPath, 46 } 47 } 48 49 // ReadyChecker should return an error if the component is not ready yet. 50 type ReadyChecker func(r *http.Request) error 51 52 // ReadyCheckers is a map of ReadyCheckers. 53 type ReadyCheckers map[string]ReadyChecker 54 55 // NoopReadyChecker is always ready. 56 func NoopReadyChecker() error { 57 return nil 58 } 59 60 // Handler handles HTTP requests to health and version endpoints. 61 type Handler struct { 62 H herodot.Writer 63 VersionString string 64 ReadyChecks ReadyCheckers 65 } 66 67 // NewHandler instantiates a handler. 68 func NewHandler( 69 h herodot.Writer, 70 version string, 71 readyChecks ReadyCheckers, 72 ) *Handler { 73 return &Handler{ 74 H: h, 75 VersionString: version, 76 ReadyChecks: readyChecks, 77 } 78 } 79 80 // SetHealthRoutes registers this handler's routes for health checking. 81 func (h *Handler) SetHealthRoutes(r *httprouter.Router, shareErrors bool) { 82 r.GET(AliveCheckPath, h.Alive) 83 r.GET(ReadyCheckPath, h.Ready(shareErrors)) 84 } 85 86 // SetHealthRoutes registers this handler's routes for health checking. 87 func (h *Handler) SetVersionRoutes(r *httprouter.Router) { 88 r.GET(VersionPath, h.Version) 89 } 90 91 // Alive returns an ok status if the instance is ready to handle HTTP requests. 92 // 93 // swagger:route GET /health/alive health isInstanceAlive 94 // 95 // Check alive status 96 // 97 // This endpoint returns a 200 status code when the HTTP server is up running. 98 // This status does currently not include checks whether the database connection is working. 99 // 100 // If the service supports TLS Edge Termination, this endpoint does not require the 101 // `X-Forwarded-Proto` header to be set. 102 // 103 // Be aware that if you are running multiple nodes of this service, the health status will never 104 // refer to the cluster state, only to a single instance. 105 // 106 // Produces: 107 // - application/json 108 // 109 // Responses: 110 // 200: healthStatus 111 // 500: genericError 112 func (h *Handler) Alive(rw http.ResponseWriter, r *http.Request, _ httprouter.Params) { 113 h.H.Write(rw, r, &swaggerHealthStatus{ 114 Status: "ok", 115 }) 116 } 117 118 // Ready returns an ok status if the instance is ready to handle HTTP requests and all ReadyCheckers are ok. 119 // 120 // swagger:route GET /health/ready health isInstanceReady 121 // 122 // Check readiness status 123 // 124 // This endpoint returns a 200 status code when the HTTP server is up running and the environment dependencies (e.g. 125 // the database) are responsive as well. 126 // 127 // If the service supports TLS Edge Termination, this endpoint does not require the 128 // `X-Forwarded-Proto` header to be set. 129 // 130 // Be aware that if you are running multiple nodes of this service, the health status will never 131 // refer to the cluster state, only to a single instance. 132 // 133 // Produces: 134 // - application/json 135 // 136 // Responses: 137 // 200: healthStatus 138 // 503: healthNotReadyStatus 139 func (h *Handler) Ready(shareErrors bool) httprouter.Handle { 140 return func(rw http.ResponseWriter, r *http.Request, _ httprouter.Params) { 141 var notReady = swaggerNotReadyStatus{ 142 Errors: map[string]string{}, 143 } 144 145 for n, c := range h.ReadyChecks { 146 if err := c(r); err != nil { 147 if shareErrors { 148 notReady.Errors[n] = err.Error() 149 } else { 150 notReady.Errors[n] = "error may contain sensitive information and was obfuscated" 151 } 152 } 153 } 154 155 if len(notReady.Errors) > 0 { 156 h.H.WriteCode(rw, r, http.StatusServiceUnavailable, notReady) 157 return 158 } 159 160 h.H.Write(rw, r, &swaggerHealthStatus{ 161 Status: "ok", 162 }) 163 } 164 } 165 166 // Version returns this service's versions. 167 // 168 // swagger:route GET /version version getVersion 169 // 170 // Get service version 171 // 172 // This endpoint returns the service version typically notated using semantic versioning. 173 // 174 // If the service supports TLS Edge Termination, this endpoint does not require the 175 // `X-Forwarded-Proto` header to be set. 176 // 177 // Be aware that if you are running multiple nodes of this service, the health status will never 178 // refer to the cluster state, only to a single instance. 179 // 180 // Produces: 181 // - application/json 182 // 183 // Responses: 184 // 200: version 185 func (h *Handler) Version(rw http.ResponseWriter, r *http.Request, _ httprouter.Params) { 186 h.H.Write(rw, r, &swaggerVersion{ 187 Version: h.VersionString, 188 }) 189 } 190