-
-
Notifications
You must be signed in to change notification settings - Fork 42
/
caller.go
130 lines (104 loc) · 3.22 KB
/
caller.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
129
130
//go:generate mockgen -typed -package mock -destination=mock/caller.go github.com/mymmrac/telego/telegoapi Caller
package telegoapi
import (
"errors"
"fmt"
"math"
"net/http"
"time"
"github.com/valyala/fasthttp"
"github.com/mymmrac/telego/internal/json"
)
// FastHTTPCaller fasthttp implementation of Caller
type FastHTTPCaller struct {
Client *fasthttp.Client
}
// DefaultFastHTTPCaller is a default fast http caller
var DefaultFastHTTPCaller = &FastHTTPCaller{
Client: &fasthttp.Client{},
}
// Call is a fasthttp implementation
func (a FastHTTPCaller) Call(url string, data *RequestData) (*Response, error) {
req := fasthttp.AcquireRequest()
defer fasthttp.ReleaseRequest(req)
req.SetRequestURI(url)
req.Header.SetContentType(data.ContentType)
req.Header.SetMethod(fasthttp.MethodPost)
req.SetBodyRaw(data.Buffer.Bytes())
resp := fasthttp.AcquireResponse()
defer fasthttp.ReleaseResponse(resp)
err := a.Client.Do(req, resp)
if err != nil {
return nil, fmt.Errorf("fasthttp do request: %w", err)
}
if statusCode := resp.StatusCode(); statusCode >= fasthttp.StatusInternalServerError {
return nil, fmt.Errorf("internal server error: %d", statusCode)
}
apiResp := &Response{}
err = json.Unmarshal(resp.Body(), apiResp)
if err != nil {
return nil, fmt.Errorf("decode json: %w", err)
}
return apiResp, nil
}
// HTTPCaller http implementation of Caller
type HTTPCaller struct {
Client *http.Client
}
// DefaultHTTPCaller is a default http caller
var DefaultHTTPCaller = &HTTPCaller{
Client: http.DefaultClient,
}
// Call is a http implementation
func (h HTTPCaller) Call(url string, data *RequestData) (*Response, error) {
req, err := http.NewRequest(http.MethodPost, url, data.Buffer)
if err != nil {
return nil, fmt.Errorf("http create request: %w", err)
}
req.Header.Set(ContentTypeHeader, data.ContentType)
resp, err := h.Client.Do(req)
if err != nil {
return nil, fmt.Errorf("http do request: %w", err)
}
if resp.StatusCode >= http.StatusInternalServerError {
return nil, fmt.Errorf("internal server error: %d", resp.StatusCode)
}
apiResp := &Response{}
err = json.NewDecoder(resp.Body).Decode(apiResp)
if err != nil {
return nil, fmt.Errorf("decode json: %w", err)
}
if err = resp.Body.Close(); err != nil {
return nil, fmt.Errorf("close body: %w", err)
}
return apiResp, nil
}
// RetryCaller decorator over Caller that provides reties with exponential backoff
// Delay = (ExponentBase ^ AttemptNumber) * StartDelay or MaxDelay
type RetryCaller struct {
Caller Caller
MaxAttempts int
ExponentBase float64
StartDelay time.Duration
MaxDelay time.Duration
}
// ErrMaxRetryAttempts returned when max retry attempts reached
var ErrMaxRetryAttempts = errors.New("max retry attempts reached")
// Call makes calls using provided caller with retries
func (r *RetryCaller) Call(url string, data *RequestData) (resp *Response, err error) {
for i := 0; i < r.MaxAttempts; i++ {
resp, err = r.Caller.Call(url, data)
if err == nil {
return resp, nil
}
if i == r.MaxAttempts-1 {
break
}
delay := time.Duration(math.Pow(r.ExponentBase, float64(i))) * r.StartDelay
if delay > r.MaxDelay {
delay = r.MaxDelay
}
time.Sleep(delay)
}
return nil, errors.Join(err, ErrMaxRetryAttempts)
}