Skip to content

Commit 2e724ec

Browse files
adding retry handler with example
1 parent 68536ea commit 2e724ec

File tree

5 files changed

+298
-63
lines changed

5 files changed

+298
-63
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using System;
2+
using System.Net;
3+
using SocketLabs.InjectionApi;
4+
using SocketLabs.InjectionApi.Message;
5+
6+
namespace dotNetCoreExample.Examples.Basic
7+
{
8+
public class BasicSendWithRetry : IExample
9+
{
10+
public SendResponse RunExample()
11+
{
12+
var proxy = new WebProxy("http://localhost:4433", false);
13+
14+
var client = new SocketLabsClient(ExampleConfig.ServerId, ExampleConfig.ApiKey, proxy)
15+
{
16+
EndpointUrl = ExampleConfig.TargetApi,
17+
RequestTimeout = 5,
18+
RetrySettings = new RetrySettings(3, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(3))
19+
};
20+
21+
var message = new BasicMessage();
22+
23+
message.Subject = "Sending A Test Message With Retry Enabled";
24+
message.HtmlBody = "<html>This is the Html Body of my message.</html>";
25+
message.PlainTextBody = "This is the Plain Text Body of my message.";
26+
27+
message.From.Email = "from@example.com";
28+
message.ReplyTo.Email = "replyto@example.com";
29+
message.To.Add("recipient1@example.com");
30+
31+
return client.Send(message);
32+
}
33+
}
34+
}

Example Projects/dotNetCoreExample/Program.cs

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -53,23 +53,24 @@ private static void DisplayTheMenu()
5353
Console.WriteLine(" 6: Basic Send With Custom-Headers ");
5454
Console.WriteLine(" 7: Basic Send With Embedded Image ");
5555
Console.WriteLine(" 8: Basic Send With Proxy ");
56-
Console.WriteLine(" 9: Basic Send Complex Example ");
56+
Console.WriteLine(" 9: Basic Send With Retry ");
57+
Console.WriteLine(" 10: Basic Send Complex Example ");
5758
Console.WriteLine();
5859
Console.WriteLine(" Validation Error Handling Examples: ");
59-
Console.WriteLine(" 10: Basic Send With Invalid Attachment");
60-
Console.WriteLine(" 11: Basic Send With Invalid From ");
61-
Console.WriteLine(" 12: Basic Send With Invalid Recipients ");
60+
Console.WriteLine(" 11: Basic Send With Invalid Attachment");
61+
Console.WriteLine(" 12: Basic Send With Invalid From ");
62+
Console.WriteLine(" 13: Basic Send With Invalid Recipients ");
6263
Console.WriteLine();
6364
Console.WriteLine(" Bulk Send Examples: ");
64-
Console.WriteLine(" 13: Bulk Send ");
65-
Console.WriteLine(" 14: Bulk Send With MergeData ");
66-
Console.WriteLine(" 15: Bulk Send With Ascii Charset And MergeData ");
67-
Console.WriteLine(" 16: Bulk Send From DataSource With MergeData ");
68-
Console.WriteLine(" 17: Bulk Send Complex Example (Everything including the Kitchen Sink) ");
65+
Console.WriteLine(" 14: Bulk Send ");
66+
Console.WriteLine(" 15: Bulk Send With MergeData ");
67+
Console.WriteLine(" 16: Bulk Send With Ascii Charset And MergeData ");
68+
Console.WriteLine(" 17: Bulk Send From DataSource With MergeData ");
69+
Console.WriteLine(" 18: Bulk Send Complex Example (Everything including the Kitchen Sink) ");
6970
Console.WriteLine();
7071
Console.WriteLine(" Amp Examples: ");
71-
Console.WriteLine(" 18: Basic Send With Amp Body ");
72-
Console.WriteLine(" 19: Bulk Send With Amp Body ");
72+
Console.WriteLine(" 19: Basic Send With Amp Body ");
73+
Console.WriteLine(" 20: Bulk Send With Amp Body ");
7374
Console.WriteLine();
7475
Console.WriteLine("-------------------------------------------------------------------------");
7576
}
@@ -91,18 +92,22 @@ private static string GetExampleName(string selection)
9192
case 5: return "dotNetCoreExample.Examples.Basic.BasicSendWithAttachment";
9293
case 6: return "dotNetCoreExample.Examples.Basic.BasicSendWithCustomHeaders";
9394
case 7: return "dotNetCoreExample.Examples.Basic.BasicSendWithEmbeddedImage";
94-
case 8: return "dotNetCoreExample.Examples.Basic.BasicSendWithProxy";
95-
case 9: return "dotNetCoreExample.Examples.Basic.BasicComplexExample";
96-
case 10: return "dotNetCoreExample.Examples.Basic.Invalid.BasicSendWithInvalidAttachment";
97-
case 11: return "dotNetCoreExample.Examples.Basic.Invalid.BasicSendWithInvalidFrom";
98-
case 12: return "dotNetCoreExample.Examples.Basic.Invalid.BasicSendWithInvalidRecipients";
99-
case 13: return "dotNetCoreExample.Examples.Bulk.BulkSend";
100-
case 14: return "dotNetCoreExample.Examples.Bulk.BulkSendWithMergeData";
101-
case 15: return "dotNetCoreExample.Examples.Bulk.BulkSendWithAsciiCharsetMergeData";
102-
case 16: return "dotNetCoreExample.Examples.Bulk.BulkSendFromDataSourceWithMerge";
103-
case 17: return "dotNetCoreExample.Examples.Bulk.BulkSendComplexExample";
104-
case 18: return "dotNetCoreExample.Examples.Basic.BasicSendWithAmpBody";
105-
case 19: return "dotNetCoreExample.Examples.Bulk.BulkSendWithAmpBody";
95+
case 8: return "dotNetCoreExample.Examples.Basic.BasicSendWithRetry";
96+
case 9: return "dotNetCoreExample.Examples.Basic.BasicSendWithProxy";
97+
case 10: return "dotNetCoreExample.Examples.Basic.BasicComplexExample";
98+
99+
case 11: return "dotNetCoreExample.Examples.Basic.Invalid.BasicSendWithInvalidAttachment";
100+
case 12: return "dotNetCoreExample.Examples.Basic.Invalid.BasicSendWithInvalidFrom";
101+
case 13: return "dotNetCoreExample.Examples.Basic.Invalid.BasicSendWithInvalidRecipients";
102+
103+
case 14: return "dotNetCoreExample.Examples.Bulk.BulkSend";
104+
case 15: return "dotNetCoreExample.Examples.Bulk.BulkSendWithMergeData";
105+
case 16: return "dotNetCoreExample.Examples.Bulk.BulkSendWithAsciiCharsetMergeData";
106+
case 17: return "dotNetCoreExample.Examples.Bulk.BulkSendFromDataSourceWithMerge";
107+
case 18: return "dotNetCoreExample.Examples.Bulk.BulkSendComplexExample";
108+
109+
case 19: return "dotNetCoreExample.Examples.Basic.BasicSendWithAmpBody";
110+
case 20: return "dotNetCoreExample.Examples.Bulk.BulkSendWithAmpBody";
106111

107112
default:
108113
Console.WriteLine("Invalid Input (Out of Range)");
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Net;
4+
using System.Net.Http;
5+
using System.Threading;
6+
using System.Threading.Tasks;
7+
8+
namespace SocketLabs.InjectionApi.Core
9+
{
10+
internal class RetryHandler
11+
{
12+
13+
private readonly HttpClient HttpClient;
14+
private readonly string EndpointUrl;
15+
private readonly RetrySettings RetrySettings;
16+
17+
private readonly List<HttpStatusCode> ErrorStatusCodes = new List<HttpStatusCode>()
18+
{
19+
HttpStatusCode.InternalServerError,
20+
HttpStatusCode.BadGateway,
21+
HttpStatusCode.ServiceUnavailable,
22+
HttpStatusCode.GatewayTimeout
23+
};
24+
25+
/// <summary>
26+
/// Creates a new instance of the <c>RetryHandler</c>.
27+
/// </summary>
28+
/// <param name="httpClient">A <c>HttpClient</c> instance</param>
29+
/// <param name="endpointUrl">The SocketLabs Injection API endpoint Url</param>
30+
/// <param name="settings">A <c>RetrySettings</c> instance</param>
31+
public RetryHandler(HttpClient httpClient, string endpointUrl, RetrySettings settings)
32+
{
33+
HttpClient = httpClient;
34+
EndpointUrl = endpointUrl;
35+
RetrySettings = settings;
36+
}
37+
38+
39+
public async Task<HttpResponseMessage> SendAsync(StringContent content, CancellationToken cancellationToken)
40+
{
41+
if (RetrySettings.MaximumNumberOfRetries == 0)
42+
{
43+
return await HttpClient.PostAsync(EndpointUrl, content, cancellationToken)
44+
.ConfigureAwait(false);
45+
}
46+
47+
HttpResponseMessage response = null;
48+
49+
var numberOfAttempts = 0;
50+
var sent = false;
51+
52+
while (!sent)
53+
{
54+
var waitFor = this.GetNextWaitInterval(numberOfAttempts);
55+
56+
try
57+
{
58+
response = await HttpClient.PostAsync(EndpointUrl, content, cancellationToken).ConfigureAwait(false);
59+
60+
if (ErrorStatusCodes.Contains(response.StatusCode))
61+
throw new HttpRequestException($"HttpStatusCode: '{response.StatusCode}'. Response contains server error.");
62+
63+
64+
sent = true;
65+
}
66+
catch (TaskCanceledException)
67+
{
68+
numberOfAttempts++;
69+
70+
if (numberOfAttempts > RetrySettings.MaximumNumberOfRetries)
71+
{
72+
throw new TimeoutException();
73+
}
74+
75+
// ReSharper disable once MethodSupportsCancellation, cancel will be indicated on the token
76+
await Task.Delay(waitFor).ConfigureAwait(false);
77+
}
78+
catch (HttpRequestException)
79+
{
80+
numberOfAttempts++;
81+
82+
if (numberOfAttempts > RetrySettings.MaximumNumberOfRetries)
83+
{
84+
throw;
85+
}
86+
87+
await Task.Delay(waitFor).ConfigureAwait(false);
88+
}
89+
}
90+
91+
return response;
92+
}
93+
94+
95+
96+
internal virtual int GetRetryDelta(int numberOfAttempts)
97+
{
98+
var random = new Random();
99+
100+
var min = (int) (TimeSpan.FromSeconds(1).TotalMilliseconds * 0.8);
101+
var max = (int) (TimeSpan.FromSeconds(1).TotalMilliseconds * 1.2);
102+
103+
return (int) ((Math.Pow(2.0, numberOfAttempts) - 1.0) * random.Next(min, max));
104+
}
105+
106+
private TimeSpan GetNextWaitInterval(int numberOfAttempts)
107+
{
108+
var interval = (int)Math.Min(
109+
RetrySettings.MinimumRetryTimeBetween.TotalMilliseconds + GetRetryDelta(numberOfAttempts),
110+
RetrySettings.MaximumRetryTimeBetween.TotalMilliseconds);
111+
112+
return TimeSpan.FromMilliseconds(interval);
113+
}
114+
115+
}
116+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
using System;
2+
3+
namespace SocketLabs.InjectionApi
4+
{
5+
/// <summary>
6+
///
7+
/// </summary>
8+
public class RetrySettings
9+
{
10+
11+
/// <summary>
12+
/// The maximum number of retries when sending an Injection API Request before throwing an exception. Default: 0, no retries, you must explicitly enable retry settings
13+
/// </summary>
14+
public int MaximumNumberOfRetries { get; }
15+
16+
/// <summary>
17+
/// The minimum wait time between between HTTP retries. Default: 1s
18+
/// </summary>
19+
public TimeSpan MinimumRetryTimeBetween { get; }
20+
21+
/// <summary>
22+
/// The maximum wait time between between retries. Default: 10s
23+
/// </summary>
24+
public TimeSpan MaximumRetryTimeBetween { get; }
25+
26+
private const int _defaultNumberOfRetries = 0;
27+
private const int _maximumAllowedNumberOfRetries = 5;
28+
private readonly TimeSpan _defaultMinimumRetryTime = TimeSpan.FromSeconds(1);
29+
private readonly TimeSpan _defaultMaximumRetryTime = TimeSpan.FromSeconds(10);
30+
31+
/// <summary>
32+
/// Creates a new instance of the <c>RetrySettings</c>.
33+
/// </summary>
34+
/// <param name="maximumNumberOfRetries"></param>
35+
/// <param name="minimumRetryTimeBetween"></param>
36+
/// <param name="maximumRetryTimeBetween"></param>
37+
public RetrySettings(int? maximumNumberOfRetries = null, TimeSpan? minimumRetryTimeBetween = null, TimeSpan? maximumRetryTimeBetween = null)
38+
{
39+
40+
if (maximumNumberOfRetries != null)
41+
{
42+
if (maximumNumberOfRetries < 0) throw new ArgumentOutOfRangeException(nameof(maximumNumberOfRetries), "maximumNumberOfRetries must be greater than 0");
43+
if (maximumNumberOfRetries > 5) throw new ArgumentOutOfRangeException(nameof(maximumNumberOfRetries), $"The maximum number of allowed retries is {_maximumAllowedNumberOfRetries}");
44+
45+
MaximumNumberOfRetries = maximumNumberOfRetries.Value;
46+
}
47+
else
48+
MaximumNumberOfRetries = _defaultNumberOfRetries;
49+
50+
51+
52+
if (minimumRetryTimeBetween != null)
53+
{
54+
if (minimumRetryTimeBetween.Value.Ticks < 0) throw new ArgumentOutOfRangeException(nameof(minimumRetryTimeBetween), "minimumRetryTimeBetween must be greater than 0");
55+
56+
MinimumRetryTimeBetween = minimumRetryTimeBetween.Value;
57+
}
58+
else
59+
MinimumRetryTimeBetween = _defaultMinimumRetryTime;
60+
61+
62+
if (maximumRetryTimeBetween != null)
63+
{
64+
if (maximumRetryTimeBetween.Value.Ticks < 0) throw new ArgumentOutOfRangeException(nameof(maximumRetryTimeBetween), "maximumRetryTimeBetween must be greater than 0");
65+
if (maximumRetryTimeBetween.Value.TotalSeconds > 30) throw new ArgumentOutOfRangeException(nameof(maximumRetryTimeBetween), "maximumRetryTimeBetween must be less than 30 seconds");
66+
67+
MaximumRetryTimeBetween = maximumRetryTimeBetween.Value;
68+
}
69+
else
70+
MaximumRetryTimeBetween = _defaultMaximumRetryTime;
71+
72+
73+
if (minimumRetryTimeBetween != null && maximumRetryTimeBetween != null)
74+
{
75+
if (minimumRetryTimeBetween.Value.TotalMilliseconds > maximumRetryTimeBetween.Value.TotalMilliseconds)
76+
throw new ArgumentOutOfRangeException(nameof(minimumRetryTimeBetween),
77+
"minimumRetryTimeBetween must be less than maximumRetryTimeBetween");
78+
}
79+
80+
81+
}
82+
}
83+
}

0 commit comments

Comments
 (0)