Commit e7992b9e authored by Alessio Parma's avatar Alessio Parma

client retry policy

parent e1acf37e
......@@ -18,6 +18,7 @@
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="2.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="Polly" Version="7.2.1" />
<PackageReference Include="PommaLabs.MimeTypes" Version="1.4.0" />
</ItemGroup>
</Project>
\ No newline at end of file
......@@ -22,13 +22,17 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Flurl;
using Flurl.Http;
using Microsoft.Extensions.Options;
using Polly;
using Polly.Retry;
using PommaLabs.MimeTypes;
using PommaLabs.Thumbnailer.Client.Core;
using PommaLabs.Thumbnailer.Client.Models.Enumerations;
......@@ -43,7 +47,10 @@ public sealed class ConcreteThumbnailerClient : IThumbnailerClient
private const string ApiKeyHeaderName = "X-Api-Key";
private const string OptimizationModeParamName = "mode";
private static readonly HashSet<int> s_retriableStatusCodes = new HashSet<int> { 424 };
private readonly IOptions<ThumbnailerClientConfiguration> _clientConfiguration;
private readonly AsyncRetryPolicy _retryPolicy;
private readonly Validator _validator;
/// <summary>
......@@ -57,6 +64,15 @@ public sealed class ConcreteThumbnailerClient : IThumbnailerClient
{
_validator = validator;
_clientConfiguration = clientConfiguration;
_retryPolicy = Policy
.Handle<FlurlHttpException>(ex =>
{
if (ex is FlurlHttpTimeoutException) return true;
return ex.Call.HttpStatus != null && s_retriableStatusCodes.Contains((int)ex.Call.HttpStatus);
})
// Wait 1 second after first error, then 2 and 4 seconds.
.WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt - 1)));
}
/// <inheritdoc/>
......@@ -78,7 +94,7 @@ public sealed class ConcreteThumbnailerClient : IThumbnailerClient
.SetQueryParam(nameof(shavePx), shavePx.ToString(CultureInfo.InvariantCulture))
.SetQueryParam(nameof(fill), fill.ToString(CultureInfo.InvariantCulture))
.GetBytesAsync(cancellationToken)
.ConfigureAwait(false))
.ConfigureAwait(false), cancellationToken)
.ConfigureAwait(false);
}
......@@ -105,7 +121,7 @@ public sealed class ConcreteThumbnailerClient : IThumbnailerClient
.SetQueryParam(nameof(fill), fill.ToString(CultureInfo.InvariantCulture))
.PostMultipartAsync(mp => mp.AddFile("file", new MemoryStream(contents), fileName, contentType), cancellationToken)
.ReceiveBytes()
.ConfigureAwait(false))
.ConfigureAwait(false), cancellationToken)
.ConfigureAwait(false);
}
......@@ -136,7 +152,7 @@ public Task<bool> IsThumbnailGenerationSupportedAsync(string contentType)
.SetQueryParam(nameof(fileUri), fileUri.AbsoluteUri)
.SetQueryParam(OptimizationModeParamName, mode.ToString())
.GetBytesAsync(cancellationToken)
.ConfigureAwait(false))
.ConfigureAwait(false), cancellationToken)
.ConfigureAwait(false);
}
......@@ -159,7 +175,7 @@ public Task<bool> IsThumbnailGenerationSupportedAsync(string contentType)
.SetQueryParam(OptimizationModeParamName, mode.ToString())
.PostMultipartAsync(mp => mp.AddFile("file", new MemoryStream(contents), fileName, contentType), cancellationToken)
.ReceiveBytes()
.ConfigureAwait(false))
.ConfigureAwait(false), cancellationToken)
.ConfigureAwait(false);
}
......@@ -169,11 +185,16 @@ private static string AdjustFileName(string contentType, string? fileName)
return $"f{(!string.IsNullOrWhiteSpace(extension) ? extension : MimeTypeMap.GetExtension(contentType))}";
}
private static async Task<byte[]> EvaluateResponseAsync(OperationType operationType, Func<Task<byte[]>> httpCall)
private async Task<byte[]> EvaluateResponseAsync(
OperationType operationType,
Func<Task<byte[]>> httpCall,
CancellationToken cancellationToken)
{
try
{
return await httpCall().ConfigureAwait(false);
return await _retryPolicy.ExecuteAsync(
async _ => await httpCall().ConfigureAwait(false),
cancellationToken).ConfigureAwait(false);
}
catch (FlurlHttpTimeoutException ftex)
{
......
......@@ -91,7 +91,7 @@ public sealed class ConcreteOptimizationManager : IOptimizationManager
{
ImageOptimizationMode.Lossless => s_losslessPlugins,
ImageOptimizationMode.Lossy => s_lossyPlugins,
_ => throw new InvalidOperationException()
_ => throw new ArgumentException("Invalid image optimization mode", nameof(mode))
};
var plugin = plugins[file.ContentType];
......
......@@ -213,6 +213,7 @@ public void ConfigureContainer(ServiceRegistry services)
// ASP.NET Core:
services.AddProblemDetails(options =>
{
options.MapToStatusCode<ArgumentException>(StatusCodes.Status400BadRequest);
options.MapToStatusCode<ArgumentNullException>(StatusCodes.Status400BadRequest);
options.MapToStatusCode<CommandException>(StatusCodes.Status422UnprocessableEntity);
options.MapToStatusCode<InvalidOperationException>(StatusCodes.Status424FailedDependency);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment