File: DependencyInjection\SocketsHttpHandlerBuilderExtensions.cs
Web Access
Project: src\src\libraries\Microsoft.Extensions.Http\src\Microsoft.Extensions.Http.csproj (Microsoft.Extensions.Http)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
#if NET
using System;
using System.Net;
using System.Net.Http;
using System.Runtime.Versioning;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Http;
 
namespace Microsoft.Extensions.DependencyInjection
{
    /// <summary>
    /// Extension methods to configure <see cref="SocketsHttpHandler"/> for a named
    /// <see cref="System.Net.Http.HttpClient"/> instances returned by <see cref="IHttpClientFactory"/>.
    /// </summary>
    public static class SocketsHttpHandlerBuilderExtensions
    {
        /// <summary>
        /// Adds a delegate that will be used to configure the primary <see cref="SocketsHttpHandler"/> for a
        /// named <see cref="HttpClient"/>.
        /// </summary>
        /// <param name="builder">The <see cref="ISocketsHttpHandlerBuilder"/>.</param>
        /// <param name="configure">A delegate that is used to modify a <see cref="SocketsHttpHandler"/>.</param>
        /// <returns>An <see cref="ISocketsHttpHandlerBuilder"/> that can be used to configure the handler.</returns>
        [UnsupportedOSPlatform("browser")]
        public static ISocketsHttpHandlerBuilder Configure(this ISocketsHttpHandlerBuilder builder, Action<SocketsHttpHandler, IServiceProvider> configure)
        {
            builder.Services.Configure<HttpClientFactoryOptions>(builder.Name, options =>
            {
                options.HttpMessageHandlerBuilderActions.Add(b =>
                {
                    if (b.PrimaryHandler is not SocketsHttpHandler socketsHttpHandler)
                    {
                        string message = SR.Format(SR.SocketsHttpHandlerBuilder_PrimaryHandlerIsInvalid, nameof(b.PrimaryHandler), typeof(SocketsHttpHandler).FullName, Environment.NewLine, b.PrimaryHandler?.ToString() ?? "(null)");
                        throw new InvalidOperationException(message);
                    }
 
                    configure(socketsHttpHandler, b.Services);
                });
            });
            return builder;
        }
 
        /// <summary>
        /// Uses <see cref="IConfiguration"/> to configure the primary <see cref="SocketsHttpHandler"/> for a
        /// named <see cref="HttpClient"/>.
        /// </summary>
        /// <param name="builder">The <see cref="ISocketsHttpHandlerBuilder"/>.</param>
        /// <param name="configuration">Configuration containing properties of <see cref="SocketsHttpHandler"/>.</param>
        /// <returns>An <see cref="ISocketsHttpHandlerBuilder"/> that can be used to configure the handler.</returns>
        /// <remarks>
        /// <para>
        /// Only simple (of type `bool`, `int`, <see cref="Enum"/> or <see cref="TimeSpan"/>) properties of <see cref="SocketsHttpHandler"/> will be parsed.
        /// </para>
        /// <para>
        /// All unmatched properties in <see cref="IConfiguration"/> will be ignored.
        /// </para>
        /// </remarks>
        [UnsupportedOSPlatform("browser")]
        public static ISocketsHttpHandlerBuilder Configure(this ISocketsHttpHandlerBuilder builder, IConfiguration configuration)
        {
            SocketsHttpHandlerConfiguration parsedConfig = ParseSocketsHttpHandlerConfiguration(configuration);
            return Configure(builder, (handler, _) => FillFromConfig(handler, parsedConfig));
        }
 
        [UnsupportedOSPlatform("browser")]
        private static void FillFromConfig(SocketsHttpHandler handler, in SocketsHttpHandlerConfiguration config)
        {
            if (config.PooledConnectionIdleTimeout is not null)
            {
                handler.PooledConnectionIdleTimeout = config.PooledConnectionIdleTimeout.Value;
            }
 
            if (config.PooledConnectionLifetime is not null)
            {
                handler.PooledConnectionLifetime = config.PooledConnectionLifetime.Value;
            }
 
            if (config.PreAuthenticate is not null)
            {
                handler.PreAuthenticate = config.PreAuthenticate.Value;
            }
 
            if (config.ResponseDrainTimeout is not null)
            {
                handler.ResponseDrainTimeout = config.ResponseDrainTimeout.Value;
            }
 
            if (config.UseCookies is not null)
            {
                handler.UseCookies = config.UseCookies.Value;
            }
 
            if (config.UseProxy is not null)
            {
                handler.UseProxy = config.UseProxy.Value;
            }
 
            if (config.EnableMultipleHttp2Connections is not null)
            {
                handler.EnableMultipleHttp2Connections = config.EnableMultipleHttp2Connections.Value;
            }
 
            if (config.MaxResponseHeadersLength is not null)
            {
                handler.MaxResponseHeadersLength = config.MaxResponseHeadersLength.Value;
            }
 
            if (config.MaxResponseDrainSize is not null)
            {
                handler.MaxResponseDrainSize = config.MaxResponseDrainSize.Value;
            }
 
            if (config.MaxConnectionsPerServer is not null)
            {
                handler.MaxConnectionsPerServer = config.MaxConnectionsPerServer.Value;
            }
 
            if (config.MaxAutomaticRedirections is not null)
            {
                handler.MaxAutomaticRedirections = config.MaxAutomaticRedirections.Value;
            }
 
            if (config.InitialHttp2StreamWindowSize is not null)
            {
                handler.InitialHttp2StreamWindowSize = config.InitialHttp2StreamWindowSize.Value;
            }
 
            if (config.AllowAutoRedirect is not null)
            {
                handler.AllowAutoRedirect = config.AllowAutoRedirect.Value;
            }
 
            if (config.AutomaticDecompression is not null)
            {
                handler.AutomaticDecompression = config.AutomaticDecompression.Value;
            }
 
            if (config.ConnectTimeout is not null)
            {
                handler.ConnectTimeout = config.ConnectTimeout.Value;
            }
 
            if (config.Expect100ContinueTimeout is not null)
            {
                handler.Expect100ContinueTimeout = config.Expect100ContinueTimeout.Value;
            }
 
            if (config.KeepAlivePingDelay is not null)
            {
                handler.KeepAlivePingDelay = config.KeepAlivePingDelay.Value;
            }
 
            if (config.KeepAlivePingTimeout is not null)
            {
                handler.KeepAlivePingTimeout = config.KeepAlivePingTimeout.Value;
            }
 
            if (config.KeepAlivePingPolicy is not null)
            {
                handler.KeepAlivePingPolicy = config.KeepAlivePingPolicy.Value;
            }
        }
 
        private readonly record struct SocketsHttpHandlerConfiguration
        {
            public TimeSpan? PooledConnectionIdleTimeout { get; init; }
            public TimeSpan? PooledConnectionLifetime { get; init; }
            public bool? PreAuthenticate { get; init; }
            public TimeSpan? ResponseDrainTimeout { get; init; }
            public bool? UseCookies { get; init; }
            public bool? UseProxy { get; init; }
            public bool? EnableMultipleHttp2Connections { get; init; }
            public int? MaxResponseHeadersLength { get; init; }
            public int? MaxResponseDrainSize { get; init; }
            public int? MaxConnectionsPerServer { get; init; }
            public int? MaxAutomaticRedirections { get; init; }
            public int? InitialHttp2StreamWindowSize { get; init; }
            public bool? AllowAutoRedirect { get; init; }
            public DecompressionMethods? AutomaticDecompression { get; init; }
            public TimeSpan? ConnectTimeout { get; init; }
            public TimeSpan? Expect100ContinueTimeout { get; init; }
            public TimeSpan? KeepAlivePingDelay { get; init; }
            public TimeSpan? KeepAlivePingTimeout { get; init; }
            public HttpKeepAlivePingPolicy? KeepAlivePingPolicy { get; init; }
        }
 
        [UnsupportedOSPlatform("browser")]
        private static SocketsHttpHandlerConfiguration ParseSocketsHttpHandlerConfiguration(IConfiguration config)
        {
            return new SocketsHttpHandlerConfiguration()
            {
                PooledConnectionIdleTimeout = ParseTimeSpan(config[nameof(SocketsHttpHandler.PooledConnectionIdleTimeout)]),
                PooledConnectionLifetime = ParseTimeSpan(config[nameof(SocketsHttpHandler.PooledConnectionLifetime)]),
                PreAuthenticate = ParseBool(config[nameof(SocketsHttpHandler.PreAuthenticate)]),
                ResponseDrainTimeout = ParseTimeSpan(config[nameof(SocketsHttpHandler.ResponseDrainTimeout)]),
                UseCookies = ParseBool(config[nameof(SocketsHttpHandler.UseCookies)]),
                UseProxy = ParseBool(config[nameof(SocketsHttpHandler.UseProxy)]),
                EnableMultipleHttp2Connections = ParseBool(config[nameof(SocketsHttpHandler.EnableMultipleHttp2Connections)]),
                MaxResponseHeadersLength = ParseInt(config[nameof(SocketsHttpHandler.MaxResponseHeadersLength)]),
                MaxResponseDrainSize = ParseInt(config[nameof(SocketsHttpHandler.MaxResponseDrainSize)]),
                MaxConnectionsPerServer = ParseInt(config[nameof(SocketsHttpHandler.MaxConnectionsPerServer)]),
                MaxAutomaticRedirections = ParseInt(config[nameof(SocketsHttpHandler.MaxAutomaticRedirections)]),
                InitialHttp2StreamWindowSize = ParseInt(config[nameof(SocketsHttpHandler.InitialHttp2StreamWindowSize)]),
                AllowAutoRedirect = ParseBool(config[nameof(SocketsHttpHandler.AllowAutoRedirect)]),
                AutomaticDecompression = ParseEnum<DecompressionMethods>(config[nameof(SocketsHttpHandler.AutomaticDecompression)]),
                ConnectTimeout = ParseTimeSpan(config[nameof(SocketsHttpHandler.ConnectTimeout)]),
                Expect100ContinueTimeout = ParseTimeSpan(config[nameof(SocketsHttpHandler.Expect100ContinueTimeout)]),
                KeepAlivePingDelay = ParseTimeSpan(config[nameof(SocketsHttpHandler.KeepAlivePingDelay)]),
                KeepAlivePingTimeout = ParseTimeSpan(config[nameof(SocketsHttpHandler.KeepAlivePingTimeout)]),
                KeepAlivePingPolicy = ParseEnum<HttpKeepAlivePingPolicy>(config[nameof(SocketsHttpHandler.KeepAlivePingPolicy)])
            };
        }
 
        private static TEnum? ParseEnum<TEnum>(string? enumString) where TEnum : struct
            => Enum.TryParse<TEnum>(enumString, ignoreCase: true, out var result) ? result : null;
 
        private static bool? ParseBool(string? boolString) => bool.TryParse(boolString, out var result) ? result : null;
        private static int? ParseInt(string? intString) => int.TryParse(intString, out var result) ? result : null;
        private static TimeSpan? ParseTimeSpan(string? timeSpanString) => TimeSpan.TryParse(timeSpanString, out var result) ? result : null;
    }
}
#endif