Skip to content

[API Proposal] Configure SocketsHttpHandler for HttpClientFactory with IConfiguration #84075

Closed
@tekian

Description

@tekian
Original issue by @tekian

Background and motivation

The purpose of the new API, represented by the HttpClientSocketHandlingExtensions, SocketsHttpHandlerBuilder and SocketsHttpHandlerOptions classes, is to provide a more convenient and fluent way to configure SocketsHttpHandler instances for named HttpClient instances in applications that use dependency injection and the IHttpClientFactory.

API Proposal

public 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"));
        });
}

Additionally, we could consider also adding following convenience methods:

public class SocketsHttpHandlerBuilder
{
       public SocketsHttpHandlerBuilder ConfigureClientCertificate(Func<IServiceProvider, X509Certificate2> clientCertificate) { ...}
       public SocketsHttpHandlerBuilder DisableRemoteCertificateValidation() { ... }
}

....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 and UseSocketsHttpHandler, is to provide a more convenient and fluent way to configure SocketsHttpHandler instances for named HttpClient instances in applications that use dependency injection and the IHttpClientFactory. One of the main asks is to be able to configure SocketsHttpHandler 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

namespace Microsoft.Extensions.DependencyInjection;

// existing
public static class HttpClientBuilderExtensions
{
#if NET5_0_OR_GREATER

    // new
    [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

    // existing (+2 overloads)
    // public static IHttpClientBuilder ConfigurePrimaryHttpMessageHandler(this IHttpClientBuilder builder, Func<HttpMessageHandler> configureHandler) {}
}

#if NET5_0_OR_GREATER

// new
public interface ISocketsHttpHandlerBuilder
{
    string Name { get; }
    IServiceCollection Services { get; }
}

// new
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,
        IConfigurationSection configurationSection) {}
}

#endif

API Usage

// 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 file and chains setting up SslOptions via builder
services.AddHttpClient("baz")
    .UseSocketsHttpHandler(builder =>
        builder.Configure(configuration.GetSection("HttpClientSettings:baz"))
               .Configure((handler, _) => handler.SslOptions = new SslClientAuthenticationOptions
               {
                   RemoteCertificateValidationCallback = delegate { return true; },
               })
    );
{
  "HttpClientSettings": {
    "baz": {
      "AllowAutoRedirect": true,
      "UseCookies": false,
      "ConnectTimeout": "00:00:05"
    }
  }
}

Illustrative only: additional extension methods

services.AddHttpClient("qux")
    .UseSocketsHttpHandler(builder =>
        builder.Configure(configuration.GetSection("HttpClientSettings:qux"))
            .SetMaxConnectionsPerServer(1)
            .DisableRemoteCertificateValidation()
    );

// e.g. added by user or a 3rd party lib
public static class MyFluentSocketsHttpHandlerBuilderExtensions
{
    public static ISocketsHttpHandlerBuilder DisableRemoteCertificateValidation(this ISocketsHttpHandlerBuilder builder, bool allowAutoRedirect) {}
    public static ISocketsHttpHandlerBuilder ConfigureClientCertificate(Func<IServiceProvider, X509Certificate> clientCertificate) {}

    // ...

    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) {}

    // ...
}

Metadata

Metadata

Assignees

Labels

api-approvedAPI was approved in API review, it can be implementedarea-Extensions-HttpClientFactoryblockingMarks issues that we want to fast track in order to unblock other important work

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions