From be31bc2fd5f364f0af49eb60364b44011050079e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Chalet?= Date: Wed, 14 Feb 2024 11:04:15 +0100 Subject: [PATCH] Update OpenIddict.Client.SystemNetHttp and OpenIddict.Validation.SystemNetHttp to support Microsoft.Extensions.Http.Resilience --- Directory.Build.targets | 1 + Directory.Packages.props | 1 + .../OpenIddict.Client.SystemNetHttp.csproj | 5 ++ .../OpenIddictClientSystemNetHttpBuilder.cs | 46 +++++++++++++++++++ ...nIddictClientSystemNetHttpConfiguration.cs | 19 +++++++- .../OpenIddictClientSystemNetHttpOptions.cs | 35 +++++++++++++- ...OpenIddict.Validation.SystemNetHttp.csproj | 5 ++ ...penIddictValidationSystemNetHttpBuilder.cs | 46 +++++++++++++++++++ ...ictValidationSystemNetHttpConfiguration.cs | 19 +++++++- ...penIddictValidationSystemNetHttpOptions.cs | 35 +++++++++++++- 10 files changed, 204 insertions(+), 8 deletions(-) diff --git a/Directory.Build.targets b/Directory.Build.targets index c95f8662..7923ee0a 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -116,6 +116,7 @@ + $(DefineConstants);SUPPORTS_HTTP_CLIENT_RESILIENCE $(DefineConstants);SUPPORTS_IMMUTABLE_COLLECTIONS_MARSHAL $(DefineConstants);SUPPORTS_TIME_PROVIDER diff --git a/Directory.Packages.props b/Directory.Packages.props index 240d50fa..a4131234 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -326,6 +326,7 @@ + diff --git a/src/OpenIddict.Client.SystemNetHttp/OpenIddict.Client.SystemNetHttp.csproj b/src/OpenIddict.Client.SystemNetHttp/OpenIddict.Client.SystemNetHttp.csproj index 8bbc5c0d..16f9ae41 100644 --- a/src/OpenIddict.Client.SystemNetHttp/OpenIddict.Client.SystemNetHttp.csproj +++ b/src/OpenIddict.Client.SystemNetHttp/OpenIddict.Client.SystemNetHttp.csproj @@ -42,6 +42,11 @@ + + + + diff --git a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpBuilder.cs b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpBuilder.cs index 9d5278bc..f9ac960d 100644 --- a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpBuilder.cs +++ b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpBuilder.cs @@ -241,6 +241,52 @@ public sealed class OpenIddictClientSystemNetHttpBuilder return Configure(options => options.HttpErrorPolicy = policy); } +#if SUPPORTS_HTTP_CLIENT_RESILIENCE + /// + /// Replaces the default HTTP resilience pipeline used by the OpenIddict client services. + /// + /// + /// The delegate used to configure the . + /// + /// + /// Note: this option has no effect when an HTTP error policy was explicitly configured + /// using . + /// + /// The instance. + public OpenIddictClientSystemNetHttpBuilder SetHttpResiliencePipeline( + Action> configuration) + { + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + var builder = new ResiliencePipelineBuilder(); + configuration(builder); + + return SetHttpResiliencePipeline(builder.Build()); + } + + /// + /// Replaces the default HTTP resilience pipeline used by the OpenIddict client services. + /// + /// The HTTP resilience pipeline. + /// + /// Note: this option has no effect when an HTTP error policy was explicitly configured + /// using . + /// + /// The instance. + public OpenIddictClientSystemNetHttpBuilder SetHttpResiliencePipeline(ResiliencePipeline pipeline) + { + if (pipeline is null) + { + throw new ArgumentNullException(nameof(pipeline)); + } + + return Configure(options => options.HttpResiliencePipeline = pipeline); + } +#endif + /// /// Sets the product information used in the "User-Agent" header that is attached /// to the backchannel HTTP requests sent to the authorization server. diff --git a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpConfiguration.cs b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpConfiguration.cs index 7910054d..15124752 100644 --- a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpConfiguration.cs +++ b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpConfiguration.cs @@ -12,6 +12,10 @@ using Microsoft.Extensions.Http; using Microsoft.Extensions.Options; using Polly; +#if SUPPORTS_HTTP_CLIENT_RESILIENCE +using Microsoft.Extensions.Http.Resilience; +#endif + namespace OpenIddict.Client.SystemNetHttp; /// @@ -105,12 +109,23 @@ public sealed class OpenIddictClientSystemNetHttpConfiguration : IConfigureOptio // To avoid that, cookies support is explicitly disabled here, for security reasons. handler.UseCookies = false; - // Unless the HTTP error policy was explicitly disabled in the options, - // add the HTTP handler responsible for replaying failed HTTP requests. + // If applicable, add the handler responsible for replaying failed HTTP requests. + // + // Note: on .NET 8.0 and higher, the HTTP error policy is always set + // to null by default and an HTTP resilience pipeline is used instead. if (options.CurrentValue.HttpErrorPolicy is IAsyncPolicy policy) { builder.AdditionalHandlers.Add(new PolicyHttpMessageHandler(policy)); } + +#if SUPPORTS_HTTP_CLIENT_RESILIENCE + else if (options.CurrentValue.HttpResiliencePipeline is ResiliencePipeline pipeline) + { +#pragma warning disable EXTEXP0001 + builder.AdditionalHandlers.Add(new ResilienceHandler(pipeline)); +#pragma warning restore EXTEXP0001 + } +#endif }); // Register the user-defined HTTP client handler actions. diff --git a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpOptions.cs b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpOptions.cs index 2cab2ef9..4547a9b8 100644 --- a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpOptions.cs +++ b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpOptions.cs @@ -11,6 +11,10 @@ using System.Net.Mail; using Polly; using Polly.Extensions.Http; +#if SUPPORTS_HTTP_CLIENT_RESILIENCE +using Microsoft.Extensions.Http.Resilience; +#endif + namespace OpenIddict.Client.SystemNetHttp; /// @@ -21,10 +25,37 @@ public sealed class OpenIddictClientSystemNetHttpOptions /// /// Gets or sets the HTTP Polly error policy used by the internal OpenIddict HTTP clients. /// + /// + /// Note: on .NET 8.0 and higher, this property is set to by default. + /// public IAsyncPolicy? HttpErrorPolicy { get; set; } +#if !SUPPORTS_HTTP_CLIENT_RESILIENCE = HttpPolicyExtensions.HandleTransientHttpError() - .OrResult(response => response.StatusCode == HttpStatusCode.NotFound) - .WaitAndRetryAsync(4, attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt))); + .OrResult(static response => response.StatusCode is HttpStatusCode.NotFound) + .WaitAndRetryAsync(4, static attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt))); +#endif + +#if SUPPORTS_HTTP_CLIENT_RESILIENCE + /// + /// Gets or sets the HTTP resilience pipeline used by the internal OpenIddict HTTP clients. + /// + /// + /// Note: this property is not used when + /// is explicitly set to a non- value. + /// + public ResiliencePipeline? HttpResiliencePipeline { get; set; } + = new ResiliencePipelineBuilder() + .AddRetry(new HttpRetryStrategyOptions + { + DelayGenerator = static arguments => new( + TimeSpan.FromSeconds(Math.Pow(2, arguments.AttemptNumber))), + MaxRetryAttempts = 4, + ShouldHandle = static arguments => new( + HttpClientResiliencePredicates.IsTransient(arguments.Outcome) || + arguments.Outcome.Result?.StatusCode is HttpStatusCode.NotFound) + }) + .Build(); +#endif /// /// Gets or sets the contact mail address used in the "From" header that is diff --git a/src/OpenIddict.Validation.SystemNetHttp/OpenIddict.Validation.SystemNetHttp.csproj b/src/OpenIddict.Validation.SystemNetHttp/OpenIddict.Validation.SystemNetHttp.csproj index 7063d908..cdb49383 100644 --- a/src/OpenIddict.Validation.SystemNetHttp/OpenIddict.Validation.SystemNetHttp.csproj +++ b/src/OpenIddict.Validation.SystemNetHttp/OpenIddict.Validation.SystemNetHttp.csproj @@ -42,6 +42,11 @@ + + + + diff --git a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpBuilder.cs b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpBuilder.cs index 4dd12524..c245e7fd 100644 --- a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpBuilder.cs +++ b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpBuilder.cs @@ -129,6 +129,52 @@ public sealed class OpenIddictValidationSystemNetHttpBuilder return Configure(options => options.HttpErrorPolicy = policy); } +#if SUPPORTS_HTTP_CLIENT_RESILIENCE + /// + /// Replaces the default HTTP resilience pipeline used by the OpenIddict client services. + /// + /// + /// The delegate used to configure the . + /// + /// + /// Note: this option has no effect when an HTTP error policy was explicitly configured + /// using . + /// + /// The instance. + public OpenIddictValidationSystemNetHttpBuilder SetHttpResiliencePipeline( + Action> configuration) + { + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + var builder = new ResiliencePipelineBuilder(); + configuration(builder); + + return SetHttpResiliencePipeline(builder.Build()); + } + + /// + /// Replaces the default HTTP resilience pipeline used by the OpenIddict client services. + /// + /// The HTTP resilience pipeline. + /// + /// Note: this option has no effect when an HTTP error policy was explicitly configured + /// using . + /// + /// The instance. + public OpenIddictValidationSystemNetHttpBuilder SetHttpResiliencePipeline(ResiliencePipeline pipeline) + { + if (pipeline is null) + { + throw new ArgumentNullException(nameof(pipeline)); + } + + return Configure(options => options.HttpResiliencePipeline = pipeline); + } +#endif + /// /// Sets the product information used in the "User-Agent" header that is attached /// to the backchannel HTTP requests sent to the authorization server. diff --git a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpConfiguration.cs b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpConfiguration.cs index cfbf83bc..0beba556 100644 --- a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpConfiguration.cs +++ b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpConfiguration.cs @@ -11,6 +11,10 @@ using Microsoft.Extensions.Http; using Microsoft.Extensions.Options; using Polly; +#if SUPPORTS_HTTP_CLIENT_RESILIENCE +using Microsoft.Extensions.Http.Resilience; +#endif + namespace OpenIddict.Validation.SystemNetHttp; /// @@ -98,12 +102,23 @@ public sealed class OpenIddictValidationSystemNetHttpConfiguration : IConfigureO // To avoid that, cookies support is explicitly disabled here, for security reasons. handler.UseCookies = false; - // Unless the HTTP error policy was explicitly disabled in the options, - // add the HTTP handler responsible for replaying failed HTTP requests. + // If applicable, add the handler responsible for replaying failed HTTP requests. + // + // Note: on .NET 8.0 and higher, the HTTP error policy is always set + // to null by default and an HTTP resilience pipeline is used instead. if (options.CurrentValue.HttpErrorPolicy is IAsyncPolicy policy) { builder.AdditionalHandlers.Add(new PolicyHttpMessageHandler(policy)); } + +#if SUPPORTS_HTTP_CLIENT_RESILIENCE + else if (options.CurrentValue.HttpResiliencePipeline is ResiliencePipeline pipeline) + { +#pragma warning disable EXTEXP0001 + builder.AdditionalHandlers.Add(new ResilienceHandler(pipeline)); +#pragma warning restore EXTEXP0001 + } +#endif }); // Register the user-defined HTTP client handler actions. diff --git a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpOptions.cs b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpOptions.cs index 86f17b57..35ad1668 100644 --- a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpOptions.cs +++ b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpOptions.cs @@ -11,6 +11,10 @@ using System.Net.Mail; using Polly; using Polly.Extensions.Http; +#if SUPPORTS_HTTP_CLIENT_RESILIENCE +using Microsoft.Extensions.Http.Resilience; +#endif + namespace OpenIddict.Validation.SystemNetHttp; /// @@ -21,10 +25,37 @@ public sealed class OpenIddictValidationSystemNetHttpOptions /// /// Gets or sets the HTTP Polly error policy used by the internal OpenIddict HTTP clients. /// + /// + /// Note: on .NET 8.0 and higher, this property is set to by default. + /// public IAsyncPolicy? HttpErrorPolicy { get; set; } +#if !SUPPORTS_HTTP_CLIENT_RESILIENCE = HttpPolicyExtensions.HandleTransientHttpError() - .OrResult(response => response.StatusCode == HttpStatusCode.NotFound) - .WaitAndRetryAsync(3, attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt))); + .OrResult(static response => response.StatusCode is HttpStatusCode.NotFound) + .WaitAndRetryAsync(4, static attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt))); +#endif + +#if SUPPORTS_HTTP_CLIENT_RESILIENCE + /// + /// Gets or sets the HTTP resilience pipeline used by the internal OpenIddict HTTP clients. + /// + /// + /// Note: this property is not used when + /// is explicitly set to a non- value. + /// + public ResiliencePipeline? HttpResiliencePipeline { get; set; } + = new ResiliencePipelineBuilder() + .AddRetry(new HttpRetryStrategyOptions + { + DelayGenerator = static arguments => new( + TimeSpan.FromSeconds(Math.Pow(2, arguments.AttemptNumber))), + MaxRetryAttempts = 4, + ShouldHandle = static arguments => new( + HttpClientResiliencePredicates.IsTransient(arguments.Outcome) || + arguments.Outcome.Result?.StatusCode is HttpStatusCode.NotFound) + }) + .Build(); +#endif /// /// Gets or sets the contact mail address used in the "From" header that is