-
Notifications
You must be signed in to change notification settings - Fork 24
/
http.go
128 lines (101 loc) · 3.51 KB
/
http.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
package http
import (
"bytes"
"crypto/tls"
"fmt"
"net"
"net/http"
"time"
"github.com/jumppad-labs/jumppad/pkg/clients/logger"
)
// HTTP defines an interface for a HTTP client
//go:generate mockery --name HTTP --filename http.go
type HTTP interface {
// HealthCheckHTTP makes a HTTP GET request to the given URI and
// if a successful status []codes is returned the method returns a nil error.
// If it is not possible to contact the URI or if any status other than the passed codes is returned
// by the upstream, then the URI is retried until the timeout elapses.
HealthCheckHTTP(uri, method string, headers map[string][]string, body string, codes []int, timeout time.Duration) error
// HealthCheckTCP attempts to connect to a raw socket at the given address
// if a connection is established the health check is marked as a success
// if failed the check will retry until the timeout occurs
HealthCheckTCP(uri string, timeout time.Duration) error
// Do executes a HTTP request and returns the response
Do(r *http.Request) (*http.Response, error)
}
type HTTPImpl struct {
backoff time.Duration
httpc *http.Client
l logger.Logger
}
func NewHTTP(backoff time.Duration, l logger.Logger) HTTP {
httpc := &http.Client{}
httpc.Transport = http.DefaultTransport.(*http.Transport).Clone()
httpc.Transport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
return &HTTPImpl{backoff, httpc, l}
}
// HealthCheckHTTP checks a http or HTTPS endpoint for a status 200
func (h *HTTPImpl) HealthCheckHTTP(address, method string, headers map[string][]string, body string, codes []int, timeout time.Duration) error {
h.l.Debug("Performing HTTP health check for address", "address", address, "method", method, "headers", headers, "body", body, "codes", codes)
st := time.Now()
for {
if time.Since(st) > timeout {
h.l.Error("Timeout waiting for HTTP health check", "address", address)
return fmt.Errorf("timeout waiting for HTTP health check %s", address)
}
if method != "" {
method = http.MethodGet
}
buffBody := bytes.NewBuffer([]byte(body))
rq, err := http.NewRequest(method, address, buffBody)
if err != nil {
return fmt.Errorf("unable to constrcut http request: %s", err)
}
rq.Header = headers
if len(codes) == 0 {
codes = []int{200}
}
resp, err := h.httpc.Do(rq)
if err == nil && assertResponseCode(codes, resp.StatusCode) {
h.l.Debug("HTTP health check complete", "address", address)
return nil
}
status := 0
if err == nil {
status = resp.StatusCode
}
// back off
h.l.Debug("HTTP health check failed, retrying", "address", address, "response", status, "error", err)
time.Sleep(h.backoff)
}
}
func (h *HTTPImpl) HealthCheckTCP(address string, timeout time.Duration) error {
h.l.Debug("Performing TCP health check for address", "address", address)
st := time.Now()
for {
if time.Since(st) > timeout {
h.l.Error("timeout waiting for TCP health check", "address", address)
return fmt.Errorf("timeout waiting for HTTP health check %s", address)
}
// attempt to open a socket
_, err := net.Dial("tcp", address)
if err == nil {
h.l.Debug("TCP health check complete", "address", address)
return nil
}
// backoff
time.Sleep(h.backoff)
}
}
func assertResponseCode(codes []int, responseCode int) bool {
for _, c := range codes {
if responseCode == c {
return true
}
}
return false
}
// Do executes a HTTP request and returns the response
func (h *HTTPImpl) Do(r *http.Request) (*http.Response, error) {
return h.httpc.Do(r)
}