From fbc4a0c56e8353214fc0aadbda67a637c0cd7afb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Chalet?= Date: Thu, 27 Apr 2023 17:47:59 +0200 Subject: [PATCH] Introduce ConfigureHttpClient()/ConfigureHttpClientHandler() to simplify customizing the HttpClient/HttpClientHandler used by the client and validation stacks --- .../OpenIddictClientSystemNetHttpBuilder.cs | 134 +++++++++++++++++- ...nIddictClientSystemNetHttpConfiguration.cs | 29 +++- .../OpenIddictClientSystemNetHttpOptions.cs | 12 ++ ...penIddictValidationSystemNetHttpBuilder.cs | 34 ++++- ...ictValidationSystemNetHttpConfiguration.cs | 23 ++- ...penIddictValidationSystemNetHttpOptions.cs | 12 ++ 6 files changed, 232 insertions(+), 12 deletions(-) diff --git a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpBuilder.cs b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpBuilder.cs index 4b5e5929..8597c291 100644 --- a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpBuilder.cs +++ b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpBuilder.cs @@ -32,7 +32,7 @@ public sealed class OpenIddictClientSystemNetHttpBuilder public IServiceCollection Services { get; } /// - /// Amends the default OpenIddict client/server integration configuration. + /// Amends the default OpenIddict client/System.Net.Http configuration. /// /// The delegate used to configure the OpenIddict options. /// This extension can be safely called multiple times. @@ -49,6 +49,138 @@ public sealed class OpenIddictClientSystemNetHttpBuilder return this; } + /// + /// Configures the used by the OpenIddict client/System.Net.Http integration. + /// + /// + /// Note: customizations configured using this method apply to all providers. + /// + /// The delegate used to configure the . + /// The instance. + [EditorBrowsable(EditorBrowsableState.Advanced)] + public OpenIddictClientSystemNetHttpBuilder ConfigureHttpClient(Action configuration) + { + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + return Configure(options => + { + if (options.HttpClientActions.TryGetValue(string.Empty, out var actions)) + { + actions.Add(configuration); + } + + else + { + options.HttpClientActions[string.Empty] = new(capacity: 1) { configuration }; + } + }); + } + + /// + /// Configures the used by the OpenIddict client/System.Net.Http integration. + /// + /// + /// Note: customizations configured using this method only apply to the specified provider. + /// + /// The provider name, to which the customizations are applied. + /// The delegate used to configure the . + /// The instance. + [EditorBrowsable(EditorBrowsableState.Advanced)] + public OpenIddictClientSystemNetHttpBuilder ConfigureHttpClient(string provider, Action configuration) + { + if (string.IsNullOrEmpty(provider)) + { + throw new ArgumentException(SR.FormatID0366(nameof(provider)), nameof(provider)); + } + + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + return Configure(options => + { + if (options.HttpClientActions.TryGetValue(provider, out var actions)) + { + actions.Add(configuration); + } + + else + { + options.HttpClientActions[provider] = new(capacity: 1) { configuration }; + } + }); + } + + /// + /// Configures the used by the OpenIddict client/System.Net.Http integration. + /// + /// + /// Note: customizations configured using this method apply to all providers. + /// + /// The delegate used to configure the . + /// The instance. + [EditorBrowsable(EditorBrowsableState.Advanced)] + public OpenIddictClientSystemNetHttpBuilder ConfigureHttpClientHandler(Action configuration) + { + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + return Configure(options => + { + if (options.HttpClientHandlerActions.TryGetValue(string.Empty, out var actions)) + { + actions.Add(configuration); + } + + else + { + options.HttpClientHandlerActions[string.Empty] = new(capacity: 1) { configuration }; + } + }); + } + + /// + /// Configures the used by the OpenIddict client/System.Net.Http integration. + /// + /// + /// Note: customizations configured using this method only apply to the specified provider. + /// + /// The provider name, to which the customizations are applied. + /// The delegate used to configure the . + /// The instance. + [EditorBrowsable(EditorBrowsableState.Advanced)] + public OpenIddictClientSystemNetHttpBuilder ConfigureHttpClientHandler(string provider, Action configuration) + { + if (string.IsNullOrEmpty(provider)) + { + throw new ArgumentException(SR.FormatID0366(nameof(provider)), nameof(provider)); + } + + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + return Configure(options => + { + if (options.HttpClientHandlerActions.TryGetValue(provider, out var actions)) + { + actions.Add(configuration); + } + + else + { + options.HttpClientHandlerActions[provider] = new(capacity: 1) { configuration }; + } + }); + } + /// /// Sets the contact address used in the "From" 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 2ca70f6b..036f3821 100644 --- a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpConfiguration.cs +++ b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpConfiguration.cs @@ -19,7 +19,6 @@ namespace OpenIddict.Client.SystemNetHttp; public sealed class OpenIddictClientSystemNetHttpConfiguration : IConfigureOptions, IConfigureNamedOptions { -#if !SUPPORTS_SERVICE_PROVIDER_IN_HTTP_MESSAGE_HANDLER_BUILDER private readonly IServiceProvider _provider; /// @@ -28,7 +27,6 @@ public sealed class OpenIddictClientSystemNetHttpConfiguration : IConfigureOptio /// The service provider. public OpenIddictClientSystemNetHttpConfiguration(IServiceProvider provider) => _provider = provider ?? throw new ArgumentNullException(nameof(provider)); -#endif /// public void Configure(OpenIddictClientOptions options) @@ -60,15 +58,26 @@ public sealed class OpenIddictClientSystemNetHttpConfiguration : IConfigureOptio return; } - options.HttpClientActions.Add(options => + var settings = _provider.GetRequiredService>().CurrentValue; + + options.HttpClientActions.Add(client => { // By default, HttpClient uses a default timeout of 100 seconds and allows payloads of up to 2GB. // To help reduce the effects of malicious responses (e.g responses returned at a very slow pace // or containing an infine amount of data), the default values are amended to use lower values. - options.MaxResponseContentBufferSize = 10 * 1024 * 1024; - options.Timeout = TimeSpan.FromMinutes(1); + client.MaxResponseContentBufferSize = 10 * 1024 * 1024; + client.Timeout = TimeSpan.FromMinutes(1); }); + // Register the user-defined HTTP client actions. + foreach (var action in settings.HttpClientActions + .Where(action => string.IsNullOrEmpty(action.Key) || // Note: actions that have an empty key apply to all providers. + action.Key.AsSpan().Equals(name.AsSpan(assembly.Name!.Length + 1), StringComparison.Ordinal)) + .SelectMany(action => action.Value)) + { + options.HttpClientActions.Add(action); + } + options.HttpMessageHandlerBuilderActions.Add(builder => { #if SUPPORTS_SERVICE_PROVIDER_IN_HTTP_MESSAGE_HANDLER_BUILDER @@ -98,5 +107,15 @@ public sealed class OpenIddictClientSystemNetHttpConfiguration : IConfigureOptio builder.AdditionalHandlers.Add(new PolicyHttpMessageHandler(policy)); } }); + + // Register the user-defined HTTP client handler actions. + foreach (var action in settings.HttpClientHandlerActions + .Where(action => string.IsNullOrEmpty(action.Key) || // Note: actions that have an empty key apply to all providers. + action.Key.AsSpan().Equals(name.AsSpan(assembly.Name!.Length + 1), StringComparison.Ordinal)) + .SelectMany(action => action.Value)) + { + options.HttpMessageHandlerBuilderActions.Add(builder => action(builder.PrimaryHandler as HttpClientHandler ?? + throw new InvalidOperationException(SR.FormatID0373(typeof(HttpClientHandler).FullName)))); + } } } diff --git a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpOptions.cs b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpOptions.cs index 9a0d7d1f..d393314c 100644 --- a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpOptions.cs +++ b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpOptions.cs @@ -36,4 +36,16 @@ public sealed class OpenIddictClientSystemNetHttpOptions /// attached to the backchannel HTTP requests sent to the authorization server. /// public ProductInfoHeaderValue? ProductInformation { get; set; } + + /// + /// Gets the user-defined actions used to amend the + /// instances created by the OpenIddict client/System.Net.Http integration. + /// + public Dictionary>> HttpClientActions { get; } = new(); + + /// + /// Gets the user-defined actions used to amend the + /// instances created by the OpenIddict client/System.Net.Http integration. + /// + public Dictionary>> HttpClientHandlerActions { get; } = new(); } diff --git a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpBuilder.cs b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpBuilder.cs index 209c2e88..d6a88253 100644 --- a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpBuilder.cs +++ b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpBuilder.cs @@ -32,7 +32,7 @@ public sealed class OpenIddictValidationSystemNetHttpBuilder public IServiceCollection Services { get; } /// - /// Amends the default OpenIddict validation/server integration configuration. + /// Amends the default OpenIddict validation/System.Net.Http configuration. /// /// The delegate used to configure the OpenIddict options. /// This extension can be safely called multiple times. @@ -49,6 +49,38 @@ public sealed class OpenIddictValidationSystemNetHttpBuilder return this; } + /// + /// Configures the used by the OpenIddict validation/System.Net.Http integration. + /// + /// The delegate used to configure the . + /// The instance. + [EditorBrowsable(EditorBrowsableState.Advanced)] + public OpenIddictValidationSystemNetHttpBuilder ConfigureHttpClient(Action configuration) + { + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + return Configure(options => options.HttpClientActions.Add(configuration)); + } + + /// + /// Configures the used by the OpenIddict client/System.Net.Http integration. + /// + /// The delegate used to configure the . + /// The instance. + [EditorBrowsable(EditorBrowsableState.Advanced)] + public OpenIddictValidationSystemNetHttpBuilder ConfigureHttpClientHandler(Action configuration) + { + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + return Configure(options => options.HttpClientHandlerActions.Add(configuration)); + } + /// /// Sets the contact address used in the "From" 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 9ec617f4..139ee8d2 100644 --- a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpConfiguration.cs +++ b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpConfiguration.cs @@ -19,7 +19,6 @@ namespace OpenIddict.Validation.SystemNetHttp; public sealed class OpenIddictValidationSystemNetHttpConfiguration : IConfigureOptions, IConfigureNamedOptions { -#if !SUPPORTS_SERVICE_PROVIDER_IN_HTTP_MESSAGE_HANDLER_BUILDER private readonly IServiceProvider _provider; /// @@ -28,7 +27,6 @@ public sealed class OpenIddictValidationSystemNetHttpConfiguration : IConfigureO /// The service provider. public OpenIddictValidationSystemNetHttpConfiguration(IServiceProvider provider) => _provider = provider ?? throw new ArgumentNullException(nameof(provider)); -#endif /// public void Configure(OpenIddictValidationOptions options) @@ -60,15 +58,23 @@ public sealed class OpenIddictValidationSystemNetHttpConfiguration : IConfigureO return; } - options.HttpClientActions.Add(options => + var settings = _provider.GetRequiredService>().CurrentValue; + + options.HttpClientActions.Add(client => { // By default, HttpClient uses a default timeout of 100 seconds and allows payloads of up to 2GB. // To help reduce the effects of malicious responses (e.g responses returned at a very slow pace // or containing an infine amount of data), the default values are amended to use lower values. - options.MaxResponseContentBufferSize = 10 * 1024 * 1024; - options.Timeout = TimeSpan.FromMinutes(1); + client.MaxResponseContentBufferSize = 10 * 1024 * 1024; + client.Timeout = TimeSpan.FromMinutes(1); }); + // Register the user-defined HTTP client actions. + foreach (var action in settings.HttpClientActions) + { + options.HttpClientActions.Add(action); + } + options.HttpMessageHandlerBuilderActions.Add(builder => { #if SUPPORTS_SERVICE_PROVIDER_IN_HTTP_MESSAGE_HANDLER_BUILDER @@ -98,5 +104,12 @@ public sealed class OpenIddictValidationSystemNetHttpConfiguration : IConfigureO builder.AdditionalHandlers.Add(new PolicyHttpMessageHandler(policy)); } }); + + // Register the user-defined HTTP client handler actions. + foreach (var action in settings.HttpClientHandlerActions) + { + options.HttpMessageHandlerBuilderActions.Add(builder => action(builder.PrimaryHandler as HttpClientHandler ?? + throw new InvalidOperationException(SR.FormatID0373(typeof(HttpClientHandler).FullName)))); + } } } diff --git a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpOptions.cs b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpOptions.cs index d50946c6..a03ed869 100644 --- a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpOptions.cs +++ b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpOptions.cs @@ -36,4 +36,16 @@ public sealed class OpenIddictValidationSystemNetHttpOptions /// attached to the backchannel HTTP requests sent to the authorization server. /// public ProductInfoHeaderValue? ProductInformation { get; set; } + + /// + /// Gets the user-defined actions used to amend the + /// instances created by the OpenIddict validation/System.Net.Http integration. + /// + public List> HttpClientActions { get; } = new(); + + /// + /// Gets the user-defined actions used to amend the + /// instances created by the OpenIddict validation/System.Net.Http integration. + /// + public List> HttpClientHandlerActions { get; } = new(); }