-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[API Proposal] Configure SocketsHttpHandler for HttpClientFactory with IConfiguration #84075
Comments
Tagging subscribers to this area: @dotnet/ncl Issue DetailsBackground and motivationThe purpose of the new API, represented by the API Proposalpublic class SocketsHttpHandlerOptions
{
public bool AllowAutoRedirect { get; set; }
public bool UseCookies { get; set; }
public int MaxConnectionsPerServer { get; set; }
public DecompressionMethods AutomaticDecompression { get; set; }
public TimeSpan ConnectTimeout { get; set; }
public TimeSpan PooledConnectionLifetime { get; set; }
public TimeSpan PooledConnectionIdleTimeout { get; set; }
#if NET5_0_OR_GREATER
public TimeSpan KeepAlivePingDelay { get; set; }
public TimeSpan KeepAlivePingTimeout { get; set; }
#endif
}
public class SocketsHttpHandlerBuilder
{
public string Name { get; }
public IServiceCollection Services { get; }
public SocketsHttpHandlerBuilder ConfigureHandler(Action<SocketsHttpHandler> configure) {}
public SocketsHttpHandlerBuilder ConfigureHandler(Action<IServiceProvider, SocketsHttpHandler> configure) {}
public SocketsHttpHandlerBuilder ConfigureOptions(IConfigurationSection section) {}
public SocketsHttpHandlerBuilder ConfigureOptions(Action<SocketsHttpHandlerOptions> configure) {}
}
public static class HttpClientSocketHandlingExtensions
{
public static IHttpClientBuilder AddSocketsHttpHandler(this IHttpClientBuilder builder) {}
public static IHttpClientBuilder AddSocketsHttpHandler(this IHttpClientBuilder builder, Action<SocketsHttpHandlerBuilder> configure) {}
} API Usage{
"HttpClientSettings": {
"MyClient": {
"AllowAutoRedirect": true,
"UseCookies": true,
"ConnectTimeout": "00:00:05"
}
}
} public void ConfigureServices(IServiceCollection services)
{
IConfiguration configuration = ...;
services
.AddHttpClient("MyClient")
.AddSocketsHttpHandler(builder =>
{
builder.ConfigureOptions(configuration.GetSection("HttpClientSettings:MyClient"));
});
} Alternative DesignsNo response RisksNo response
|
@tekian Thanks for the suggestions. But the problem is that SocketsHttpHandler only exists in .NET Core 2.1+. HttpClientFactory is built for netstandard2.0. There're more problems to that -- I wonder how these APIs should behave on .NET Framework? On WASM or on Mobile platforms, where platform-specific handlers are used? Should it throw in these cases? Or ignore the configuration? What should happen if a user configured their own PrimaryHandler in addition to calling this API? Should the configuration replace user's handler? Or the other way around? |
@CarnaViire See inline.
Cannot the entire feature be available only for .NET Core 2.1+?
It should be opt-in. Unless you call
Then order matters. Whichever comes last should win. We could change the name of the method to say |
Tagging subscribers to this area: @dotnet/ncl Issue DetailsBackground and motivationThe purpose of the new API, represented by the API Proposalpublic class SocketsHttpHandlerOptions
{
public bool AllowAutoRedirect { get; set; }
public bool UseCookies { get; set; }
public int MaxConnectionsPerServer { get; set; }
public DecompressionMethods AutomaticDecompression { get; set; }
public TimeSpan ConnectTimeout { get; set; }
public TimeSpan PooledConnectionLifetime { get; set; }
public TimeSpan PooledConnectionIdleTimeout { get; set; }
#if NET5_0_OR_GREATER
public TimeSpan KeepAlivePingDelay { get; set; }
public TimeSpan KeepAlivePingTimeout { get; set; }
#endif
}
public class SocketsHttpHandlerBuilder
{
public string Name { get; }
public IServiceCollection Services { get; }
public SocketsHttpHandlerBuilder ConfigureHandler(Action<SocketsHttpHandler> configure) {}
public SocketsHttpHandlerBuilder ConfigureHandler(Action<IServiceProvider, SocketsHttpHandler> configure) {}
public SocketsHttpHandlerBuilder ConfigureOptions(IConfigurationSection section) {}
public SocketsHttpHandlerBuilder ConfigureOptions(Action<SocketsHttpHandlerOptions> configure) {}
}
public static class HttpClientSocketHandlingExtensions
{
public static IHttpClientBuilder AddSocketsHttpHandler(this IHttpClientBuilder builder) {}
public static IHttpClientBuilder AddSocketsHttpHandler(this IHttpClientBuilder builder, Action<SocketsHttpHandlerBuilder> configure) {}
} API Usage{
"HttpClientSettings": {
"MyClient": {
"AllowAutoRedirect": true,
"UseCookies": true,
"ConnectTimeout": "00:00:05"
}
}
} public void ConfigureServices(IServiceCollection services)
{
IConfiguration configuration = ...;
services
.AddHttpClient("MyClient")
.AddSocketsHttpHandler(builder =>
{
builder.ConfigureOptions(configuration.GetSection("HttpClientSettings:MyClient"));
});
} Alternative DesignsNo response RisksNo response
|
@CarnaViire could you please triage this issue and specify the plan addressing it? Thanks! |
We are still in the middle of internal discussions about where exactly and in what way to address this issue. |
@CarnaViire, @rzikm I've added the two convenience methods to the proposal as discussed. |
I suggest ditching the |
I find it weird to have only those two available. Similarly to what stephentoub said above, those two expose only strict subset of what can be configured on the underlying SslStream, and any addition needs to be explicitly mirrored there. Wouldn't be better to have a callback for configuring |
@CarnaViire what is the status of this issue? still tracked for .NET 8.0? |
I am working on a PoC. I will finalize the API in the upcoming days.
Yes, we still plan it for 8.0 |
Interesting. This may be tangential but I did a similar thing over here where the user configures a named options class with some high level properties, and then when the http client is built, I apply these options during its consturction. These more simplistic options could be bound from config - but they span settings on the Handler, as well as on the HttpClient itself:- services.AddHttpClient("foo-v1")
.ConfigureOptions((options) =>
{
options.BaseAddress = $"http://foo-v1.localhost";
options.EnableBypassInvalidCertificate = true;
options.MaxResponseContentBufferSize = 2000;
options.Timeout = TimeSpan.FromMinutes(2);
options.Handlers.Add("status-handler");
}); /// <summary>
/// Encapsulates options that can be applied to an <see cref="HttpClient"/>
/// </summary>
public class HttpClientOptions
{
public bool EnableBypassInvalidCertificate { get; set; } = false;
public bool UseCookies { get; set; } = false;
/// <summary>
/// <see cref="HttpClient.BaseAddress"/>
/// </summary>
public string? BaseAddress { get; set; }
/// <summary>
/// <see cref="HttpClient.Timeout"/>
/// </summary>
public TimeSpan? Timeout { get; set; }
/// <summary>
/// <see cref="HttpClient.MaxResponseContentBufferSize"/>
/// </summary>
public long? MaxResponseContentBufferSize { get; set; }
public List<string> Handlers { get; set; } = new List<string>();
/// <summary>
/// Apply these options to an <see cref="HttpClient"/>
/// </summary>
/// <param name="httpClient"></param>
public virtual void Apply(HttpClient httpClient)
{
if (!string.IsNullOrWhiteSpace(BaseAddress))
{
httpClient.BaseAddress = new Uri(BaseAddress);
}
if (Timeout != null)
{
httpClient.Timeout = Timeout.Value;
}
if (MaxResponseContentBufferSize != null)
{
httpClient.MaxResponseContentBufferSize = MaxResponseContentBufferSize.Value;
}
}
} For example Where as the "Handlers" is an ordered list of handler names, that will be applied to the http client when it is built and these names correspond to a custom registry where the user can register handler factories with those names - and these facgtories will be invoked to create the handlers for the http client when it is built. The "name" of the http client being built is passed to these factories so they can bind to a json section or whatever to get the config to build the handler as specified for the particular named http client. This results in a config section like this: {
"HttpClients": {
"foo": {
"BaseAddress": "http://foo.localhost",
"Timeout": "00:00:30",
"Handlers": [
"Diagnostics",
"BasicAuth"
],
"BasicAuth": {
"Username": "MyUser",
"Password": "ThisShouldBeASecret"
}
}
} It looks like this issue (from the title) is about configuring one specific Handler only, but what about the story for configuring a complete |
Thanks for the insights @dazinator. This definitely has some interesting functionality, which can be explored in the future, would you mind creating a separate GH issue for that? |
I've spent some time experimenting with the APIs and consulted @stephentoub. We aligned on the following API form: // existing
public static class HttpClientBuilderExtensions
{
// new
public static IHttpClientBuilder UseSocketsHttpHandler(this IHttpClientBuilder builder,
Action<SocketsHttpHandler, IServiceProvider>? configureHandler = null) {}
public static IHttpClientBuilder UseSocketsHttpHandler(this IHttpClientBuilder builder,
Action<ISocketsHttpHandlerBuilder> configureBuilder) {}
}
// new
public interface ISocketsHttpHandlerBuilder
{
string Name { get; }
IServiceCollection Services { get; }
}
// new
public static class SocketsHttpHandlerBuilderExtensions
{
public static ISocketsHttpHandlerBuilder Configure(this ISocketsHttpHandlerBuilder builder,
Action<SocketsHttpHandler, IServiceProvider> configure) {}
public static ISocketsHttpHandlerBuilder Configure(this ISocketsHttpHandlerBuilder builder,
IConfigurationSection configurationSection, bool reloadOnHandlerCreation = false) {}
} The use of builder allows chaining configuration methods, and also allows for creation of additional extension configuration methods that could work directly with For example, while they most likely wouldn't meet the cut, we considered the following APIs (instead of SocketsHttpHandlerOptions proposed in the original post). We are not really fond of them either, because they just mimic what can already be done on SocketsHttpHandler directly, so this is mostly for illustrative purposes: public static class SocketsHttpHandlerBuilderExtensions
{
public static ISocketsHttpHandlerBuilder SetAllowAutoRedirect(this ISocketsHttpHandlerBuilder builder, bool allowAutoRedirect) {}
public static ISocketsHttpHandlerBuilder SetUseCookies(this ISocketsHttpHandlerBuilder builder, bool useCookies) {}
public static ISocketsHttpHandlerBuilder SetMaxConnectionsPerServer(this ISocketsHttpHandlerBuilder builder, int maxConnectionsPerServer) {}
public static ISocketsHttpHandlerBuilder SetConnectTimeout(this ISocketsHttpHandlerBuilder builder, TimeSpan connectTimeout) {}
public static ISocketsHttpHandlerBuilder SetPooledConnectionLifetime(this ISocketsHttpHandlerBuilder builder, TimeSpan pooledConnectionLifetime) {}
public static ISocketsHttpHandlerBuilder SetPooledConnectionIdleTimeout(this ISocketsHttpHandlerBuilder builder, TimeSpan pooledConnectionIdleTimeout) {}
public static ISocketsHttpHandlerBuilder SetKeepAlivePingDelay(this ISocketsHttpHandlerBuilder builder, TimeSpan keepAlivePingDelay) {}
public static ISocketsHttpHandlerBuilder SetKeepAlivePingTimeout(this ISocketsHttpHandlerBuilder builder, TimeSpan keepAlivePingTimeout) {}
} Usage examples: // uses SocketsHttpHandler as a primary handler
services.AddHttpClient("foo")
.UseSocketsHttpHandler();
// sets up properties on the handler directly
services.AddHttpClient("bar")
.UseSocketsHttpHandler((handler, _) =>
{
handler.PooledConnectionLifetime = TimeSpan.FromMinutes(2);
handler.UseCookies = false;
handler.MaxConnectionsPerServer = 1;
});
// loads properties from config and sets up properties via builder
services.AddHttpClient("baz")
.UseSocketsHttpHandler(builder =>
builder.Configure(configuration.GetSection("HttpClientSettings:baz"))
.Configure((handler, _) => handler.MaxConnectionsPerServer = 1)
);
// illustrative only: additional extension methods
services.AddHttpClient("qux")
.UseSocketsHttpHandler(builder =>
builder.Configure(configuration.GetSection("HttpClientSettings:qux"), reloadOnHandlerCreation: true)
.SetMaxConnectionsPerServer(1)
.DisableRemoteCertificateValidation()
); Re We've also agreed with @stephentoub that as we would not introduce a public |
@CarnaViire is there a reason the proposed api has to be specialised to sockets handler type only? It would be very useful to have a standard api to add and configure a handler in general.. and it looks on first glances that this proposal could be genericised? services.AddHttpClient("qux")
.UseHandler<SocketsHttpHandler>(builder =>
builder.Configure(configuration.GetSection("HttpClientSettings:qux:sockets"), reloadOnHandlerCreation: true) // this reload business is confusing but left it
.SetMaxConnectionsPerServer(1)
.DisableRemoteCertificateValidation()
); Where users (such as yours truly) would want to add and configure multiple other handlers they might not be in awe of having to create a specialised builder for each handler type. Thinking it would be good to have a similar experience to UseMiddleware on the server side.. you can add any middleware to the pipeline in a standard way. Middleware supports injecting IOptions though ofcourse and you've deviated away from Options so far.
Why would you want Note: if you did decide to support some kind of hot reload of the handlers in future, i.e by actively invalidating the pooled handlers built with the old config when the config reload token was signalled, such that new handlers would be created for the next request, then I think adding a param such as |
@dazinator thanks for confirming that the reload parameter is confusing... we decided to drop it altogether (for this release at least. It will be possible to add it later). As for its default value, we had a meeting offline and aligned that the reload being off by default is more preferable, as it being on adds a performance overhead, which we don't want to have by default. My understanding is also that we'd like to promote immutable deployment. @tekian would be able to explain in more details. If there would be enough ask in the future either for this, or for what you called "hot-reload", we will prioritize it accordingly. |
namespace Microsoft.Extensions.DependencyInjection;
public static partial class HttpClientBuilderExtensions
{
#if NET5_0_OR_GREATER
[UnsupportedOSPlatform("browser")]
public static IHttpClientBuilder UseSocketsHttpHandler(
this IHttpClientBuilder builder,
Action<SocketsHttpHandler, IServiceProvider>? configureHandler = null);
[UnsupportedOSPlatform("browser")]
public static IHttpClientBuilder UseSocketsHttpHandler(
this IHttpClientBuilder builder,
Action<ISocketsHttpHandlerBuilder> configureBuilder);
#endif
}
#if NET5_0_OR_GREATER
public interface ISocketsHttpHandlerBuilder
{
string Name { get; }
IServiceCollection Services { get; }
}
public static class SocketsHttpHandlerBuilderExtensions
{
[UnsupportedOSPlatform("browser")]
public static ISocketsHttpHandlerBuilder Configure(
this ISocketsHttpHandlerBuilder builder,
Action<SocketsHttpHandler, IServiceProvider> configure);
[UnsupportedOSPlatform("browser")]
public static ISocketsHttpHandlerBuilder Configure(
this ISocketsHttpHandlerBuilder builder,
IConfiguration configuration);
}
#endif |
Original issue by @tekian
Background and motivation
The purpose of the new API, represented by the
HttpClientSocketHandlingExtensions
,SocketsHttpHandlerBuilder
andSocketsHttpHandlerOptions
classes, is to provide a more convenient and fluent way to configureSocketsHttpHandler
instances for namedHttpClient
instances in applications that use dependency injection and theIHttpClientFactory
.API Proposal
API Usage
Additionally, we could consider also adding following convenience methods:
....either in this form or as an extension methods.
Alternative Designs
No response
Risks
No response
Background and motivation
The purpose of the new API, represented by the
ISocketsHttpHandlerBuilder
,SocketsHttpHandlerBuilderExtensions
andUseSocketsHttpHandler
, is to provide a more convenient and fluent way to configureSocketsHttpHandler
instances for namedHttpClient
instances in applications that use dependency injection and theIHttpClientFactory
. One of the main asks is to be able to configureSocketsHttpHandler
via a configuration file.This is a convenience API for some of the most widely used basic scenarios investigated by dotnet/extensions team.
While it is possible to achieve the same result by existing methods, the API significantly simplifies it in the common scenarios.
Note: this API is .NET 5+ only.
API Proposal
API Usage
Illustrative only: additional extension methods
The text was updated successfully, but these errors were encountered: