Browse Source

Update the System.Net.Http integrations to support per-provider HttpClient instantiation and allow replacing it dynamically

pull/1634/head
Kévin Chalet 3 years ago
parent
commit
af73574512
  1. 3
      src/OpenIddict.Abstractions/OpenIddictResources.resx
  2. 2
      src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpConfiguration.cs
  3. 4
      src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.Discovery.cs
  4. 2
      src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.Exchange.cs
  5. 2
      src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.Userinfo.cs
  6. 113
      src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.cs
  7. 8
      src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHelpers.cs
  8. 4
      src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.Discovery.cs
  9. 2
      src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.Introspection.cs
  10. 111
      src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs
  11. 8
      src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHelpers.cs

3
src/OpenIddict.Abstractions/OpenIddictResources.resx

@ -1414,6 +1414,9 @@ Alternatively, create a class implementing 'IOpenIddictClientHandler<HandlePo
<value>The post-logout redirection response was not correctly applied.
To apply post-logout redirection responses, create a class implementing 'IOpenIddictClientHandler&lt;ApplyPostLogoutRedirectionResponseContext&gt;' and register it using 'services.AddOpenIddict().AddClient().AddEventHandler()'.</value>
</data>
<data name="ID0372" xml:space="preserve">
<value>The System.Net.Http client cannot be resolved.</value>
</data>
<data name="ID2000" xml:space="preserve">
<value>The security token is missing.</value>
</data>

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

@ -54,7 +54,7 @@ public sealed class OpenIddictClientSystemNetHttpConfiguration : IConfigureOptio
// Only amend the HTTP client factory options if the instance is managed by OpenIddict.
var assembly = typeof(OpenIddictClientSystemNetHttpOptions).Assembly.GetName();
if (!string.Equals(name, assembly.Name, StringComparison.Ordinal))
if (string.IsNullOrEmpty(name) || !name.StartsWith(assembly.Name!, StringComparison.Ordinal))
{
return;
}

4
src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.Discovery.cs

@ -16,7 +16,9 @@ public static partial class OpenIddictClientSystemNetHttpHandlers
/*
* Configuration request processing:
*/
CreateHttpClient<PrepareConfigurationRequestContext>.Descriptor,
PrepareGetHttpRequest<PrepareConfigurationRequestContext>.Descriptor,
AttachHttpVersion<PrepareConfigurationRequestContext>.Descriptor,
AttachJsonAcceptHeaders<PrepareConfigurationRequestContext>.Descriptor,
AttachUserAgentHeader<PrepareConfigurationRequestContext>.Descriptor,
AttachFromHeader<PrepareConfigurationRequestContext>.Descriptor,
@ -36,7 +38,9 @@ public static partial class OpenIddictClientSystemNetHttpHandlers
/*
* Cryptography request processing:
*/
CreateHttpClient<PrepareCryptographyRequestContext>.Descriptor,
PrepareGetHttpRequest<PrepareCryptographyRequestContext>.Descriptor,
AttachHttpVersion<PrepareCryptographyRequestContext>.Descriptor,
AttachJsonAcceptHeaders<PrepareCryptographyRequestContext>.Descriptor,
AttachUserAgentHeader<PrepareCryptographyRequestContext>.Descriptor,
AttachFromHeader<PrepareCryptographyRequestContext>.Descriptor,

2
src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.Exchange.cs

@ -19,7 +19,9 @@ public static partial class OpenIddictClientSystemNetHttpHandlers
/*
* Token request processing:
*/
CreateHttpClient<PrepareTokenRequestContext>.Descriptor,
PreparePostHttpRequest<PrepareTokenRequestContext>.Descriptor,
AttachHttpVersion<PrepareTokenRequestContext>.Descriptor,
AttachJsonAcceptHeaders<PrepareTokenRequestContext>.Descriptor,
AttachUserAgentHeader<PrepareTokenRequestContext>.Descriptor,
AttachFromHeader<PrepareTokenRequestContext>.Descriptor,

2
src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.Userinfo.cs

@ -19,7 +19,9 @@ public static partial class OpenIddictClientSystemNetHttpHandlers
/*
* Userinfo request processing:
*/
CreateHttpClient<PrepareUserinfoRequestContext>.Descriptor,
PrepareGetHttpRequest<PrepareUserinfoRequestContext>.Descriptor,
AttachHttpVersion<PrepareUserinfoRequestContext>.Descriptor,
AttachJsonAcceptHeaders<PrepareUserinfoRequestContext>.Descriptor,
AttachUserAgentHeader<PrepareUserinfoRequestContext>.Descriptor,
AttachFromHeader<PrepareUserinfoRequestContext>.Descriptor,

113
src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.cs

@ -9,7 +9,6 @@ using System.ComponentModel;
using System.Diagnostics;
using System.IO.Compression;
using System.Net.Http.Headers;
using System.Net.Mail;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
@ -27,6 +26,47 @@ public static partial class OpenIddictClientSystemNetHttpHandlers
.AddRange(Exchange.DefaultHandlers)
.AddRange(Userinfo.DefaultHandlers);
/// <summary>
/// Contains the logic responsible for creating and attaching a <see cref="HttpClient"/>.
/// </summary>
public sealed class CreateHttpClient<TContext> : IOpenIddictClientHandler<TContext> where TContext : BaseExternalContext
{
private readonly IHttpClientFactory _factory;
public CreateHttpClient(IHttpClientFactory factory)
=> _factory = factory ?? throw new ArgumentNullException(nameof(factory));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<TContext>()
.AddFilter<RequireHttpMetadataUri>()
.UseSingletonHandler<CreateHttpClient<TContext>>()
.SetOrder(int.MinValue + 100_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(TContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
var assembly = typeof(OpenIddictClientSystemNetHttpOptions).Assembly.GetName();
var name = !string.IsNullOrEmpty(context.Registration.ProviderName) ?
$"{assembly.Name}:{context.Registration.ProviderName}" : assembly.Name!;
// Create and store the HttpClient in the transaction properties.
context.Transaction.SetProperty(typeof(HttpClient).FullName!, _factory.CreateClient(name) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0174)));
return default;
}
}
/// <summary>
/// Contains the logic responsible for preparing an HTTP GET request message.
/// </summary>
@ -39,7 +79,7 @@ public static partial class OpenIddictClientSystemNetHttpHandlers
= OpenIddictClientHandlerDescriptor.CreateBuilder<TContext>()
.AddFilter<RequireHttpMetadataUri>()
.UseSingletonHandler<PrepareGetHttpRequest<TContext>>()
.SetOrder(int.MinValue + 100_000)
.SetOrder(CreateHttpClient<TContext>.Descriptor.Order + 1_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
@ -91,6 +131,53 @@ public static partial class OpenIddictClientSystemNetHttpHandlers
}
}
/// <summary>
/// Contains the logic responsible for attaching the HTTP version to the HTTP request message.
/// </summary>
public sealed class AttachHttpVersion<TContext> : IOpenIddictClientHandler<TContext> where TContext : BaseExternalContext
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<TContext>()
.AddFilter<RequireHttpMetadataUri>()
.UseSingletonHandler<AttachHttpVersion<TContext>>()
.SetOrder(PreparePostHttpRequest<TContext>.Descriptor.Order + 1_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(TContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
#if SUPPORTS_HTTP_CLIENT_DEFAULT_REQUEST_VERSION || SUPPORTS_HTTP_CLIENT_DEFAULT_REQUEST_VERSION_POLICY
// This handler only applies to System.Net.Http requests. If the HTTP request cannot be resolved,
// this may indicate that the request was incorrectly processed by another client stack.
var request = context.Transaction.GetHttpRequestMessage() ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0173));
var client = context.Transaction.GetHttpClient() ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0372));
#if SUPPORTS_HTTP_CLIENT_DEFAULT_REQUEST_VERSION
// If supported, import the HTTP version from the client instance.
request.Version = client.DefaultRequestVersion;
#endif
#if SUPPORTS_HTTP_CLIENT_DEFAULT_REQUEST_VERSION_POLICY
// If supported, import the HTTP version policy from the client instance.
request.VersionPolicy = client.DefaultVersionPolicy;
#endif
#endif
return default;
}
}
/// <summary>
/// Contains the logic responsible for attaching the appropriate HTTP
/// Accept-* headers to the HTTP request message to receive JSON responses.
@ -104,7 +191,7 @@ public static partial class OpenIddictClientSystemNetHttpHandlers
= OpenIddictClientHandlerDescriptor.CreateBuilder<TContext>()
.AddFilter<RequireHttpMetadataUri>()
.UseSingletonHandler<AttachJsonAcceptHeaders<TContext>>()
.SetOrder(PreparePostHttpRequest<TContext>.Descriptor.Order + 1_000)
.SetOrder(AttachHttpVersion<TContext>.Descriptor.Order + 1_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
@ -319,11 +406,6 @@ public static partial class OpenIddictClientSystemNetHttpHandlers
/// </summary>
public sealed class SendHttpRequest<TContext> : IOpenIddictClientHandler<TContext> where TContext : BaseExternalContext
{
private readonly IHttpClientFactory _factory;
public SendHttpRequest(IHttpClientFactory factory)
=> _factory = factory ?? throw new ArgumentNullException(nameof(factory));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
@ -348,19 +430,10 @@ public static partial class OpenIddictClientSystemNetHttpHandlers
var request = context.Transaction.GetHttpRequestMessage() ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0173));
var assembly = typeof(OpenIddictClientSystemNetHttpOptions).Assembly.GetName();
using var client = _factory.CreateClient(assembly.Name!) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0174));
// Note: a "using" statement is deliberately used here to dispose of the client in this handler.
using var client = context.Transaction.GetHttpClient() ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0372));
#if SUPPORTS_HTTP_CLIENT_DEFAULT_REQUEST_VERSION
// If supported, import the HTTP version from the client instance.
request.Version = client.DefaultRequestVersion;
#endif
#if SUPPORTS_HTTP_CLIENT_DEFAULT_REQUEST_VERSION_POLICY
// If supported, import the HTTP version policy from the client instance.
request.VersionPolicy = client.DefaultVersionPolicy;
#endif
HttpResponseMessage response;
try

8
src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHelpers.cs

@ -13,6 +13,14 @@ namespace System.Net.Http;
/// </summary>
public static class OpenIddictClientSystemNetHttpHelpers
{
/// <summary>
/// Gets the <see cref="HttpClient"/> associated with the current context.
/// </summary>
/// <param name="transaction">The transaction instance.</param>
/// <returns>The <see cref="HttpClient"/> instance or <see langword="null"/> if it couldn't be found.</returns>
public static HttpClient? GetHttpClient(this OpenIddictClientTransaction transaction)
=> transaction.GetProperty<HttpClient>(typeof(HttpClient).FullName!);
/// <summary>
/// Gets the <see cref="HttpRequestMessage"/> associated with the current context.
/// </summary>

4
src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.Discovery.cs

@ -16,7 +16,9 @@ public static partial class OpenIddictValidationSystemNetHttpHandlers
/*
* Configuration request processing:
*/
CreateHttpClient<PrepareConfigurationRequestContext>.Descriptor,
PrepareGetHttpRequest<PrepareConfigurationRequestContext>.Descriptor,
AttachHttpVersion<PrepareConfigurationRequestContext>.Descriptor,
AttachJsonAcceptHeaders<PrepareConfigurationRequestContext>.Descriptor,
AttachUserAgentHeader<PrepareConfigurationRequestContext>.Descriptor,
AttachFromHeader<PrepareConfigurationRequestContext>.Descriptor,
@ -36,7 +38,9 @@ public static partial class OpenIddictValidationSystemNetHttpHandlers
/*
* Cryptography request processing:
*/
CreateHttpClient<PrepareCryptographyRequestContext>.Descriptor,
PrepareGetHttpRequest<PrepareCryptographyRequestContext>.Descriptor,
AttachHttpVersion<PrepareCryptographyRequestContext>.Descriptor,
AttachJsonAcceptHeaders<PrepareCryptographyRequestContext>.Descriptor,
AttachUserAgentHeader<PrepareCryptographyRequestContext>.Descriptor,
AttachFromHeader<PrepareCryptographyRequestContext>.Descriptor,

2
src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.Introspection.cs

@ -19,7 +19,9 @@ public static partial class OpenIddictValidationSystemNetHttpHandlers
/*
* Introspection request processing:
*/
CreateHttpClient<PrepareIntrospectionRequestContext>.Descriptor,
PreparePostHttpRequest<PrepareIntrospectionRequestContext>.Descriptor,
AttachHttpVersion<PrepareIntrospectionRequestContext>.Descriptor,
AttachJsonAcceptHeaders<PrepareIntrospectionRequestContext>.Descriptor,
AttachUserAgentHeader<PrepareIntrospectionRequestContext>.Descriptor,
AttachFromHeader<PrepareIntrospectionRequestContext>.Descriptor,

111
src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs

@ -9,7 +9,6 @@ using System.ComponentModel;
using System.Diagnostics;
using System.IO.Compression;
using System.Net.Http.Headers;
using System.Net.Mail;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
@ -26,6 +25,45 @@ public static partial class OpenIddictValidationSystemNetHttpHandlers
.AddRange(Discovery.DefaultHandlers)
.AddRange(Introspection.DefaultHandlers);
/// <summary>
/// Contains the logic responsible for creating and attaching a <see cref="HttpClient"/>.
/// </summary>
public sealed class CreateHttpClient<TContext> : IOpenIddictValidationHandler<TContext> where TContext : BaseExternalContext
{
private readonly IHttpClientFactory _factory;
public CreateHttpClient(IHttpClientFactory factory)
=> _factory = factory ?? throw new ArgumentNullException(nameof(factory));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<TContext>()
.AddFilter<RequireHttpMetadataUri>()
.UseSingletonHandler<CreateHttpClient<TContext>>()
.SetOrder(int.MinValue + 100_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(TContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
var assembly = typeof(OpenIddictValidationSystemNetHttpOptions).Assembly.GetName();
// Create and store the HttpClient in the transaction properties.
context.Transaction.SetProperty(typeof(HttpClient).FullName!, _factory.CreateClient(assembly.Name!) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0174)));
return default;
}
}
/// <summary>
/// Contains the logic responsible for preparing an HTTP GET request message.
/// </summary>
@ -38,7 +76,7 @@ public static partial class OpenIddictValidationSystemNetHttpHandlers
= OpenIddictValidationHandlerDescriptor.CreateBuilder<TContext>()
.AddFilter<RequireHttpMetadataUri>()
.UseSingletonHandler<PrepareGetHttpRequest<TContext>>()
.SetOrder(int.MinValue + 100_000)
.SetOrder(CreateHttpClient<TContext>.Descriptor.Order + 1_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build();
@ -90,6 +128,53 @@ public static partial class OpenIddictValidationSystemNetHttpHandlers
}
}
/// <summary>
/// Contains the logic responsible for attaching the HTTP version to the HTTP request message.
/// </summary>
public sealed class AttachHttpVersion<TContext> : IOpenIddictValidationHandler<TContext> where TContext : BaseExternalContext
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<TContext>()
.AddFilter<RequireHttpMetadataUri>()
.UseSingletonHandler<AttachHttpVersion<TContext>>()
.SetOrder(PreparePostHttpRequest<TContext>.Descriptor.Order + 1_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(TContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
#if SUPPORTS_HTTP_CLIENT_DEFAULT_REQUEST_VERSION || SUPPORTS_HTTP_CLIENT_DEFAULT_REQUEST_VERSION_POLICY
// This handler only applies to System.Net.Http requests. If the HTTP request cannot be resolved,
// this may indicate that the request was incorrectly processed by another client stack.
var request = context.Transaction.GetHttpRequestMessage() ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0173));
var client = context.Transaction.GetHttpClient() ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0372));
#if SUPPORTS_HTTP_CLIENT_DEFAULT_REQUEST_VERSION
// If supported, import the HTTP version from the client instance.
request.Version = client.DefaultRequestVersion;
#endif
#if SUPPORTS_HTTP_CLIENT_DEFAULT_REQUEST_VERSION_POLICY
// If supported, import the HTTP version policy from the client instance.
request.VersionPolicy = client.DefaultVersionPolicy;
#endif
#endif
return default;
}
}
/// <summary>
/// Contains the logic responsible for attaching the appropriate HTTP
/// Accept-* headers to the HTTP request message to receive JSON responses.
@ -103,7 +188,7 @@ public static partial class OpenIddictValidationSystemNetHttpHandlers
= OpenIddictValidationHandlerDescriptor.CreateBuilder<TContext>()
.AddFilter<RequireHttpMetadataUri>()
.UseSingletonHandler<AttachJsonAcceptHeaders<TContext>>()
.SetOrder(PreparePostHttpRequest<TContext>.Descriptor.Order + 1_000)
.SetOrder(AttachHttpVersion<TContext>.Descriptor.Order + 1_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build();
@ -320,11 +405,6 @@ public static partial class OpenIddictValidationSystemNetHttpHandlers
/// </summary>
public sealed class SendHttpRequest<TContext> : IOpenIddictValidationHandler<TContext> where TContext : BaseExternalContext
{
private readonly IHttpClientFactory _factory;
public SendHttpRequest(IHttpClientFactory factory)
=> _factory = factory ?? throw new ArgumentNullException(nameof(factory));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
@ -349,19 +429,10 @@ public static partial class OpenIddictValidationSystemNetHttpHandlers
var request = context.Transaction.GetHttpRequestMessage() ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0173));
var assembly = typeof(OpenIddictValidationSystemNetHttpOptions).Assembly.GetName();
using var client = _factory.CreateClient(assembly.Name!) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0174));
// Note: a "using" statement is deliberately used here to dispose of the client in this handler.
using var client = context.Transaction.GetHttpClient() ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0372));
#if SUPPORTS_HTTP_CLIENT_DEFAULT_REQUEST_VERSION
// If supported, import the HTTP version from the client instance.
request.Version = client.DefaultRequestVersion;
#endif
#if SUPPORTS_HTTP_CLIENT_DEFAULT_REQUEST_VERSION_POLICY
// If supported, import the HTTP version policy from the client instance.
request.VersionPolicy = client.DefaultVersionPolicy;
#endif
HttpResponseMessage response;
try

8
src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHelpers.cs

@ -13,6 +13,14 @@ namespace System.Net.Http;
/// </summary>
public static class OpenIddictValidationSystemNetHttpHelpers
{
/// <summary>
/// Gets the <see cref="HttpClient"/> associated with the current context.
/// </summary>
/// <param name="transaction">The transaction instance.</param>
/// <returns>The <see cref="HttpClient"/> instance or <see langword="null"/> if it couldn't be found.</returns>
public static HttpClient? GetHttpClient(this OpenIddictValidationTransaction transaction)
=> transaction.GetProperty<HttpClient>(typeof(HttpClient).FullName!);
/// <summary>
/// Gets the <see cref="HttpRequestMessage"/> associated with the current context.
/// </summary>

Loading…
Cancel
Save