Browse Source

React to PrimaryHandler no longer being an HttpClientHandler instance when using Microsoft.Extensions.Http 9.0

pull/2141/head
Kévin Chalet 2 years ago
parent
commit
3cecda7fe6
  1. 110
      src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpConfiguration.cs
  2. 3
      src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpExtensions.cs
  3. 85
      src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpConfiguration.cs
  4. 3
      src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpExtensions.cs

110
src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpConfiguration.cs

@ -24,7 +24,8 @@ namespace OpenIddict.Client.SystemNetHttp;
/// </summary> /// </summary>
[EditorBrowsable(EditorBrowsableState.Advanced)] [EditorBrowsable(EditorBrowsableState.Advanced)]
public sealed class OpenIddictClientSystemNetHttpConfiguration : IConfigureOptions<OpenIddictClientOptions>, public sealed class OpenIddictClientSystemNetHttpConfiguration : IConfigureOptions<OpenIddictClientOptions>,
IConfigureNamedOptions<HttpClientFactoryOptions> IConfigureNamedOptions<HttpClientFactoryOptions>,
IPostConfigureOptions<HttpClientFactoryOptions>
{ {
private readonly IServiceProvider _provider; private readonly IServiceProvider _provider;
@ -73,7 +74,7 @@ public sealed class OpenIddictClientSystemNetHttpConfiguration : IConfigureOptio
var settings = _provider.GetRequiredService<IOptionsMonitor<OpenIddictClientSystemNetHttpOptions>>().CurrentValue; var settings = _provider.GetRequiredService<IOptionsMonitor<OpenIddictClientSystemNetHttpOptions>>().CurrentValue;
options.HttpClientActions.Add(client => options.HttpClientActions.Add(static client =>
{ {
// By default, HttpClient uses a default timeout of 100 seconds and allows payloads of up to 2GB. // 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 // To help reduce the effects of malicious responses (e.g responses returned at a very slow pace
@ -95,6 +96,61 @@ public sealed class OpenIddictClientSystemNetHttpConfiguration : IConfigureOptio
#else #else
var options = _provider.GetRequiredService<IOptionsMonitor<OpenIddictClientSystemNetHttpOptions>>(); var options = _provider.GetRequiredService<IOptionsMonitor<OpenIddictClientSystemNetHttpOptions>>();
#endif #endif
// 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<HttpResponseMessage> policy)
{
builder.AdditionalHandlers.Add(new PolicyHttpMessageHandler(policy));
}
#if SUPPORTS_HTTP_CLIENT_RESILIENCE
else if (options.CurrentValue.HttpResiliencePipeline is ResiliencePipeline<HttpResponseMessage> pipeline)
{
#pragma warning disable EXTEXP0001
builder.AdditionalHandlers.Add(new ResilienceHandler(pipeline));
#pragma warning restore EXTEXP0001
}
#endif
});
// Register the user-defined HTTP client handler actions.
foreach (var action in settings.HttpClientHandlerActions)
{
options.HttpMessageHandlerBuilderActions.Add(builder => action(registration,
builder.PrimaryHandler as HttpClientHandler ??
throw new InvalidOperationException(SR.FormatID0373(typeof(HttpClientHandler).FullName))));
}
}
/// <inheritdoc/>
public void PostConfigure(string? name, HttpClientFactoryOptions options)
{
if (options is null)
{
throw new ArgumentNullException(nameof(options));
}
// Only amend the HTTP client factory options if the instance is managed by OpenIddict.
if (string.IsNullOrEmpty(name) || !TryResolveRegistrationId(name, out string? identifier))
{
return;
}
options.HttpMessageHandlerBuilderActions.Insert(0, static builder =>
{
// Note: Microsoft.Extensions.Http 9.0+ no longer uses HttpClientHandler as the default instance
// for PrimaryHandler on platforms that support SocketsHttpHandler. Since OpenIddict requires an
// HttpClientHandler instance, it is manually reassigned here if it's not an HttpClientHandler.
if (builder.PrimaryHandler is not HttpClientHandler)
{
builder.PrimaryHandler = new HttpClientHandler();
}
});
options.HttpMessageHandlerBuilderActions.Add(static builder =>
{
if (builder.PrimaryHandler is not HttpClientHandler handler) if (builder.PrimaryHandler is not HttpClientHandler handler)
{ {
throw new InvalidOperationException(SR.FormatID0373(typeof(HttpClientHandler).FullName)); throw new InvalidOperationException(SR.FormatID0373(typeof(HttpClientHandler).FullName));
@ -135,48 +191,22 @@ public sealed class OpenIddictClientSystemNetHttpConfiguration : IConfigureOptio
// //
// To avoid that, cookies support is explicitly disabled here, for security reasons. // To avoid that, cookies support is explicitly disabled here, for security reasons.
handler.UseCookies = false; handler.UseCookies = false;
// 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<HttpResponseMessage> policy)
{
builder.AdditionalHandlers.Add(new PolicyHttpMessageHandler(policy));
}
#if SUPPORTS_HTTP_CLIENT_RESILIENCE
else if (options.CurrentValue.HttpResiliencePipeline is ResiliencePipeline<HttpResponseMessage> pipeline)
{
#pragma warning disable EXTEXP0001
builder.AdditionalHandlers.Add(new ResilienceHandler(pipeline));
#pragma warning restore EXTEXP0001
}
#endif
}); });
}
// Register the user-defined HTTP client handler actions. static bool TryResolveRegistrationId(string name, [NotNullWhen(true)] out string? value)
foreach (var action in settings.HttpClientHandlerActions) {
{ var assembly = typeof(OpenIddictClientSystemNetHttpOptions).Assembly.GetName();
options.HttpMessageHandlerBuilderActions.Add(builder => action(registration,
builder.PrimaryHandler as HttpClientHandler ??
throw new InvalidOperationException(SR.FormatID0373(typeof(HttpClientHandler).FullName))));
}
static bool TryResolveRegistrationId(string name, [NotNullWhen(true)] out string? value) if (!name.StartsWith(assembly.Name!, StringComparison.Ordinal) ||
name.Length < assembly.Name!.Length + 1 ||
name[assembly.Name.Length] is not ':')
{ {
var assembly = typeof(OpenIddictClientSystemNetHttpOptions).Assembly.GetName(); value = null;
return false;
if (!name.StartsWith(assembly.Name!, StringComparison.Ordinal) ||
name.Length < assembly.Name!.Length + 1 ||
name[assembly.Name.Length] is not ':')
{
value = null;
return false;
}
value = name[(assembly.Name.Length + 1)..];
return true;
} }
value = name[(assembly.Name.Length + 1)..];
return true;
} }
} }

3
src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpExtensions.cs

@ -43,7 +43,8 @@ public static class OpenIddictClientSystemNetHttpExtensions
builder.Services.TryAddEnumerable( builder.Services.TryAddEnumerable(
[ [
ServiceDescriptor.Singleton<IConfigureOptions<OpenIddictClientOptions>, OpenIddictClientSystemNetHttpConfiguration>(), ServiceDescriptor.Singleton<IConfigureOptions<OpenIddictClientOptions>, OpenIddictClientSystemNetHttpConfiguration>(),
ServiceDescriptor.Singleton<IConfigureOptions<HttpClientFactoryOptions>, OpenIddictClientSystemNetHttpConfiguration>() ServiceDescriptor.Singleton<IConfigureOptions<HttpClientFactoryOptions>, OpenIddictClientSystemNetHttpConfiguration>(),
ServiceDescriptor.Singleton<IPostConfigureOptions<HttpClientFactoryOptions>, OpenIddictClientSystemNetHttpConfiguration>()
]); ]);
return new OpenIddictClientSystemNetHttpBuilder(builder.Services); return new OpenIddictClientSystemNetHttpBuilder(builder.Services);

85
src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpConfiguration.cs

@ -23,7 +23,8 @@ namespace OpenIddict.Validation.SystemNetHttp;
/// </summary> /// </summary>
[EditorBrowsable(EditorBrowsableState.Advanced)] [EditorBrowsable(EditorBrowsableState.Advanced)]
public sealed class OpenIddictValidationSystemNetHttpConfiguration : IConfigureOptions<OpenIddictValidationOptions>, public sealed class OpenIddictValidationSystemNetHttpConfiguration : IConfigureOptions<OpenIddictValidationOptions>,
IConfigureNamedOptions<HttpClientFactoryOptions> IConfigureNamedOptions<HttpClientFactoryOptions>,
IPostConfigureOptions<HttpClientFactoryOptions>
{ {
private readonly IServiceProvider _provider; private readonly IServiceProvider _provider;
@ -66,7 +67,7 @@ public sealed class OpenIddictValidationSystemNetHttpConfiguration : IConfigureO
var settings = _provider.GetRequiredService<IOptionsMonitor<OpenIddictValidationSystemNetHttpOptions>>().CurrentValue; var settings = _provider.GetRequiredService<IOptionsMonitor<OpenIddictValidationSystemNetHttpOptions>>().CurrentValue;
options.HttpClientActions.Add(client => options.HttpClientActions.Add(static client =>
{ {
// By default, HttpClient uses a default timeout of 100 seconds and allows payloads of up to 2GB. // 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 // To help reduce the effects of malicious responses (e.g responses returned at a very slow pace
@ -88,6 +89,61 @@ public sealed class OpenIddictValidationSystemNetHttpConfiguration : IConfigureO
#else #else
var options = _provider.GetRequiredService<IOptionsMonitor<OpenIddictValidationSystemNetHttpOptions>>(); var options = _provider.GetRequiredService<IOptionsMonitor<OpenIddictValidationSystemNetHttpOptions>>();
#endif #endif
// 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<HttpResponseMessage> policy)
{
builder.AdditionalHandlers.Add(new PolicyHttpMessageHandler(policy));
}
#if SUPPORTS_HTTP_CLIENT_RESILIENCE
else if (options.CurrentValue.HttpResiliencePipeline is ResiliencePipeline<HttpResponseMessage> pipeline)
{
#pragma warning disable EXTEXP0001
builder.AdditionalHandlers.Add(new ResilienceHandler(pipeline));
#pragma warning restore EXTEXP0001
}
#endif
});
// 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))));
}
}
/// <inheritdoc/>
public void PostConfigure(string? name, HttpClientFactoryOptions options)
{
if (options is null)
{
throw new ArgumentNullException(nameof(options));
}
// Only amend the HTTP client factory options if the instance is managed by OpenIddict.
var assembly = typeof(OpenIddictValidationSystemNetHttpOptions).Assembly.GetName();
if (!string.Equals(name, assembly.Name, StringComparison.Ordinal))
{
return;
}
options.HttpMessageHandlerBuilderActions.Insert(0, static builder =>
{
// Note: Microsoft.Extensions.Http 9.0+ no longer uses HttpClientHandler as the default instance
// for PrimaryHandler on platforms that support SocketsHttpHandler. Since OpenIddict requires an
// HttpClientHandler instance, it is manually reassigned here if it's not an HttpClientHandler.
if (builder.PrimaryHandler is not HttpClientHandler)
{
builder.PrimaryHandler = new HttpClientHandler();
}
});
options.HttpMessageHandlerBuilderActions.Add(static builder =>
{
if (builder.PrimaryHandler is not HttpClientHandler handler) if (builder.PrimaryHandler is not HttpClientHandler handler)
{ {
throw new InvalidOperationException(SR.FormatID0373(typeof(HttpClientHandler).FullName)); throw new InvalidOperationException(SR.FormatID0373(typeof(HttpClientHandler).FullName));
@ -128,31 +184,6 @@ public sealed class OpenIddictValidationSystemNetHttpConfiguration : IConfigureO
// //
// To avoid that, cookies support is explicitly disabled here, for security reasons. // To avoid that, cookies support is explicitly disabled here, for security reasons.
handler.UseCookies = false; handler.UseCookies = false;
// 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<HttpResponseMessage> policy)
{
builder.AdditionalHandlers.Add(new PolicyHttpMessageHandler(policy));
}
#if SUPPORTS_HTTP_CLIENT_RESILIENCE
else if (options.CurrentValue.HttpResiliencePipeline is ResiliencePipeline<HttpResponseMessage> pipeline)
{
#pragma warning disable EXTEXP0001
builder.AdditionalHandlers.Add(new ResilienceHandler(pipeline));
#pragma warning restore EXTEXP0001
}
#endif
}); });
// 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))));
}
} }
} }

3
src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpExtensions.cs

@ -43,7 +43,8 @@ public static class OpenIddictValidationSystemNetHttpExtensions
builder.Services.TryAddEnumerable( builder.Services.TryAddEnumerable(
[ [
ServiceDescriptor.Singleton<IConfigureOptions<OpenIddictValidationOptions>, OpenIddictValidationSystemNetHttpConfiguration>(), ServiceDescriptor.Singleton<IConfigureOptions<OpenIddictValidationOptions>, OpenIddictValidationSystemNetHttpConfiguration>(),
ServiceDescriptor.Singleton<IConfigureOptions<HttpClientFactoryOptions>, OpenIddictValidationSystemNetHttpConfiguration>() ServiceDescriptor.Singleton<IConfigureOptions<HttpClientFactoryOptions>, OpenIddictValidationSystemNetHttpConfiguration>(),
ServiceDescriptor.Singleton<IPostConfigureOptions<HttpClientFactoryOptions>, OpenIddictValidationSystemNetHttpConfiguration>()
]); ]);
return new OpenIddictValidationSystemNetHttpBuilder(builder.Services); return new OpenIddictValidationSystemNetHttpBuilder(builder.Services);

Loading…
Cancel
Save