Files
watchdog/checks.go

140 lines
3.8 KiB
Go
Raw Permalink Normal View History

2025-07-12 22:01:23 +03:00
package watchdog
import (
"context"
"fmt"
"io"
"net"
"net/http"
"net/url"
"time"
)
// DefaultTimeout is used to limit checks duration
const DefaultTimeout = time.Second * 10
// GetHTTP creates a CheckFunc that operates as follows.
// The check makes a request with GET method to provided addr using http.DefaultClient.
2025-07-12 22:01:23 +03:00
// If request fails within specified timeout the returned status is StatusDown.
// The function then tries to read response body. If it fails,
// the returned status is StatusDown.
//
// If request succeeds but reponse code is not 200, the returned
// status is StatusDown and response body is contained in the
2025-07-12 22:01:23 +03:00
// returned error.
//
// GetHTTP return an error if addr can not be parsed with url.Parse.
// If zero timeout is provided then the DefaultTimeout (10 second) is used.
func GetHTTP(addr string, timeout time.Duration) (CheckFunc, error) {
u, err := url.Parse(addr)
if err != nil {
return nil, fmt.Errorf("coulf not parse URL: %w", err)
}
if timeout == 0 {
timeout = DefaultTimeout
}
return func(ctx context.Context) (Status, error) {
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
if err != nil {
return StatusUnknown, fmt.Errorf("failed to create http request: %w", err)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return StatusDown, fmt.Errorf("do request failed: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return StatusDown, fmt.Errorf("err reading response body: %w", err)
}
if resp.StatusCode != http.StatusOK {
return StatusDown, fmt.Errorf("got HTTP response code %d, body: %s", resp.StatusCode, string(body))
}
return StatusOK, nil
}, nil
}
// HeadHTTP creates a CheckFunc that operates as follows.
// The check make a request with HEAD method to provided addr using http.DefaultClient.
2025-07-12 22:01:23 +03:00
// If request fails within specified timeout the returned status is StatusDown.
//
// If request succeeds but reponse code is not 200, the returned
// status is StatusDown.
2025-07-12 22:01:23 +03:00
//
// HeadHTTP return an error if addr can not be parsed with url.Parse.
// If zero timeout is provided then the DefaultTimeout (10 second) is used.
func HeadHTTP(addr string, timeout time.Duration) (CheckFunc, error) {
u, err := url.Parse(addr)
if err != nil {
return nil, fmt.Errorf("coulf not parse URL: %w", err)
}
if timeout == 0 {
timeout = DefaultTimeout
}
return func(ctx context.Context) (Status, error) {
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodHead, u.String(), nil)
if err != nil {
return StatusUnknown, fmt.Errorf("failed to create http request: %w", err)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return StatusDown, fmt.Errorf("do request failed: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return StatusDown, fmt.Errorf("got HTTP response code %d", resp.StatusCode)
}
return StatusOK, nil
2025-07-12 22:01:23 +03:00
}, nil
}
// DialTCP creates a CheckFunc that may be used to check tcp connectivity
// to a host.
//
2025-07-12 22:01:23 +03:00
// The check tries to net.DialTimeout to the provided addr. If it fails,
// the returned status is StatusDown.
//
// No validation of addr is made.
//
2025-07-12 22:01:23 +03:00
// If zero timeout is provided then the DefaultTimeout (10 second) is used.
func DialTCP(addr string, timeout time.Duration) (CheckFunc, error) {
if timeout == 0 {
timeout = DefaultTimeout
}
return func(ctx context.Context) (Status, error) {
deadline := time.Now().Add(timeout)
if t, ok := ctx.Deadline(); ok && t.Before(deadline) {
deadline = t
}
conn, err := net.DialTimeout("tcp", addr, time.Until(deadline))
if err != nil {
return StatusDown, fmt.Errorf("error dialing: %w", err)
}
defer conn.Close()
return StatusOK, nil
}, nil
}