package gohealthchecks import ( "bytes" "context" "fmt" "io" "net/http" "net/url" "strconv" ) func handleResponse(resp *http.Response) error { if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusCreated { return nil // don't do anything more here, we would just have a `OK` or `Created` in the body } respBody, err := io.ReadAll(resp.Body) if err != nil { return fmt.Errorf("failed to read response body: %w", err) } switch resp.StatusCode { case http.StatusNotFound: return ErrCheckNotFound case http.StatusConflict: return ErrSlugConflict case http.StatusTooManyRequests: return ErrRateLimit case http.StatusBadRequest: return fmt.Errorf("invalid URL format: %s", string(respBody)) default: return fmt.Errorf("unexpected statuscode %d (%s)", resp.StatusCode, string(respBody)) } } func addQueryParams(reqURL *url.URL, check Check) { q := reqURL.Query() for param, value := range check.params() { q.Add(param, value) } reqURL.RawQuery = q.Encode() } type PingClient interface { ReportSuccess(ctx context.Context, check Check) error ReportStart(ctx context.Context, check Check) error ReportFailure(ctx context.Context, check Check) error LogMessage(ctx context.Context, check Check, log []byte) error ReportExitCode(ctx context.Context, check Check, exitCode int) error } type pingClient struct { httpClt http.Client host string } func (c *pingClient) ReportSuccess(ctx context.Context, check Check) error { url, err := url.JoinPath(c.host, check.path()) if err != nil { return fmt.Errorf("invalid check or hostname provided: %w", err) } req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, nil) if err != nil { return fmt.Errorf("failed to build ping request: %w", err) } addQueryParams(req.URL, check) resp, err := c.httpClt.Do(req) if err != nil { return fmt.Errorf("failed to send ping: %w", err) } return handleResponse(resp) } func (c *pingClient) ReportStart(ctx context.Context, check Check) error { url, err := url.JoinPath(c.host, check.path(), "start") if err != nil { return fmt.Errorf("invalid check or hostname provided: %w", err) } req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, nil) if err != nil { return fmt.Errorf("failed to build ping request: %w", err) } addQueryParams(req.URL, check) resp, err := c.httpClt.Do(req) if err != nil { return fmt.Errorf("failed to notify start: %w", err) } return handleResponse(resp) } func (c *pingClient) ReportFailure(ctx context.Context, check Check) error { url, err := url.JoinPath(c.host, check.path(), "fail") if err != nil { return fmt.Errorf("invalid check or hostname provided: %w", err) } req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, nil) if err != nil { return fmt.Errorf("failed to build ping request: %w", err) } addQueryParams(req.URL, check) resp, err := c.httpClt.Do(req) if err != nil { return fmt.Errorf("failed to send ping: %w", err) } return handleResponse(resp) } func (c *pingClient) LogMessage(ctx context.Context, check Check, log []byte) error { url, err := url.JoinPath(c.host, check.path(), "log") if err != nil { return fmt.Errorf("invalid check or hostname provided: %w", err) } reqBody := bytes.NewBuffer(log) req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, reqBody) if err != nil { return fmt.Errorf("failed to build log request: %w", err) } addQueryParams(req.URL, check) resp, err := c.httpClt.Do(req) if err != nil { return fmt.Errorf("failed to send log: %w", err) } return handleResponse(resp) } func (c *pingClient) ReportExitCode(ctx context.Context, check Check, exitCode int) error { url, err := url.JoinPath(c.host, check.path(), strconv.FormatInt(int64(exitCode), 10)) if err != nil { return fmt.Errorf("invalid check or hostname provided: %w", err) } req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, nil) if err != nil { return fmt.Errorf("failed to build exitcode request: %w", err) } addQueryParams(req.URL, check) resp, err := c.httpClt.Do(req) if err != nil { return fmt.Errorf("failed to send exitcode: %w", err) } return handleResponse(resp) } func NewPingClient(host string) PingClient { return &pingClient{ httpClt: *http.DefaultClient, host: host, } }