Skip to content

Commit

Permalink
add handle for 5xx errors
Browse files Browse the repository at this point in the history
  • Loading branch information
izharikov committed Jun 17, 2024
1 parent b6ac069 commit 0050462
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 19 deletions.
11 changes: 7 additions & 4 deletions dotnet/SitecoreSend.SDK.Tests/Limiter/SendRateLimiterPolicy.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Polly;
using Polly.Contrib.WaitAndRetry;
using Polly.RateLimit;

namespace SitecoreSend.SDK.Tests.Limiter;
Expand Down Expand Up @@ -29,7 +30,7 @@ public class SendRateLimiterPolicy<T> where T : SendResponse?
TimeSpan perTimeSpan)
{
Instance = Policy.WrapAsync(PoliciesHelper.RetryRateLimit<T>(),
PoliciesHelper.RetryRateLimitException<T>(),
PoliciesHelper.RetryRateLimitException<T>(numberOfExecutions, perTimeSpan),
PoliciesHelper.ConfigureRateLimit<T>(numberOfExecutions, perTimeSpan));
}
}
Expand All @@ -44,17 +45,19 @@ public static class PoliciesHelper
;
}

public static AsyncPolicy<T> RetryRateLimitException<T>()
public static AsyncPolicy<T> RetryRateLimitException<T>(int numberOfExecutions, TimeSpan perTimeSpan)
{
var delay = Backoff.DecorrelatedJitterBackoffV2(medianFirstRetryDelay: perTimeSpan.Divide(numberOfExecutions) * 1.5,
retryCount: 5);
return Policy<T>.Handle<RateLimitRejectedException>()
.WaitAndRetryAsync(3, (i) => TimeSpan.FromSeconds(i * 2));
.WaitAndRetryAsync(delay);
}

public static AsyncPolicy<T> RetryRateLimit<T>() where T : SendResponse?
{
return Policy
.HandleResult<T>((response) => response?.RateLimitDetails != null)
.WaitAndRetryAsync(3, (i, result, _) =>
.WaitAndRetryAsync(5, (i, result, _) =>
{
if (result.Result?.RateLimitDetails?.Expires != null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Polly" Version="7.2.3" />
<PackageReference Include="Polly.Contrib.WaitAndRetry" Version="1.1.1" />
</ItemGroup>

<ItemGroup>
Expand Down
18 changes: 18 additions & 0 deletions dotnet/SitecoreSend.SDK/Models/Common/HttpDetails.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System.Net;
using System.Net.Http;

namespace SitecoreSend.SDK;

public class HttpDetails
{
public HttpStatusCode StatusCode { get; set; }
public string? Reason { get; set; }
public bool IsSuccess { get; set; }

public HttpDetails(HttpResponseMessage message)
{
StatusCode = message.StatusCode;
Reason = message.ReasonPhrase;
IsSuccess = (int) message.StatusCode >= 200 && (int) message.StatusCode <= 299;
}
}
6 changes: 4 additions & 2 deletions dotnet/SitecoreSend.SDK/Models/Common/SendResponse.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text.Json.Serialization;
using System.Net;
using System.Text.Json.Serialization;

namespace SitecoreSend.SDK;

Expand All @@ -11,7 +12,8 @@ public class SendResponse<TResponse> : SendResponse
public class SendResponse
{
public int Code { get; set; }
public HttpDetails? Http { get; set; }
public string? Error { get; set; }
public bool Success => Code == 0 && string.IsNullOrEmpty(Error);
public bool Success => (Http?.IsSuccess ?? false) && Code == 0 && string.IsNullOrEmpty(Error);
public RateLimitDetails? RateLimitDetails { get; set; }
}
37 changes: 24 additions & 13 deletions dotnet/SitecoreSend.SDK/Services/Implementations/BaseApiService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,58 +50,69 @@ protected string Url(string baseUrl, params object?[] queryParams)
return result.ToString();
}

protected async Task<T?> Get<T>(string url, CancellationToken? cancellationToken = null) where T : SendResponse
protected async Task<T?> Get<T>(string url, CancellationToken? cancellationToken = null) where T : SendResponse, new()
{
return await Get<T>(_httpClient, url, cancellationToken);
}

protected async Task<T?> Get<T>(HttpClient client, string url, CancellationToken? cancellationToken = null)
where T : SendResponse
where T : SendResponse, new()
{
var response = await client.GetAsync(url, cancellationToken ?? CancellationToken.None);
return await ReadResponse<T>(response).ConfigureAwait(false);
}

protected async Task<T?> Delete<T>(string url, CancellationToken? cancellationToken = null) where T : SendResponse
protected async Task<T?> Delete<T>(string url, CancellationToken? cancellationToken = null) where T : SendResponse, new()
{
return await Delete<T>(_httpClient, url, cancellationToken);
}

protected async Task<T?> Delete<T>(HttpClient client, string url,
CancellationToken? cancellationToken = null) where T : SendResponse
CancellationToken? cancellationToken = null) where T : SendResponse, new()
{
var response = await client.DeleteAsync(url, cancellationToken ?? CancellationToken.None)
.ConfigureAwait(false);
return await ReadResponse<T>(response).ConfigureAwait(false);
}

protected async Task<TResponse?> Post<TResponse>(string url, object body,
CancellationToken? cancellationToken = null) where TResponse : SendResponse
CancellationToken? cancellationToken = null) where TResponse : SendResponse, new()
{
return await Post<TResponse>(_httpClient, url, body, cancellationToken);
}

protected async Task<TResponse?> Post<TResponse>(HttpClient client, string url, object body,
CancellationToken? cancellationToken = null) where TResponse : SendResponse
CancellationToken? cancellationToken = null) where TResponse : SendResponse, new()
{
var response = await client.PostAsJsonAsync(url, body, cancellationToken ?? CancellationToken.None)
.ConfigureAwait(false);
return await ReadResponse<TResponse>(response).ConfigureAwait(false);
}

protected async Task<TResponse?> ReadResponse<TResponse>(HttpResponseMessage response)
where TResponse : SendResponse
where TResponse : SendResponse, new()
{
var result = await response.Content.ReadFromJsonAsync<TResponse>();
if (result?.Error == KnownErrors.RATE_LIMITING)
if (!response.IsSuccessStatusCode)
{
result.RateLimitDetails = new RateLimitDetails()
return new TResponse()
{
FirstCall = GetDateHeader(response, "x-ratelimit-firstcall"),
Expires = GetDateHeader(response, "x-ratelimit-expires"),
Http = new HttpDetails(response),
Error = response.ReasonPhrase,
};
}

var result = await response.Content.ReadFromJsonAsync<TResponse>();
if (result != null)
{
result.Http = new HttpDetails(response);
if (result.Error == KnownErrors.RATE_LIMITING)
{
result.RateLimitDetails = new RateLimitDetails()
{
FirstCall = GetDateHeader(response, "x-ratelimit-firstcall"),
Expires = GetDateHeader(response, "x-ratelimit-expires"),
};
}
}
return result;
}

Expand Down

0 comments on commit 0050462

Please sign in to comment.