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>
[EditorBrowsable(EditorBrowsableState.Advanced)]
public sealed class OpenIddictClientSystemNetHttpConfiguration : IConfigureOptions<OpenIddictClientOptions>,
IConfigureNamedOptions<HttpClientFactoryOptions>
IConfigureNamedOptions<HttpClientFactoryOptions>,
IPostConfigureOptions<HttpClientFactoryOptions>
{
private readonly IServiceProvider _provider;
@ -73,7 +74,7 @@ public sealed class OpenIddictClientSystemNetHttpConfiguration : IConfigureOptio
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.
// 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
var options = _provider.GetRequiredService<IOptionsMonitor<OpenIddictClientSystemNetHttpOptions>>();
#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)
{
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.
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(registration,
builder.PrimaryHandler as HttpClientHandler ??
throw new InvalidOperationException(SR.FormatID0373(typeof(HttpClientHandler).FullName))));
}
static bool TryResolveRegistrationId(string name, [NotNullWhen(true)] out string? value)
{
var assembly = typeof(OpenIddictClientSystemNetHttpOptions).Assembly.GetName();
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();
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 = null;
return false;
}
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(
[
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);

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

@ -23,7 +23,8 @@ namespace OpenIddict.Validation.SystemNetHttp;
/// </summary>
[EditorBrowsable(EditorBrowsableState.Advanced)]
public sealed class OpenIddictValidationSystemNetHttpConfiguration : IConfigureOptions<OpenIddictValidationOptions>,
IConfigureNamedOptions<HttpClientFactoryOptions>
IConfigureNamedOptions<HttpClientFactoryOptions>,
IPostConfigureOptions<HttpClientFactoryOptions>
{
private readonly IServiceProvider _provider;
@ -66,7 +67,7 @@ public sealed class OpenIddictValidationSystemNetHttpConfiguration : IConfigureO
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.
// 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
var options = _provider.GetRequiredService<IOptionsMonitor<OpenIddictValidationSystemNetHttpOptions>>();
#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)
{
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.
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(
[
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);

Loading…
Cancel
Save