github.com/icyphox/x@v0.0.355-0.20220311094250-029bd783e8b8/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/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  type router interface {
    81  	GET(path string, handle httprouter.Handle)
    82  }
    83  
    84  // SetHealthRoutes registers this handler's routes for health checking.
    85  func (h *Handler) SetHealthRoutes(r router, shareErrors bool) {
    86  	r.GET(AliveCheckPath, h.Alive)
    87  	r.GET(ReadyCheckPath, h.Ready(shareErrors))
    88  }
    89  
    90  // SetHealthRoutes registers this handler's routes for health checking.
    91  func (h *Handler) SetVersionRoutes(r router) {
    92  	r.GET(VersionPath, h.Version)
    93  }
    94  
    95  // Alive returns an ok status if the instance is ready to handle HTTP requests.
    96  //
    97  // swagger:route GET /health/alive health isInstanceAlive
    98  //
    99  // Check alive status
   100  //
   101  // This endpoint returns a 200 status code when the HTTP server is up running.
   102  // This status does currently not include checks whether the database connection is working.
   103  //
   104  // If the service supports TLS Edge Termination, this endpoint does not require the
   105  // `X-Forwarded-Proto` header to be set.
   106  //
   107  // Be aware that if you are running multiple nodes of this service, the health status will never
   108  // refer to the cluster state, only to a single instance.
   109  //
   110  //     Produces:
   111  //     - application/json
   112  //
   113  //     Responses:
   114  //       200: healthStatus
   115  //       500: genericError
   116  func (h *Handler) Alive(rw http.ResponseWriter, r *http.Request, _ httprouter.Params) {
   117  	h.H.Write(rw, r, &swaggerHealthStatus{
   118  		Status: "ok",
   119  	})
   120  }
   121  
   122  // Ready returns an ok status if the instance is ready to handle HTTP requests and all ReadyCheckers are ok.
   123  //
   124  // swagger:route GET /health/ready health isInstanceReady
   125  //
   126  // Check readiness status
   127  //
   128  // This endpoint returns a 200 status code when the HTTP server is up running and the environment dependencies (e.g.
   129  // the database) are responsive as well.
   130  //
   131  // If the service supports TLS Edge Termination, this endpoint does not require the
   132  // `X-Forwarded-Proto` header to be set.
   133  //
   134  // Be aware that if you are running multiple nodes of this service, the health status will never
   135  // refer to the cluster state, only to a single instance.
   136  //
   137  //     Produces:
   138  //     - application/json
   139  //
   140  //     Responses:
   141  //       200: healthStatus
   142  //       503: healthNotReadyStatus
   143  func (h *Handler) Ready(shareErrors bool) httprouter.Handle {
   144  	return func(rw http.ResponseWriter, r *http.Request, _ httprouter.Params) {
   145  		var notReady = swaggerNotReadyStatus{
   146  			Errors: map[string]string{},
   147  		}
   148  
   149  		for n, c := range h.ReadyChecks {
   150  			if err := c(r); err != nil {
   151  				if shareErrors {
   152  					notReady.Errors[n] = err.Error()
   153  				} else {
   154  					notReady.Errors[n] = "error may contain sensitive information and was obfuscated"
   155  				}
   156  			}
   157  		}
   158  
   159  		if len(notReady.Errors) > 0 {
   160  			h.H.WriteCode(rw, r, http.StatusServiceUnavailable, notReady)
   161  			return
   162  		}
   163  
   164  		h.H.Write(rw, r, &swaggerHealthStatus{
   165  			Status: "ok",
   166  		})
   167  	}
   168  }
   169  
   170  // Version returns this service's versions.
   171  //
   172  // swagger:route GET /version version getVersion
   173  //
   174  // Get service version
   175  //
   176  // This endpoint returns the service version typically notated using semantic versioning.
   177  //
   178  // If the service supports TLS Edge Termination, this endpoint does not require the
   179  // `X-Forwarded-Proto` header to be set.
   180  //
   181  // Be aware that if you are running multiple nodes of this service, the health status will never
   182  // refer to the cluster state, only to a single instance.
   183  //
   184  //     Produces:
   185  //     - application/json
   186  //
   187  //	   Responses:
   188  // 			200: version
   189  func (h *Handler) Version(rw http.ResponseWriter, r *http.Request, _ httprouter.Params) {
   190  	h.H.Write(rw, r, &swaggerVersion{
   191  		Version: h.VersionString,
   192  	})
   193  }