prober.go RawHistoryFolder
1// Package httpprober provides HTTP probing functionality for health checks.
2package httpprober
3
4import (
5 "context"
6 "crypto/tls"
7 "fmt"
8 "net/http"
9 "time"
10)
11
12// DefaultTimeout is the default timeout for HTTP probes.
13const DefaultTimeout = 5 * time.Second
14
15// Prober implements the HTTPProber interface for health checking.
16type Prober struct {
17 client *http.Client
18 timeout time.Duration
19}
20
21// Option configures the Prober.
22type Option func(*Prober)
23
24// WithTimeout sets the probe timeout.
25func WithTimeout(timeout time.Duration) Option {
26 return func(p *Prober) {
27 p.timeout = timeout
28 }
29}
30
31// WithHTTPClient sets a custom HTTP client.
32func WithHTTPClient(client *http.Client) Option {
33 return func(p *Prober) {
34 p.client = client
35 }
36}
37
38// New creates a new HTTP prober.
39func New(opts ...Option) *Prober {
40 p := &Prober{
41 timeout: DefaultTimeout,
42 }
43
44 for _, opt := range opts {
45 opt(p)
46 }
47
48 if p.client == nil {
49 p.client = &http.Client{
50 Timeout: p.timeout,
51 Transport: &http.Transport{
52 // #nosec G402 - InsecureSkipVerify is intentional for health probing
53 // internal services that may use self-signed certificates.
54 // This prober only checks reachability, not data integrity.
55 TLSClientConfig: &tls.Config{
56 InsecureSkipVerify: true,
57 },
58 DisableKeepAlives: true,
59 },
60 // Don't follow redirects - we want to see the actual response
61 CheckRedirect: func(req *http.Request, via []*http.Request) error {
62 return http.ErrUseLastResponse
63 },
64 }
65 }
66
67 return p
68}
69
70// Probe sends an HTTP GET request and returns the status code and response time.
71func (p *Prober) Probe(ctx context.Context, url string) (int, int64, error) {
72 start := time.Now()
73
74 req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
75 if err != nil {
76 return 0, 0, fmt.Errorf("failed to create request: %w", err)
77 }
78
79 // Set a reasonable user agent
80 req.Header.Set("User-Agent", "Gordon-HealthCheck/1.0")
81
82 resp, err := p.client.Do(req)
83 if err != nil {
84 elapsed := time.Since(start).Milliseconds()
85 return 0, elapsed, fmt.Errorf("request failed: %w", err)
86 }
87 defer resp.Body.Close()
88
89 elapsed := time.Since(start).Milliseconds()
90 return resp.StatusCode, elapsed, nil
91}