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, &notReady)
   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  }