Skip to content

Commit 5159532

Browse files
Merge pull request #4 from PraneethChandraThota/retry-logic
LU-51 Go Retry Logic
2 parents 982c938 + 9ed867d commit 5159532

File tree

4 files changed

+241
-13
lines changed

4 files changed

+241
-13
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"github.com/socketlabs/socketlabs-go/examples"
6+
"github.com/socketlabs/socketlabs-go/injectionapi"
7+
"github.com/socketlabs/socketlabs-go/injectionapi/message"
8+
)
9+
10+
func main() {
11+
12+
//Create a client with proxy
13+
client := injectionapi.CreateClientWithProxy(exampleconfig.ServerId(), exampleconfig.APIKey(), exampleconfig.ProxyURL())
14+
client.SetRequestTimeout(10)
15+
client.SetNumberOfRetries(2)
16+
17+
18+
//Or Create the client and then set the proxy on the client
19+
// client := injectionapi.CreateClient(exampleconfig.ServerId(), exampleconfig.APIKey())
20+
// client.SetProxyURL(exampleconfig.ProxyURL())
21+
22+
basic := message.BasicMessage{}
23+
24+
basic.Subject = "Sending a Basic Message"
25+
basic.HtmlBody = "<html>This is the Html Body of my message.</html>"
26+
basic.PlainTextBody = "This is the Plain Text Body of my message."
27+
28+
basic.From = message.EmailAddress{EmailAddress: "from@example.com"}
29+
basic.ReplyTo = message.EmailAddress{EmailAddress: "replyto@example.com"}
30+
31+
//A basic message supports up to 50 recipients and supports several different ways to add recipients
32+
basic.AddToEmailAddress("recipient@example.com") //Add a To address by passing the email address
33+
basic.AddCcEmailAddress("recipient2@example.com") //Add a CC address by passing the email address and a friendly name
34+
basic.AddBccEmailAddress("recipient3@example.com") //Add a BCC address by passing the email address
35+
36+
//Send the message
37+
sendResponse, err := client.SendBasic(&basic)
38+
39+
//Output the results
40+
fmt.Println(sendResponse.Result.ToString())
41+
fmt.Println(sendResponse.ResponseMessage)
42+
43+
if err != nil {
44+
fmt.Println(err)
45+
}
46+
}

injectionapi/retrySettings.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package injectionapi
2+
3+
import (
4+
"fmt"
5+
"math"
6+
"math/rand"
7+
"time"
8+
)
9+
10+
const nano = 1000000000
11+
12+
const defaultNumberOfRetries = 0
13+
const maximumAllowedNumberOfRetries = 5
14+
const minimumRetryTime = time.Duration(1 * nano)
15+
const maximumRetryTime = time.Duration(10 * nano)
16+
17+
type RetrySettings interface {
18+
GetMaximumNumberOfRetries() (int)
19+
GetNextWaitInterval(numberOfAttempts int) (time.Duration)
20+
GetRetryDelta(numberOfAttempts int) (int)
21+
}
22+
23+
type retrySettings struct {
24+
MaximumNumberOfRetries int
25+
}
26+
27+
func CreateRetrySettings(maximumNumberOfRetries int) RetrySettings{
28+
if maximumNumberOfRetries < 0 {
29+
panic("maximumNumberOfRetries must be greater than 0")
30+
}
31+
32+
if maximumNumberOfRetries > maximumAllowedNumberOfRetries {
33+
panic(fmt.Sprint("The maximum number of allowed retries is ", maximumAllowedNumberOfRetries))
34+
}
35+
36+
return &retrySettings{
37+
MaximumNumberOfRetries: maximumNumberOfRetries,
38+
}
39+
}
40+
41+
func (retrySettings *retrySettings) GetMaximumNumberOfRetries() int {
42+
return retrySettings.MaximumNumberOfRetries
43+
}
44+
45+
func (retrySettings *retrySettings) GetNextWaitInterval(numberOfAttempts int) time.Duration {
46+
var interval = int(math.Min(
47+
float64(minimumRetryTime.Milliseconds() + int64(retrySettings.GetRetryDelta(numberOfAttempts))),
48+
float64(maximumRetryTime.Milliseconds())))
49+
return time.Duration(interval)
50+
}
51+
52+
53+
func (retrySettings *retrySettings) GetRetryDelta(numberOfAttempts int) int {
54+
55+
min := int(float64(time.Duration(1 * nano).Milliseconds()) * 0.8)
56+
max := int(float64(time.Duration(1 * nano).Milliseconds()) * 1.2)
57+
58+
return int((math.Pow(2.0, float64(numberOfAttempts)) - 1.0) * float64(rand.Intn(max - min) + min))
59+
}

injectionapi/retryhandler.go

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package injectionapi
2+
3+
import (
4+
"bytes"
5+
"errors"
6+
"fmt"
7+
"net"
8+
"net/http"
9+
"runtime"
10+
"time"
11+
)
12+
13+
var ErrorCodes = []int{500, 502, 503, 504}
14+
15+
type RetryHandler interface {
16+
Send(serializedRequest []byte) (*http.Response, error)
17+
}
18+
19+
type retryHandler struct {
20+
HttpClient *http.Client
21+
EndpointUrl string
22+
Settings RetrySettings
23+
}
24+
25+
func CreateRetryHandler(client *http.Client, endpointUrl string, settings RetrySettings) RetryHandler {
26+
return &retryHandler{
27+
HttpClient: client,
28+
EndpointUrl: endpointUrl,
29+
Settings: settings,
30+
}
31+
}
32+
33+
func (retryHandler *retryHandler) Send(serializedRequest []byte) (*http.Response, error) {
34+
35+
if retryHandler.Settings.GetMaximumNumberOfRetries() == 0 {
36+
request, err := createRequest(retryHandler.EndpointUrl, serializedRequest)
37+
if err != nil {
38+
return nil, err
39+
}
40+
response, err := retryHandler.HttpClient.Do(request)
41+
return response, err
42+
}
43+
attempts := 0
44+
for true {
45+
46+
request, err := createRequest(retryHandler.EndpointUrl, serializedRequest)
47+
if err != nil {
48+
return nil, err
49+
}
50+
51+
waitInterval := retryHandler.Settings.GetNextWaitInterval(attempts)
52+
53+
response, err := retryHandler.HttpClient.Do(request)
54+
55+
if err == nil {
56+
57+
if elementInArray(ErrorCodes, response.StatusCode) {
58+
59+
attempts++
60+
61+
if attempts > retryHandler.Settings.GetMaximumNumberOfRetries() {
62+
return response, errors.New(fmt.Sprintf("HttpStatusCode: %d. Response Contains Error", response.StatusCode))
63+
}
64+
65+
time.Sleep(waitInterval)
66+
67+
} else {
68+
return response, err
69+
}
70+
} else {
71+
if e, ok := err.(net.Error); ok && e.Timeout() {
72+
73+
attempts++
74+
75+
if attempts > retryHandler.Settings.GetMaximumNumberOfRetries() {
76+
return response, e
77+
}
78+
79+
time.Sleep(waitInterval)
80+
81+
} else {
82+
return response, err
83+
}
84+
}
85+
86+
}
87+
88+
return nil, nil
89+
}
90+
91+
func elementInArray(array []int, element int) bool {
92+
for i := 0; i < len(array); i++ {
93+
if array[i] == element {
94+
return true
95+
}
96+
}
97+
return false
98+
}
99+
100+
func createRequest(endpointUrl string, serializedRequest []byte) (*http.Request, error) {
101+
102+
//create request
103+
request, err := http.NewRequest("POST", endpointUrl, bytes.NewBuffer(serializedRequest))
104+
if err != nil {
105+
return nil, err
106+
}
107+
108+
//add headers
109+
request.Header.Set("Content-Type", "application/json")
110+
request.Header.Set("User-Agent", "socketlabs-go/1.0.1 ("+runtime.Version()+")")
111+
112+
return request, nil
113+
114+
}

injectionapi/socketlabsclient.go

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
package injectionapi
22

33
import (
4-
"bytes"
54
"encoding/json"
65
"net/http"
76
"net/url"
8-
"runtime"
97
"time"
108

119
"github.com/socketlabs/socketlabs-go/injectionapi/core"
@@ -15,6 +13,7 @@ import (
1513

1614
const endpointURL = "https://inject.socketlabs.com/api/v1/email"
1715
const requestTimeout = 120
16+
const numberOfRetries = 0
1817

1918
// ISocketlabsClient is used to easily send messages through the Socketlabs Injection API
2019
type ISocketlabsClient interface {
@@ -34,11 +33,17 @@ type ISocketlabsClient interface {
3433
// SetRequestTimeout sets the timeout.
3534
SetRequestTimeout(timeout int)
3635

36+
// SetNumberOfRetries sets the retries
37+
SetNumberOfRetries(retries int)
38+
3739
// GetEndpointURL retreives the API endpoint.
3840
GetEndpointURL() string
3941

4042
// GetRequestTimeout retreives the timeout.
4143
GetRequestTimeout() int
44+
45+
// GetNumberOfRetries retreives the retries
46+
GetNumberOfRetries() int
4247
}
4348

4449
// socketlabsClient is the default ISocketlabsClient implementation
@@ -48,6 +53,7 @@ type socketlabsClient struct {
4853
EndpointURL string
4954
ProxyURL string
5055
RequestTimeout int
56+
NumberOfRetries int
5157
}
5258

5359
// CreateClient instatiates new client using the specified credentials
@@ -57,6 +63,7 @@ func CreateClient(serverID int, apiKey string) ISocketlabsClient {
5763
APIKey: apiKey,
5864
EndpointURL: endpointURL,
5965
RequestTimeout: requestTimeout,
66+
NumberOfRetries: numberOfRetries,
6067
}
6168
}
6269

@@ -68,6 +75,7 @@ func CreateClientWithProxy(serverID int, apiKey string, proxyURL string) ISocket
6875
EndpointURL: endpointURL,
6976
ProxyURL: proxyURL,
7077
RequestTimeout: requestTimeout,
78+
NumberOfRetries: numberOfRetries,
7179
}
7280
}
7381

@@ -86,6 +94,11 @@ func (socketlabsClient *socketlabsClient) SetRequestTimeout(timeout int) {
8694
socketlabsClient.RequestTimeout = timeout
8795
}
8896

97+
// SetNumberOfRetries sets the retries
98+
func (socketlabsClient *socketlabsClient) SetNumberOfRetries(retries int) {
99+
socketlabsClient.NumberOfRetries = retries
100+
}
101+
89102
// GetEndpointURL retreives the API endpoint.
90103
func (socketlabsClient *socketlabsClient) GetEndpointURL() string {
91104
return socketlabsClient.EndpointURL
@@ -96,6 +109,11 @@ func (socketlabsClient *socketlabsClient) GetRequestTimeout() int {
96109
return socketlabsClient.RequestTimeout
97110
}
98111

112+
// GetNumberOfRetries retreives the retries
113+
func (socketlabsClient *socketlabsClient) GetNumberOfRetries() int {
114+
return socketlabsClient.NumberOfRetries
115+
}
116+
99117
// SendBasic sends a basic email message and returns the response from the Injection API.
100118
func (socketlabsClient *socketlabsClient) SendBasic(message *message.BasicMessage) (SendResponse, error) {
101119

@@ -166,24 +184,15 @@ func (socketlabsClient socketlabsClient) sendInjectionRequest(injectionRequest *
166184
return SendResponse{}, err
167185
}
168186

169-
//create request
170-
req, err := http.NewRequest("POST", socketlabsClient.GetEndpointURL(), bytes.NewBuffer(serializedRequest))
171-
if err != nil {
172-
return SendResponse{}, err
173-
}
174-
175-
//add headers
176-
req.Header.Set("Content-Type", "application/json")
177-
req.Header.Set("User-Agent", "socketlabs-go/1.0.1 ("+runtime.Version()+")")
178-
179187
//create http client
180188
client, err := socketlabsClient.createHttpClient(socketlabsClient.ProxyURL)
181189
if err != nil {
182190
return SendResponse{}, err
183191
}
184192

185193
//issue http request
186-
resp, err := client.Do(req)
194+
retryHandler := CreateRetryHandler(client, socketlabsClient.EndpointURL, CreateRetrySettings(socketlabsClient.NumberOfRetries))
195+
resp, err := retryHandler.Send(serializedRequest)
187196
if err != nil {
188197
return SendResponse{}, err
189198
}

0 commit comments

Comments
 (0)