Skip to content

Commit e96e5df

Browse files
Merge pull request #10 from socketlabs/feature/retry
adding retry handler with example
2 parents 68536ea + 542b4f0 commit e96e5df

File tree

9 files changed

+254
-66
lines changed

9 files changed

+254
-66
lines changed

Example Projects/dotNetCoreExample/Examples/Basic/BasicSendWithAttachment.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public SendResponse RunExample()
2222
message.ReplyTo.Email = "[email protected]";
2323
message.To.Add("[email protected]");
2424

25-
var attachment = message.Attachments.AddAsync("bus.png", MimeType.PNG, @".\examples\img\bus.png").Result;
25+
var attachment = message.Attachments.Add("bus.png", MimeType.PNG, @".\examples\img\bus.png");
2626

2727
attachment.CustomHeaders.Add(new CustomHeader("Color", "Orange"));
2828
attachment.CustomHeaders.Add(new CustomHeader("Place", "Beach"));
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 = 120,
18+
NumberOfRetries = 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 = "[email protected]";
28+
message.ReplyTo.Email = "[email protected]";
29+
message.To.Add("[email protected]");
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)");

README.MD

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,9 @@ This example demonstrates how to add custom headers to your email message.
173173
### [Basic send with a web proxy](https://github.com/socketlabs/socketlabs-csharp/blob/master/Example%20Projects/dotNetCoreExample/Examples/Basic/BasicSendWithProxy.cs)
174174
This example demonstrates how to use a proxy with your HTTP client.
175175

176+
### [Basic send with retry enabled](https://github.com/socketlabs/socketlabs-csharp/blob/master/Example%20Projects/dotNetCoreExample/Examples/Basic/BasicSendWithRetry.cs)
177+
This example demonstrates how to use the retry logic with your HTTP client.
178+
176179
### [Basic send complex example](https://github.com/socketlabs/socketlabs-csharp/blob/master/Example%20Projects/dotNetCoreExample/Examples/Basic/BasicComplexExample.cs)
177180
This example demonstrates many features of the Basic Send, including adding multiple recipients, adding message and mailing id's, and adding an embedded image.
178181

@@ -205,6 +208,7 @@ For more information about AMP please see [AMP Project](https://amp.dev/document
205208

206209
<a name="version"></a>
207210
# Version
211+
* 1.2.2 - Adding optional retry logic for Http requests. If configured, the request will retry when certain 500 errors occur (500, 502, 503, 504)
208212
* 1.2.1 - Adding request timeout value on the client for Http requests
209213
* 1.2.0 - Can now pass in instance of HTTP client, adds .NET 5.0 support
210214
* 1.1.0 - Adds Amp Html Support

manifest/socketlabs.nuspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">
33
<metadata>
44
<id>SocketLabs.EmailDelivery</id>
5-
<version>1.2.1</version>
5+
<version>1.2.2</version>
66
<title>SocketLabs.EmailDelivery</title>
77
<authors>Joe Cooper, David Schrenker, Matt Reibach, Ryan Lydzinski (Contributor), Mike Goodfellow (Contributor), Saranya Kavuri (Contributor)</authors>
88
<license type="file">license.txt</license>
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
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+
// ReSharper disable MethodSupportsCancellation
9+
namespace SocketLabs.InjectionApi.Core
10+
{
11+
internal class RetryHandler
12+
{
13+
14+
private readonly HttpClient HttpClient;
15+
private readonly string EndpointUrl;
16+
private readonly RetrySettings RetrySettings;
17+
18+
private readonly List<HttpStatusCode> ErrorStatusCodes = new List<HttpStatusCode>()
19+
{
20+
HttpStatusCode.InternalServerError,
21+
HttpStatusCode.BadGateway,
22+
HttpStatusCode.ServiceUnavailable,
23+
HttpStatusCode.GatewayTimeout
24+
};
25+
26+
/// <summary>
27+
/// Creates a new instance of the <c>RetryHandler</c>.
28+
/// </summary>
29+
/// <param name="httpClient">A <c>HttpClient</c> instance</param>
30+
/// <param name="endpointUrl">The SocketLabs Injection API endpoint Url</param>
31+
/// <param name="settings">A <c>RetrySettings</c> instance</param>
32+
public RetryHandler(HttpClient httpClient, string endpointUrl, RetrySettings settings)
33+
{
34+
HttpClient = httpClient;
35+
EndpointUrl = endpointUrl;
36+
RetrySettings = settings;
37+
}
38+
39+
40+
public async Task<HttpResponseMessage> SendAsync(StringContent content, CancellationToken cancellationToken)
41+
{
42+
if (RetrySettings.MaximumNumberOfRetries == 0)
43+
return await HttpClient
44+
.PostAsync(EndpointUrl, content, cancellationToken)
45+
.ConfigureAwait(false);
46+
47+
var attempts = 0;
48+
do
49+
{
50+
var waitInterval = RetrySettings.GetNextWaitInterval(attempts);
51+
52+
try
53+
{
54+
var response = await HttpClient.PostAsync(EndpointUrl, content, cancellationToken)
55+
.ConfigureAwait(false);
56+
57+
if (ErrorStatusCodes.Contains(response.StatusCode))
58+
throw new HttpRequestException(
59+
$"HttpStatusCode: '{response.StatusCode}'. Response contains server error.");
60+
61+
return response;
62+
}
63+
catch (TaskCanceledException)
64+
{
65+
attempts++;
66+
if (attempts > RetrySettings.MaximumNumberOfRetries) throw new TimeoutException();
67+
await Task.Delay(waitInterval).ConfigureAwait(false);
68+
}
69+
catch (HttpRequestException)
70+
{
71+
attempts++;
72+
if (attempts > RetrySettings.MaximumNumberOfRetries) throw;
73+
await Task.Delay(waitInterval).ConfigureAwait(false);
74+
}
75+
76+
} while (true);
77+
78+
}
79+
80+
81+
82+
}
83+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
using System;
2+
3+
namespace SocketLabs.InjectionApi
4+
{
5+
/// <summary>
6+
///
7+
/// </summary>
8+
internal class RetrySettings
9+
{
10+
11+
private const int _defaultNumberOfRetries = 0;
12+
private const int _maximumAllowedNumberOfRetries = 5;
13+
private readonly TimeSpan _minimumRetryTime = TimeSpan.FromSeconds(1);
14+
private readonly TimeSpan _maximumRetryTime = TimeSpan.FromSeconds(10);
15+
16+
/// <summary>
17+
/// Creates a new instance of the <c>RetrySettings</c>.
18+
/// </summary>
19+
/// <param name="maximumNumberOfRetries"></param>
20+
public RetrySettings(int? maximumNumberOfRetries = null)
21+
{
22+
23+
if (maximumNumberOfRetries != null)
24+
{
25+
if (maximumNumberOfRetries < 0) throw new ArgumentOutOfRangeException(nameof(maximumNumberOfRetries), "maximumNumberOfRetries must be greater than 0");
26+
if (maximumNumberOfRetries > 5) throw new ArgumentOutOfRangeException(nameof(maximumNumberOfRetries), $"The maximum number of allowed retries is {_maximumAllowedNumberOfRetries}");
27+
28+
MaximumNumberOfRetries = maximumNumberOfRetries.Value;
29+
}
30+
else
31+
MaximumNumberOfRetries = _defaultNumberOfRetries;
32+
33+
}
34+
35+
36+
/// <summary>
37+
/// 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
38+
/// </summary>
39+
public int MaximumNumberOfRetries { get; }
40+
41+
42+
/// <summary>
43+
/// Get the time period to wait before next call
44+
/// </summary>
45+
/// <param name="numberOfAttempts"></param>
46+
/// <returns></returns>
47+
public TimeSpan GetNextWaitInterval(int numberOfAttempts)
48+
{
49+
var interval = (int)Math.Min(
50+
_minimumRetryTime.TotalMilliseconds + GetRetryDelta(numberOfAttempts),
51+
_maximumRetryTime.TotalMilliseconds);
52+
53+
return TimeSpan.FromMilliseconds(interval);
54+
}
55+
internal virtual int GetRetryDelta(int numberOfAttempts)
56+
{
57+
var random = new Random();
58+
59+
var min = (int)(TimeSpan.FromSeconds(1).TotalMilliseconds * 0.8);
60+
var max = (int)(TimeSpan.FromSeconds(1).TotalMilliseconds * 1.2);
61+
62+
return (int)((Math.Pow(2.0, numberOfAttempts) - 1.0) * random.Next(min, max));
63+
}
64+
}
65+
}

0 commit comments

Comments
 (0)