File: DefaultHttpMessageHandlerBuilder.cs
Web Access
Project: src\runtime\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.

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Net.Http;
using System.Threading;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;

namespace Microsoft.Extensions.Http
{
    internal sealed class DefaultHttpMessageHandlerBuilder : HttpMessageHandlerBuilder
    {
        private string? _name;

        public DefaultHttpMessageHandlerBuilder(IServiceProvider services)
        {
            Services = services;
        }

        [DisallowNull]
        public override string? Name
        {
            get => _name;
            set
            {
                ArgumentNullException.ThrowIfNull(value);
                _name = value;
            }
        }

        public override HttpMessageHandler PrimaryHandler
        {
            get => field ??= CreatePrimaryHandler();
            set => field = value;
        }

        public override IList<DelegatingHandler> AdditionalHandlers { get; } = new List<DelegatingHandler>();

        public override IServiceProvider Services { get; }

        public override HttpMessageHandler Build()
        {
            if (PrimaryHandler == null)
            {
                string message = SR.Format(SR.HttpMessageHandlerBuilder_PrimaryHandlerIsNull, nameof(PrimaryHandler));
                throw new InvalidOperationException(message);
            }

            return CreateHandlerPipeline(PrimaryHandler, AdditionalHandlers);
        }

#pragma warning disable CA1822, CA1859 // Mark members as static, Use concrete types when possible for improved performance
        private HttpMessageHandler CreatePrimaryHandler()
#pragma warning restore CA1822, CA1859
        {
#if NET
            // On platforms where SocketsHttpHandler is supported, HttpClientHandler is a thin wrapper
            // around it. By using SocketsHttpHandler directly, we can avoid the overhead of the wrapper,
            // but more importantly, we can configure it to limit the lifetime of its pooled connections
            // to match the requested lifetime of the handler itself. That way, if/when someone holds on
            // to a resulting HttpClient for a prolonged period of time, it'll still benefit from connection
            // recycling, and without needing to tear down and reconstitute the rest of the handler pipeline.
            if (SocketsHttpHandler.IsSupported)
            {
                SocketsHttpHandler handler = new();

                if (Services.GetService<IOptionsMonitor<HttpClientFactoryOptions>>() is IOptionsMonitor<HttpClientFactoryOptions> optionsMonitor)
                {
                    TimeSpan lifetime = optionsMonitor.Get(_name).HandlerLifetime;
                    if (lifetime >= TimeSpan.Zero)
                    {
                        handler.PooledConnectionLifetime = lifetime;
                    }
                }

                return handler;
            }
#endif

            return new HttpClientHandler();
        }
    }
}