Skip to content

Commit

Permalink
Merge pull request #2 from Past9/master
Browse files Browse the repository at this point in the history
Implement automatic retry into client
  • Loading branch information
rogerjin12 committed Mar 30, 2017
2 parents ba5f3e3 + 833b47f commit 79292ff
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 4 deletions.
3 changes: 3 additions & 0 deletions ButterCMS/ButterCMS.csproj
Expand Up @@ -60,6 +60,9 @@
<Compile Include="Models\PostsMeta.cs" />
<Compile Include="Models\PostsResponse.cs" />
<Compile Include="Models\PostStatusEnum.cs" />
<Compile Include="Models\Tag.cs" />
<Compile Include="Models\TagResponse.cs" />
<Compile Include="Models\TagsResponse.cs" />
<Compile Include="Models\XmlAPIResponse.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SnakeCaseContractResolver.cs" />
Expand Down
106 changes: 102 additions & 4 deletions ButterCMS/ButterCMSClient.cs
Expand Up @@ -12,9 +12,12 @@ namespace ButterCMS
{
public class ButterCMSClient
{
private static readonly string generalExMsg = "There is a problem with the ButterCMS service";

private string authToken;
private HttpClient httpClient;
private TimeSpan defaultTimeout = new TimeSpan(0, 0, 10);
private int maxRequestTries;

private const string apiBaseAddress = "https://api.buttercms.com/";

Expand All @@ -41,13 +44,14 @@ private string authTokenParam
}
private JsonSerializerSettings serializerSettings;

public ButterCMSClient(string authToken, TimeSpan? timeOut = null)
public ButterCMSClient(string authToken, TimeSpan? timeOut = null, int maxRequestTries = 3)
{
httpClient = new HttpClient
{
Timeout = timeOut ?? defaultTimeout,
BaseAddress = new Uri(apiBaseAddress)
};
this.maxRequestTries = maxRequestTries;
this.authToken = authToken;

serializerSettings = new JsonSerializerSettings();
Expand Down Expand Up @@ -453,8 +457,30 @@ public async Task<string> RetrieveContentFieldsJSONAsync(string[] keys)
var contentFields = await ExecuteAsync(queryString);
return contentFields;
}

private string Execute(string queryString)
{
var remainingTries = maxRequestTries;
var exceptions = new List<Exception>();

do
{
--remainingTries;
try
{
return ExecuteSingle(queryString);
}
catch (Exception e)
{
exceptions.Add(e);
}
}
while (remainingTries > 0);

throw aggregateExceptions(exceptions);
}

private string ExecuteSingle(string queryString)
{
try
{
Expand All @@ -469,7 +495,7 @@ private string Execute(string queryString)
}
if (response.StatusCode >= System.Net.HttpStatusCode.InternalServerError)
{
throw new Exception("There is a problem with the ButterCMS service");
throw new Exception(generalExMsg);
}
}
catch (TaskCanceledException taskException)
Expand All @@ -492,6 +518,28 @@ private string Execute(string queryString)
}

private async Task<string> ExecuteAsync(string queryString)
{
var remainingTries = maxRequestTries;
var exceptions = new List<Exception>();

do
{
--remainingTries;
try
{
return await ExecuteSingleAsync(queryString);
}
catch (Exception e)
{
exceptions.Add(e);
}
}
while (remainingTries > 0);

throw aggregateExceptions(exceptions);
}

private async Task<string> ExecuteSingleAsync(string queryString)
{
try
{
Expand All @@ -506,7 +554,7 @@ private async Task<string> ExecuteAsync(string queryString)
}
if (response.StatusCode >= System.Net.HttpStatusCode.InternalServerError)
{
throw new Exception("There is a problem with the ButterCMS service");
throw new Exception(generalExMsg);
}
}
catch (TaskCanceledException taskException)
Expand All @@ -527,5 +575,55 @@ private async Task<string> ExecuteAsync(string queryString)
}
return string.Empty;
}

private Exception aggregateExceptions(List<Exception> exceptions)
{
// If we somehow managed to fail all the requests without getting any exceptions,
// return a general exception. This shouldn't be possible.
if (!exceptions.Any())
return new Exception(generalExMsg);

var uniqueExceptions = exceptions.Distinct(new ExceptionEqualityComparer());

// If all the requests failed with the same exception (should be the case most of the time),
// just return one exception to represent them all.
if (uniqueExceptions.Count() == 1)
return uniqueExceptions.First();

// If all the requests failed but for different reasons, return an AggregateException
// with all the root-cause exceptions.
return new AggregateException(generalExMsg, uniqueExceptions);
}

/// <summary>
/// Used to aggregate exceptions that occur on request retries.
/// </summary>
/// <remarks>
/// In most cases, the same exception will occur multiple times,
/// but we don't want to return multiple copies of it. This class is used
/// to find exceptions that are duplicates by type and message so we can
/// only return one of them.
/// </remarks>
private class ExceptionEqualityComparer : IEqualityComparer<Exception>
{
public bool Equals(Exception e1, Exception e2)
{
if (e2 == null && e1 == null)
return true;
else if (e1 == null | e2 == null)
return false;
else if (e1.GetType().Name.Equals(e2.GetType().Name) && e1.Message.Equals(e2.Message))
return true;
else
return false;
}

public int GetHashCode(Exception e)
{
return (e.GetType().Name + e.Message).GetHashCode();
}
}

}

}

0 comments on commit 79292ff

Please sign in to comment.