1
16
17 package http
18
19 import (
20 "bytes"
21 "fmt"
22 "io"
23 "math"
24 "net/http"
25 "time"
26
27 "github.com/sirupsen/logrus"
28 )
29
30 const (
31 defaultPostContentType = "application/octet-stream"
32 )
33
34
35
36
37
38 type Agent struct {
39 options *agentOptions
40 AgentImplementation
41 }
42
43
44
45
46 type AgentImplementation interface {
47 SendPostRequest(*http.Client, string, []byte, string) (*http.Response, error)
48 SendGetRequest(*http.Client, string) (*http.Response, error)
49 }
50
51 type defaultAgentImplementation struct{}
52
53
54 type agentOptions struct {
55 FailOnHTTPError bool
56 Retries uint
57 Timeout time.Duration
58 MaxWaitTime time.Duration
59 PostContentType string
60 }
61
62
63 func (ao *agentOptions) String() string {
64 return fmt.Sprintf(
65 "HTTP.Agent options: Timeout: %d - Retries: %d - FailOnHTTPError: %+v",
66 ao.Timeout, ao.Retries, ao.FailOnHTTPError,
67 )
68 }
69
70 var defaultAgentOptions = &agentOptions{
71 FailOnHTTPError: true,
72 Retries: 3,
73 Timeout: 3 * time.Second,
74 MaxWaitTime: 60 * time.Second,
75 PostContentType: defaultPostContentType,
76 }
77
78
79 func NewAgent() *Agent {
80 return &Agent{
81 AgentImplementation: &defaultAgentImplementation{},
82 options: defaultAgentOptions,
83 }
84 }
85
86
87 func (a *Agent) SetImplementation(impl AgentImplementation) {
88 a.AgentImplementation = impl
89 }
90
91
92 func (a *Agent) WithTimeout(timeout time.Duration) *Agent {
93 a.options.Timeout = timeout
94 return a
95 }
96
97
98 func (a *Agent) WithRetries(retries uint) *Agent {
99 a.options.Retries = retries
100 return a
101 }
102
103
104 func (a *Agent) WithFailOnHTTPError(flag bool) *Agent {
105 a.options.FailOnHTTPError = flag
106 return a
107 }
108
109
110 func (a *Agent) Client() *http.Client {
111 return &http.Client{
112 Timeout: a.options.Timeout,
113 }
114 }
115
116
117 func (a *Agent) Get(url string) (content []byte, err error) {
118 request, err := a.GetRequest(url)
119 if err != nil {
120 return nil, fmt.Errorf("getting GET request: %w", err)
121 }
122 defer request.Body.Close()
123
124 return a.readResponse(request)
125 }
126
127
128 func (a *Agent) GetRequest(url string) (response *http.Response, err error) {
129 logrus.Debugf("Sending GET request to %s", url)
130 try := 0
131 for {
132 response, err = a.AgentImplementation.SendGetRequest(a.Client(), url)
133 try++
134 if err == nil || try >= int(a.options.Retries) {
135 return response, err
136 }
137
138 waitTime := math.Pow(2, float64(try))
139
140 if waitTime > 60 {
141 waitTime = a.options.MaxWaitTime.Seconds()
142 }
143 logrus.Errorf(
144 "Error getting URL (will retry %d more times in %.0f secs): %s",
145 int(a.options.Retries)-try, waitTime, err.Error(),
146 )
147 time.Sleep(time.Duration(waitTime) * time.Second)
148 }
149 }
150
151
152 func (a *Agent) Post(url string, postData []byte) (content []byte, err error) {
153 response, err := a.PostRequest(url, postData)
154 if err != nil {
155 return nil, fmt.Errorf("getting post request: %w", err)
156 }
157 defer response.Body.Close()
158
159 return a.readResponse(response)
160 }
161
162
163 func (a *Agent) PostRequest(url string, postData []byte) (response *http.Response, err error) {
164 logrus.Debugf("Sending POST request to %s", url)
165 try := 0
166 for {
167 response, err = a.AgentImplementation.SendPostRequest(a.Client(), url, postData, a.options.PostContentType)
168 try++
169 if err == nil || try >= int(a.options.Retries) {
170 return response, err
171 }
172
173 waitTime := math.Pow(2, float64(try))
174
175 if waitTime > 60 {
176 waitTime = a.options.MaxWaitTime.Seconds()
177 }
178 logrus.Errorf(
179 "Error getting URL (will retry %d more times in %.0f secs): %s",
180 int(a.options.Retries)-try, waitTime, err.Error(),
181 )
182 time.Sleep(time.Duration(waitTime) * time.Second)
183 }
184 }
185
186
187 func (impl *defaultAgentImplementation) SendPostRequest(
188 client *http.Client, url string, postData []byte, contentType string,
189 ) (response *http.Response, err error) {
190 if contentType == "" {
191 contentType = defaultPostContentType
192 }
193 response, err = client.Post(url, contentType, bytes.NewBuffer(postData))
194 if err != nil {
195 return response, fmt.Errorf("posting data to %s: %w", url, err)
196 }
197 return response, nil
198 }
199
200
201 func (impl *defaultAgentImplementation) SendGetRequest(client *http.Client, url string) (
202 response *http.Response, err error,
203 ) {
204 response, err = client.Get(url)
205 if err != nil {
206 return response, fmt.Errorf("getting %s: %w", url, err)
207 }
208
209 return response, nil
210 }
211
212
213 func (a *Agent) readResponse(response *http.Response) (body []byte, err error) {
214
215 defer response.Body.Close()
216 body, err = io.ReadAll(response.Body)
217 if err != nil {
218 return nil, fmt.Errorf(
219 "reading the response body from %s: %w",
220 response.Request.URL, err,
221 )
222 }
223
224
225 if response.StatusCode < 200 || response.StatusCode >= 300 {
226 if a.options.FailOnHTTPError {
227 return nil, fmt.Errorf(
228 "HTTP error %s for %s", response.Status, response.Request.URL,
229 )
230 }
231 logrus.Warnf("Got HTTP error but FailOnHTTPError not set: %s", response.Status)
232 }
233 return body, err
234 }
235
View as plain text