Skip to content

Commit cd314ad

Browse files
Merge pull request #4 from PraneethChandraThota/retry-logic
LU-49 Python Retry Logic
2 parents f4f2b3c + af5f6ea commit cd314ad

File tree

5 files changed

+266
-26
lines changed

5 files changed

+266
-26
lines changed

Diff for: python-examples/basic/basic_send_with_retry.py

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import json
2+
import os
3+
4+
from socketlabs.injectionapi import SocketLabsClient
5+
from socketlabs.injectionapi.proxy import Proxy
6+
from socketlabs.injectionapi.message.__imports__ import BasicMessage, EmailAddress
7+
8+
def on_success(response):
9+
"""
10+
Handle the success response from the client
11+
:param response: the SendResponse
12+
:return: SendResponse
13+
"""
14+
print(json.dumps(response.to_json(), indent=2))
15+
16+
17+
def on_error(exception):
18+
"""
19+
Handle the error response from the client
20+
:param exception: the Exception
21+
:return: Exception
22+
"""
23+
print(exception)
24+
25+
# build the message
26+
message = BasicMessage()
27+
28+
message.subject = "Sending An Email Through A Proxy"
29+
message.html_body = "<html><body>" \
30+
"<h1>Sending An Email Through A Proxy</h1>" \
31+
"<p>This is the Html Body of my message.</p>" \
32+
"</body></html>"
33+
message.plain_text_body = "This is the Plain Text Body of my message."
34+
35+
message.from_email_address = EmailAddress("[email protected]")
36+
message.add_to_email_address("[email protected]")
37+
38+
39+
# get credentials from environment variables
40+
server_id = int(os.environ.get('SOCKETLABS_SERVER_ID'))
41+
api_key = os.environ.get('SOCKETLABS_INJECTION_API_KEY')
42+
43+
# create the proxy
44+
proxy = Proxy("127.0.0.1", 4433)
45+
46+
# create the client
47+
client = SocketLabsClient(server_id, api_key, proxy)
48+
49+
client.number_of_retries = 2
50+
51+
# send the message
52+
response = client.send(message)
53+
client.send_async(message, on_success, on_error)
54+
55+
print(json.dumps(response.to_json(), indent=2))

Diff for: socketlabs/injectionapi/core/httprequest.py

+15-19
Original file line numberDiff line numberDiff line change
@@ -106,26 +106,21 @@ def send_async_request(self, request: InjectionRequest, on_success_callback, on_
106106
:param on_error_callback: the callback method for error
107107
:type on_error_callback: method
108108
"""
109-
req_queue = queue.Queue()
109+
110110
try:
111111

112112
th = threading.Thread(target=self.__queue_request,
113113
kwargs={
114114
"request": request,
115-
"out_queue": req_queue
115+
"on_success_callback": on_success_callback,
116+
"on_error_callback": on_error_callback
116117
})
117118
th.start()
118-
th.join()
119-
120-
while not th.is_alive():
121-
response = req_queue.get()
122-
on_success_callback(response)
123-
break
124-
119+
125120
except Exception as e:
126121
on_error_callback(e)
127122

128-
def __queue_request(self, request: InjectionRequest, out_queue):
123+
def __queue_request(self, request: InjectionRequest, on_success_callback, on_error_callback):
129124
"""
130125
queue method for the threaded send request.
131126
:param request: the injection request to send
@@ -135,9 +130,10 @@ def __queue_request(self, request: InjectionRequest, out_queue):
135130
"""
136131
try:
137132
response = self.send_request(request)
138-
out_queue.put(response)
133+
on_success_callback(response)
134+
139135
except Exception:
140-
raise
136+
on_error_callback(sys.exc_info()[0])
141137

142138
def send_request(self, request: InjectionRequest):
143139
"""
@@ -155,15 +151,15 @@ def send_request(self, request: InjectionRequest):
155151

156152
json_body = json.dumps(request.to_json())
157153

158-
connection = self.__get_connection()
159-
connection.request("POST", self._endpoint.url, json_body, headers)
160-
response = connection.getresponse()
154+
try:
155+
connection = self.__get_connection()
156+
connection.request("POST", self._endpoint.url, json_body, headers)
157+
response = connection.getresponse()
161158

162-
data = response.read().decode("utf-8")
163-
response_code = response.status
159+
except Exception as e:
160+
raise e
164161

165-
result = InjectionResponseParser.parse(data, response_code)
166-
return result
162+
return response
167163

168164
def __get_connection(self):
169165
"""

Diff for: socketlabs/injectionapi/core/retryhandler.py

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
from http import HTTPStatus
2+
from http.client import HTTPException
3+
from ..retrysettings import RetrySettings
4+
from .httprequest import HttpRequest
5+
from .serialization.injectionrequest import InjectionRequest
6+
import socket
7+
import time
8+
9+
10+
class RetryHandler(object):
11+
attempts = 0
12+
ErrorStatusCodes = [
13+
HTTPStatus.INTERNAL_SERVER_ERROR,
14+
HTTPStatus.BAD_GATEWAY,
15+
HTTPStatus.SERVICE_UNAVAILABLE,
16+
HTTPStatus.GATEWAY_TIMEOUT
17+
]
18+
Exceptions = [
19+
socket.timeout,
20+
HTTPException
21+
]
22+
23+
def __init__(self, http_client: HttpRequest, settings: RetrySettings):
24+
25+
self.__http_client = http_client
26+
self.__retry_settings = settings
27+
28+
def send(self, body):
29+
30+
if self.__retry_settings.maximum_number_of_retries == 0:
31+
return self.__http_client.send_request(body)
32+
33+
while True:
34+
wait_interval = self.__retry_settings.get_next_wait_interval(self.attempts)
35+
36+
try:
37+
38+
response = self.__http_client.send_request(body)
39+
40+
if response.status in self.ErrorStatusCodes:
41+
raise HTTPException("HttpStatusCode: {0}. Response contains server error.".format(response.status))
42+
43+
return response
44+
45+
except socket.timeout:
46+
47+
self.attempts += 1
48+
49+
if self.attempts > self.__retry_settings.maximum_number_of_retries:
50+
raise socket.timeout
51+
time.sleep(wait_interval.seconds)
52+
53+
except HTTPException:
54+
55+
self.attempts += 1
56+
57+
if self.attempts > self.__retry_settings.maximum_number_of_retries:
58+
raise HTTPException
59+
time.sleep(wait_interval.seconds)
60+
61+
def send_async(self, request: InjectionRequest, on_success_callback, on_error_callback):
62+
63+
wait_interval = self.__retry_settings.get_next_wait_interval(self.attempts)
64+
65+
def on_success(response):
66+
67+
if response.status in self.ErrorStatusCodes and self.attempts < self.__retry_settings.maximum_number_of_retries:
68+
69+
self.attempts += 1
70+
time.sleep(wait_interval.seconds)
71+
self.send_async(request, on_success_callback, on_error_callback)
72+
73+
else:
74+
on_success_callback(response)
75+
76+
def on_error(exception):
77+
78+
if exception in self.Exceptions and self.attempts < self.__retry_settings.maximum_number_of_retries:
79+
80+
self.attempts += 1
81+
time.sleep(wait_interval.seconds)
82+
self.send_async(request, on_success_callback, on_error_callback)
83+
84+
else:
85+
86+
self.attempts = self.__retry_settings.maximum_number_of_retries + 1
87+
on_error_callback(exception)
88+
89+
self.__http_client.send_async_request(request, on_success, on_error)

Diff for: socketlabs/injectionapi/retrysettings.py

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
from datetime import timedelta
2+
import random
3+
4+
5+
class RetrySettings(object):
6+
__default_number_of_retries = 0
7+
__maximum_allowed_number_of_retries = 5
8+
__minimum_retry_time = timedelta(seconds=1)
9+
__maximum_retry_time = timedelta(seconds=10)
10+
11+
def __init__(self, maximum_retries=None):
12+
13+
if maximum_retries:
14+
15+
if maximum_retries < 0:
16+
raise AttributeError("maximumNumberOfRetries must be greater than 0")
17+
18+
if maximum_retries > self.__maximum_allowed_number_of_retries:
19+
raise AttributeError("The maximum number of allowed retries is ",
20+
self.__maximum_allowed_number_of_retries)
21+
22+
self.__maximum_number_of_retries = maximum_retries
23+
24+
else:
25+
self.__maximum_number_of_retries = self.__default_number_of_retries
26+
27+
@property
28+
def maximum_number_of_retries(self):
29+
return self.__maximum_number_of_retries
30+
31+
def get_next_wait_interval(self, number_of_attempts):
32+
33+
interval = int(min(
34+
((self.__minimum_retry_time.seconds * 1000) + self.get_retry_delta(number_of_attempts)),
35+
(self.__maximum_retry_time.seconds * 1000)
36+
))
37+
38+
return timedelta(milliseconds=interval)
39+
40+
@staticmethod
41+
def get_retry_delta(number_of_attempts):
42+
43+
minimum = int((timedelta(seconds=1).seconds * 1000) * 0.8)
44+
maximum = int((timedelta(seconds=1).seconds * 1000) * 1.2)
45+
46+
return int((pow(2.0, number_of_attempts) - 1.0) * random.randint(minimum, maximum))

Diff for: socketlabs/injectionapi/socketlabsclient.py

+61-7
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
from .core.httpendpoint import HttpEndpoint
66
from .core.httprequest import HttpRequest
77
from .core.injectionrequestfactory import InjectionRequestFactory
8+
from .core.injectionresponseparser import InjectionResponseParser
89
from .core.sendvalidator import SendValidator
10+
from .core.retryhandler import RetryHandler
11+
from .retrysettings import RetrySettings
912
from .message.basicmessage import BasicMessage
1013
from .message.bulkmessage import BulkMessage
1114
from .proxy import Proxy
@@ -32,6 +35,7 @@ def __init__(self, server_id: int, api_key: str, proxy: Proxy = None):
3235
self._api_key = api_key
3336
self._http_proxy = proxy
3437
self._request_timeout = 120
38+
self._number_of_retries = 0
3539

3640
@property
3741
def __endpoint(self):
@@ -69,6 +73,14 @@ def request_timeout(self, timeout: int):
6973
"""
7074
self._request_timeout = timeout
7175

76+
@property
77+
def number_of_retries(self):
78+
return self._number_of_retries
79+
80+
@number_of_retries.setter
81+
def number_of_retries(self, retries: int):
82+
self._number_of_retries = retries
83+
7284
def __build_http_request(self):
7385
"""
7486
Build the HttpRequest. Will add the proxy, if set
@@ -110,8 +122,13 @@ def __send_basic_message(self, message: BasicMessage):
110122
req_factory = InjectionRequestFactory(self._server_id, self._api_key)
111123
body = req_factory.generate_request(message)
112124

113-
request = self.__build_http_request()
114-
result = request.send_request(body)
125+
retry_handler = RetryHandler(self.__build_http_request(), RetrySettings(self.number_of_retries))
126+
response = retry_handler.send(body)
127+
128+
data = response.read().decode("utf-8")
129+
response_code = response.status
130+
result = InjectionResponseParser.parse(data, response_code)
131+
115132
return result
116133

117134
def __send_bulk_message(self, message: BulkMessage):
@@ -129,8 +146,13 @@ def __send_bulk_message(self, message: BulkMessage):
129146
req_factory = InjectionRequestFactory(self._server_id, self._api_key)
130147
body = req_factory.generate_request(message)
131148

132-
request = self.__build_http_request()
133-
result = request.send_request(body)
149+
retry_handler = RetryHandler(self.__build_http_request(), RetrySettings(self.number_of_retries))
150+
response = retry_handler.send(body)
151+
152+
data = response.read().decode("utf-8")
153+
response_code = response.status
154+
result = InjectionResponseParser.parse(data, response_code)
155+
134156
return result
135157

136158
def send_async(self, message: BasicMessage, on_success, on_error):
@@ -167,8 +189,21 @@ def __send_basic_message_async(self, message: BasicMessage, on_success, on_error
167189
req_factory = InjectionRequestFactory(self._server_id, self._api_key)
168190
body = req_factory.generate_request(message)
169191

170-
request = self.__build_http_request()
171-
request.send_async_request(body, on_success, on_error)
192+
retry_handler = RetryHandler(self.__build_http_request(), RetrySettings(self.number_of_retries))
193+
194+
def on_success_callback(response):
195+
response = retry_handler.send(body)
196+
197+
data = response.read().decode("utf-8")
198+
response_code = response.status
199+
result = InjectionResponseParser.parse(data, response_code)
200+
201+
on_success(result)
202+
203+
def on_error_callback(exception):
204+
on_error(exception)
205+
206+
retry_handler.send_async(body, on_success_callback, on_error_callback)
172207

173208
def __send_bulk_message_async(self, message: BulkMessage, on_success, on_error):
174209
"""
@@ -187,8 +222,27 @@ def __send_bulk_message_async(self, message: BulkMessage, on_success, on_error):
187222
req_factory = InjectionRequestFactory(self._server_id, self._api_key)
188223
body = req_factory.generate_request(message)
189224

225+
retry_handler = RetryHandler(self.__build_http_request(), RetrySettings(self.number_of_retries))
226+
227+
def on_success_callback(response):
228+
response = retry_handler.send(body)
229+
230+
data = response.read().decode("utf-8")
231+
response_code = response.status
232+
result = InjectionResponseParser.parse(data, response_code)
233+
234+
on_success(result)
235+
236+
def on_error_callback(exception):
237+
on_error(exception)
238+
239+
retry_handler.send_async(body, on_success_callback, on_error_callback)
240+
241+
"""
190242
request = self.__build_http_request()
191-
request.send_async_request(body, on_success, on_error)
243+
retry_handler = RetryHandler(request, RetrySettings(self.number_of_retries))
244+
retry_handler.send_async(body, on_success, on_error)
245+
"""
192246

193247
def __validate_basic_message(self, message: BasicMessage):
194248
"""

0 commit comments

Comments
 (0)