-
Notifications
You must be signed in to change notification settings - Fork 261
/
base_interface.go
180 lines (153 loc) · 4.77 KB
/
base_interface.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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
package sendgrid
import (
"bytes"
"compress/gzip"
"context"
"errors"
"net/http"
"strconv"
"time"
"github.com/sendgrid/rest"
"github.com/sendgrid/sendgrid-go/helpers/mail"
)
// Version is this client library's current version
const (
Version = "3.16.0"
rateLimitRetry = 5
rateLimitSleep = 1100
)
type options struct {
Auth string
Endpoint string
Host string
Subuser string
}
// Client is the Twilio SendGrid Go client
type Client struct {
rest.Request
}
func (o *options) baseURL() string {
return o.Host + o.Endpoint
}
// requestNew create Request
// @return [Request] a default request object
func requestNew(options options) rest.Request {
requestHeaders := map[string]string{
"Authorization": options.Auth,
"User-Agent": "sendgrid/" + Version + ";go",
"Accept": "application/json",
}
if len(options.Subuser) != 0 {
requestHeaders["On-Behalf-Of"] = options.Subuser
}
return rest.Request{
BaseURL: options.baseURL(),
Headers: requestHeaders,
}
}
// Send sends an email through Twilio SendGrid
func (cl *Client) Send(email *mail.SGMailV3) (*rest.Response, error) {
return cl.SendWithContext(context.Background(), email)
}
// SendWithContext sends an email through Twilio SendGrid with context.Context.
func (cl *Client) SendWithContext(ctx context.Context, email *mail.SGMailV3) (*rest.Response, error) {
cl.Body = mail.GetRequestBody(email)
// when Content-Encoding header is set to "gzip"
// mail body is compressed using gzip according to
// https://docs.sendgrid.com/api-reference/mail-send/mail-send#mail-body-compression
if cl.Headers["Content-Encoding"] == "gzip" {
var gzipped bytes.Buffer
gz := gzip.NewWriter(&gzipped)
if _, err := gz.Write(cl.Body); err != nil {
return nil, err
}
if err := gz.Flush(); err != nil {
return nil, err
}
if err := gz.Close(); err != nil {
return nil, err
}
cl.Body = gzipped.Bytes()
}
return MakeRequestWithContext(ctx, cl.Request)
}
// DefaultClient is used if no custom HTTP client is defined
var DefaultClient = rest.DefaultClient
// API sets up the request to the Twilio SendGrid API, this is main interface.
// Please use the MakeRequest or MakeRequestAsync functions instead.
// (deprecated)
func API(request rest.Request) (*rest.Response, error) {
return MakeRequest(request)
}
// MakeRequest attempts a Twilio SendGrid request synchronously.
func MakeRequest(request rest.Request) (*rest.Response, error) {
return MakeRequestWithContext(context.Background(), request)
}
// MakeRequestWithContext attempts a Twilio SendGrid request synchronously with context.Context.
func MakeRequestWithContext(ctx context.Context, request rest.Request) (*rest.Response, error) {
return DefaultClient.SendWithContext(ctx, request)
}
// MakeRequestRetry a synchronous request, but retry in the event of a rate
// limited response.
func MakeRequestRetry(request rest.Request) (*rest.Response, error) {
return MakeRequestRetryWithContext(context.Background(), request)
}
// MakeRequestRetryWithContext a synchronous request with context.Context, but retry in the event of a rate
// limited response.
func MakeRequestRetryWithContext(ctx context.Context, request rest.Request) (*rest.Response, error) {
retry := 0
var response *rest.Response
var err error
for {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
response, err = MakeRequestWithContext(ctx, request)
if err != nil {
return nil, err
}
if response.StatusCode != http.StatusTooManyRequests {
return response, nil
}
if retry > rateLimitRetry {
return nil, errors.New("rate limit retry exceeded")
}
retry++
resetTime := time.Now().Add(rateLimitSleep * time.Millisecond)
reset, ok := response.Headers["X-RateLimit-Reset"]
if ok && len(reset) > 0 {
t, err := strconv.Atoi(reset[0])
if err == nil {
resetTime = time.Unix(int64(t), 0)
}
}
time.Sleep(resetTime.Sub(time.Now()))
}
}
// MakeRequestAsync attempts a request asynchronously in a new go
// routine. This function returns two channels: responses
// and errors. This function will retry in the case of a
// rate limit.
func MakeRequestAsync(request rest.Request) (chan *rest.Response, chan error) {
return MakeRequestAsyncWithContext(context.Background(), request)
}
// MakeRequestAsyncWithContext attempts a request asynchronously in a new go
// routine with context.Context. This function returns two channels: responses
// and errors. This function will retry in the case of a
// rate limit.
func MakeRequestAsyncWithContext(ctx context.Context, request rest.Request) (chan *rest.Response, chan error) {
r := make(chan *rest.Response)
e := make(chan error)
go func() {
response, err := MakeRequestRetryWithContext(ctx, request)
if err != nil {
e <- err
}
if response != nil {
r <- response
}
}()
return r, e
}