github.com/hellofresh/janus@v0.0.0-20230925145208-ce8de8183c67/pkg/web/checker.go (about)

     1  package web
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"net/http"
     7  	"time"
     8  
     9  	"github.com/go-chi/chi"
    10  	"github.com/hellofresh/health-go/v3"
    11  	"github.com/hellofresh/janus/pkg/api"
    12  	log "github.com/sirupsen/logrus"
    13  )
    14  
    15  // NewOverviewHandler creates instance of all status checks handler
    16  func NewOverviewHandler(cfg *api.Configuration) func(w http.ResponseWriter, r *http.Request) {
    17  	return func(w http.ResponseWriter, r *http.Request) {
    18  		defs := findValidAPIHealthChecks(cfg.Definitions)
    19  
    20  		log.WithField("len", len(defs)).Debug("Loading health check endpoints")
    21  		health.Reset()
    22  
    23  		for _, def := range defs {
    24  			log.WithField("name", def.Name).Debug("Registering health check")
    25  			health.Register(health.Config{
    26  				Name:      def.Name,
    27  				Timeout:   time.Second * time.Duration(def.HealthCheck.Timeout),
    28  				SkipOnErr: true,
    29  				Check:     check(def),
    30  			})
    31  		}
    32  
    33  		health.HandlerFunc(w, r)
    34  	}
    35  }
    36  
    37  // NewStatusHandler creates instance of single proxy status check handler
    38  func NewStatusHandler(cfgs *api.Configuration) func(w http.ResponseWriter, r *http.Request) {
    39  	return func(w http.ResponseWriter, r *http.Request) {
    40  		defs := findValidAPIHealthChecks(cfgs.Definitions)
    41  
    42  		name := chi.URLParam(r, "name")
    43  		for _, def := range defs {
    44  			if name == def.Name {
    45  				resp, err := doStatusRequest(def, false)
    46  				if err != nil {
    47  					log.WithField("name", name).WithError(err).Error("Error requesting service health status")
    48  					w.WriteHeader(http.StatusInternalServerError)
    49  					w.Write([]byte(err.Error()))
    50  					return
    51  				}
    52  
    53  				body, err := ioutil.ReadAll(resp.Body)
    54  				if closeErr := resp.Body.Close(); closeErr != nil {
    55  					log.WithField("name", name).WithError(closeErr).Error("Error closing health status body")
    56  				}
    57  
    58  				if err != nil {
    59  					log.WithField("name", name).WithError(err).Error("Error reading health status body")
    60  					w.WriteHeader(http.StatusInternalServerError)
    61  					w.Write([]byte(err.Error()))
    62  					return
    63  				}
    64  
    65  				w.WriteHeader(resp.StatusCode)
    66  				w.Write(body)
    67  				return
    68  			}
    69  		}
    70  
    71  		w.WriteHeader(http.StatusNotFound)
    72  		w.Write([]byte("Definition name is not found"))
    73  	}
    74  }
    75  
    76  func doStatusRequest(def *api.Definition, closeBody bool) (*http.Response, error) {
    77  	req, err := http.NewRequest(http.MethodGet, def.HealthCheck.URL, nil)
    78  	if err != nil {
    79  		log.WithError(err).Error("Creating the request for the health check failed")
    80  		return nil, err
    81  	}
    82  
    83  	// Inform to close the connection after the transaction is complete
    84  	req.Header.Set("Connection", "close")
    85  
    86  	resp, err := http.DefaultClient.Do(req)
    87  	if err != nil {
    88  		log.WithError(err).Error("Making the request for the health check failed")
    89  		return resp, err
    90  	}
    91  
    92  	if closeBody {
    93  		defer resp.Body.Close()
    94  	}
    95  
    96  	return resp, err
    97  }
    98  
    99  func check(def *api.Definition) func() error {
   100  	return func() error {
   101  		resp, err := doStatusRequest(def, true)
   102  		if err != nil {
   103  			return fmt.Errorf("%s health check endpoint %s is unreachable", def.Name, def.HealthCheck.URL)
   104  		}
   105  
   106  		if resp.StatusCode >= http.StatusInternalServerError {
   107  			return fmt.Errorf("%s is not available at the moment", def.Name)
   108  		}
   109  
   110  		if resp.StatusCode >= http.StatusBadRequest {
   111  			return fmt.Errorf("%s is partially available at the moment", def.Name)
   112  		}
   113  
   114  		return nil
   115  	}
   116  }
   117  
   118  func findValidAPIHealthChecks(defs []*api.Definition) []*api.Definition {
   119  	var validDefs []*api.Definition
   120  
   121  	for _, def := range defs {
   122  		if def.Active && def.HealthCheck.URL != "" {
   123  			validDefs = append(validDefs, def)
   124  		}
   125  	}
   126  
   127  	return validDefs
   128  }