< Summary

Line coverage
0%
Covered lines: 0
Uncovered lines: 327
Coverable lines: 327
Total lines: 764
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 118
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Collections.Generic;
 5using System.Diagnostics;
 6using System.Diagnostics.CodeAnalysis;
 7using System.Diagnostics.Metrics;
 8using System.IO;
 9using System.Net.Http.Metrics;
 10using System.Net.Security;
 11using System.Runtime.ExceptionServices;
 12using System.Runtime.Versioning;
 13using System.Text;
 14using System.Threading;
 15using System.Threading.Tasks;
 16
 17namespace System.Net.Http
 18{
 19    [UnsupportedOSPlatform("browser")]
 20    public sealed class SocketsHttpHandler : HttpMessageHandler
 21    {
 022        private readonly HttpConnectionSettings _settings = new HttpConnectionSettings();
 23        private HttpMessageHandlerStage? _handler;
 24        private Task<HttpMessageHandlerStage>? _handlerChainSetupTask;
 25        private Func<HttpConnectionSettings, HttpMessageHandlerStage, HttpMessageHandlerStage>? _decompressionHandlerFac
 26        private bool _disposed;
 27
 28        // Accessed via UnsafeAccessor from HttpWebRequest.
 029        internal HttpConnectionSettings Settings => _settings;
 30
 31        private void CheckDisposedOrStarted()
 032        {
 033            ObjectDisposedException.ThrowIf(_disposed, this);
 034            if (_handler != null)
 035            {
 036                throw new InvalidOperationException(SR.net_http_operation_started);
 37            }
 038        }
 39
 40        /// <summary>
 41        /// Gets a value that indicates whether the handler is supported on the current platform.
 42        /// </summary>
 43        [UnsupportedOSPlatformGuard("browser")]
 044        public static bool IsSupported => !OperatingSystem.IsBrowser() && !OperatingSystem.IsWasi();
 45
 46        public bool UseCookies
 47        {
 048            get => _settings._useCookies;
 49            set
 050            {
 051                CheckDisposedOrStarted();
 052                _settings._useCookies = value;
 053            }
 54        }
 55
 56        [AllowNull]
 57        public CookieContainer CookieContainer
 58        {
 059            get => _settings._cookieContainer ??= new CookieContainer();
 60            set
 061            {
 062                CheckDisposedOrStarted();
 063                _settings._cookieContainer = value;
 064            }
 65        }
 66
 67        public DecompressionMethods AutomaticDecompression
 68        {
 069            get => _settings._automaticDecompression;
 70            set
 071            {
 072                CheckDisposedOrStarted();
 073                EnsureDecompressionHandlerFactory();
 074                _settings._automaticDecompression = value;
 075            }
 76        }
 77
 78        public bool UseProxy
 79        {
 080            get => _settings._useProxy;
 81            set
 082            {
 083                CheckDisposedOrStarted();
 084                _settings._useProxy = value;
 085            }
 86        }
 87
 88        public IWebProxy? Proxy
 89        {
 090            get => _settings._proxy;
 91            set
 092            {
 093                CheckDisposedOrStarted();
 094                _settings._proxy = value;
 095            }
 96        }
 97
 98        public ICredentials? DefaultProxyCredentials
 99        {
 0100            get => _settings._defaultProxyCredentials;
 101            set
 0102            {
 0103                CheckDisposedOrStarted();
 0104                _settings._defaultProxyCredentials = value;
 0105            }
 106        }
 107
 108        public bool PreAuthenticate
 109        {
 0110            get => _settings._preAuthenticate;
 111            set
 0112            {
 0113                CheckDisposedOrStarted();
 0114                _settings._preAuthenticate = value;
 0115            }
 116        }
 117
 118        public ICredentials? Credentials
 119        {
 0120            get => _settings._credentials;
 121            set
 0122            {
 0123                CheckDisposedOrStarted();
 0124                _settings._credentials = value;
 0125            }
 126        }
 127
 128        public bool AllowAutoRedirect
 129        {
 0130            get => _settings._allowAutoRedirect;
 131            set
 0132            {
 0133                CheckDisposedOrStarted();
 0134                _settings._allowAutoRedirect = value;
 0135            }
 136        }
 137
 138        public int MaxAutomaticRedirections
 139        {
 0140            get => _settings._maxAutomaticRedirections;
 141            set
 0142            {
 0143                ArgumentOutOfRangeException.ThrowIfNegativeOrZero(value);
 144
 0145                CheckDisposedOrStarted();
 0146                _settings._maxAutomaticRedirections = value;
 0147            }
 148        }
 149
 150        public int MaxConnectionsPerServer
 151        {
 0152            get => _settings._maxConnectionsPerServer;
 153            set
 0154            {
 0155                ArgumentOutOfRangeException.ThrowIfNegativeOrZero(value);
 156
 0157                CheckDisposedOrStarted();
 0158                _settings._maxConnectionsPerServer = value;
 0159            }
 160        }
 161
 162        public int MaxResponseDrainSize
 163        {
 0164            get => _settings._maxResponseDrainSize;
 165            set
 0166            {
 0167                ArgumentOutOfRangeException.ThrowIfNegative(value);
 168
 0169                CheckDisposedOrStarted();
 0170                _settings._maxResponseDrainSize = value;
 0171            }
 172        }
 173
 174        public TimeSpan ResponseDrainTimeout
 175        {
 0176            get => _settings._maxResponseDrainTime;
 177            set
 0178            {
 0179                if ((value < TimeSpan.Zero && value != Timeout.InfiniteTimeSpan) ||
 0180                    (value.TotalMilliseconds > int.MaxValue))
 0181                {
 0182                    throw new ArgumentOutOfRangeException(nameof(value));
 183                }
 184
 0185                CheckDisposedOrStarted();
 0186                _settings._maxResponseDrainTime = value;
 0187            }
 188        }
 189
 190        public int MaxResponseHeadersLength
 191        {
 0192            get => _settings._maxResponseHeadersLength;
 193            set
 0194            {
 0195                ArgumentOutOfRangeException.ThrowIfNegativeOrZero(value);
 196
 0197                CheckDisposedOrStarted();
 0198                _settings._maxResponseHeadersLength = value;
 0199            }
 200        }
 201
 202        [AllowNull]
 203        public SslClientAuthenticationOptions SslOptions
 204        {
 0205            get => _settings._sslOptions ??= new SslClientAuthenticationOptions();
 206            set
 0207            {
 0208                CheckDisposedOrStarted();
 0209                _settings._sslOptions = value;
 0210            }
 211        }
 212
 213        public TimeSpan PooledConnectionLifetime
 214        {
 0215            get => _settings._pooledConnectionLifetime;
 216            set
 0217            {
 0218                if (value < TimeSpan.Zero && value != Timeout.InfiniteTimeSpan)
 0219                {
 0220                    throw new ArgumentOutOfRangeException(nameof(value));
 221                }
 222
 0223                CheckDisposedOrStarted();
 0224                _settings._pooledConnectionLifetime = value;
 0225            }
 226        }
 227
 228        public TimeSpan PooledConnectionIdleTimeout
 229        {
 0230            get => _settings._pooledConnectionIdleTimeout;
 231            set
 0232            {
 0233                if (value < TimeSpan.Zero && value != Timeout.InfiniteTimeSpan)
 0234                {
 0235                    throw new ArgumentOutOfRangeException(nameof(value));
 236                }
 237
 0238                CheckDisposedOrStarted();
 0239                _settings._pooledConnectionIdleTimeout = value;
 0240            }
 241        }
 242
 243        public TimeSpan ConnectTimeout
 244        {
 0245            get => _settings._connectTimeout;
 246            set
 0247            {
 0248                if ((value <= TimeSpan.Zero && value != Timeout.InfiniteTimeSpan) ||
 0249                    (value.TotalMilliseconds > int.MaxValue))
 0250                {
 0251                    throw new ArgumentOutOfRangeException(nameof(value));
 252                }
 253
 0254                CheckDisposedOrStarted();
 0255                _settings._connectTimeout = value;
 0256            }
 257        }
 258
 259        public TimeSpan Expect100ContinueTimeout
 260        {
 0261            get => _settings._expect100ContinueTimeout;
 262            set
 0263            {
 0264                if ((value < TimeSpan.Zero && value != Timeout.InfiniteTimeSpan) ||
 0265                    (value.TotalMilliseconds > int.MaxValue))
 0266                {
 0267                    throw new ArgumentOutOfRangeException(nameof(value));
 268                }
 269
 0270                CheckDisposedOrStarted();
 0271                _settings._expect100ContinueTimeout = value;
 0272            }
 273        }
 274
 275        /// <summary>
 276        /// Defines the initial HTTP2 stream receive window size for all connections opened by the this <see cref="Socke
 277        /// </summary>
 278        /// <remarks>
 279        /// Larger the values may lead to faster download speed, but potentially higher memory footprint.
 280        /// The property must be set to a value between 65535 and the configured maximum window size, which is 16777216 
 281        /// </remarks>
 282        public int InitialHttp2StreamWindowSize
 283        {
 0284            get => _settings._initialHttp2StreamWindowSize;
 285            set
 0286            {
 0287                if (value < HttpHandlerDefaults.DefaultInitialHttp2StreamWindowSize || value > GlobalHttpSettings.Socket
 0288                {
 0289                    string message = SR.Format(
 0290                        SR.net_http_http2_invalidinitialstreamwindowsize,
 0291                        HttpHandlerDefaults.DefaultInitialHttp2StreamWindowSize,
 0292                        GlobalHttpSettings.SocketsHttpHandler.MaxHttp2StreamWindowSize);
 293
 0294                    throw new ArgumentOutOfRangeException(nameof(InitialHttp2StreamWindowSize), message);
 295                }
 0296                CheckDisposedOrStarted();
 0297                _settings._initialHttp2StreamWindowSize = value;
 0298            }
 299        }
 300
 301        /// <summary>
 302        /// Gets or sets the maximum number of concurrent HTTP/2 streams a new connection may use before it observes
 303        /// the server's <c>SETTINGS_MAX_CONCURRENT_STREAMS</c> value.
 304        /// </summary>
 305        /// <remarks>
 306        /// <para>
 307        /// HTTP/2 lets the client start sending requests as soon as the connection is established, before the server
 308        /// has advertised how many concurrent streams it accepts. Until that <c>SETTINGS</c> frame arrives, the client
 309        /// optimistically allows up to this many streams, after which the server's value takes over.
 310        /// </para>
 311        /// <para>
 312        /// The default suits virtually all deployments and most users never need to change it. It is intended for the
 313        /// small subset of deployments where the server is known ahead of time to use a lower concurrency limit, and
 314        /// starting a connection with a matching value avoids the brief burst of requests above that limit.
 315        /// </para>
 316        /// <para>
 317        /// If an earlier connection to the same host advertised a lower limit,
 318        /// that lower value is used instead for new connections to that host.
 319        /// </para>
 320        /// <para>
 321        /// The value must be greater than or equal to 1. Defaults to 100.
 322        /// </para>
 323        /// </remarks>
 324        /// <exception cref="ArgumentOutOfRangeException">The value is zero or negative.</exception>
 325        public int InitialHttp2MaxConcurrentStreams
 326        {
 0327            get => _settings._initialHttp2MaxConcurrentStreams;
 328            set
 0329            {
 0330                ArgumentOutOfRangeException.ThrowIfNegativeOrZero(value);
 331
 0332                CheckDisposedOrStarted();
 0333                _settings._initialHttp2MaxConcurrentStreams = value;
 0334            }
 335        }
 336
 337        /// <summary>
 338        /// Gets or sets the keep alive ping delay. The client will send a keep alive ping to the server if it
 339        /// doesn't receive any frames on a connection for this period of time. This property is used together with
 340        /// <see cref="SocketsHttpHandler.KeepAlivePingTimeout"/> to close broken connections.
 341        /// <para>
 342        /// Delay value must be greater than or equal to 1 second. Set to <see cref="Timeout.InfiniteTimeSpan"/> to
 343        /// disable the keep alive ping.
 344        /// Defaults to <see cref="Timeout.InfiniteTimeSpan"/>.
 345        /// </para>
 346        /// </summary>
 347        public TimeSpan KeepAlivePingDelay
 348        {
 0349            get => _settings._keepAlivePingDelay;
 350            set
 0351            {
 0352                if (value.Ticks < TimeSpan.TicksPerSecond && value != Timeout.InfiniteTimeSpan)
 0353                {
 0354                    throw new ArgumentOutOfRangeException(nameof(value), value, SR.Format(SR.net_http_value_must_be_grea
 355                }
 356
 0357                CheckDisposedOrStarted();
 0358                _settings._keepAlivePingDelay = value;
 0359            }
 360        }
 361
 362        /// <summary>
 363        /// Gets or sets the keep alive ping timeout. Keep alive pings are sent when a period of inactivity exceeds
 364        /// the configured <see cref="KeepAlivePingDelay"/> value. The client will close the connection if it
 365        /// doesn't receive any frames within the timeout.
 366        /// <para>
 367        /// Timeout must be greater than or equal to 1 second. Set to <see cref="Timeout.InfiniteTimeSpan"/> to
 368        /// disable the keep alive ping timeout.
 369        /// Defaults to 20 seconds.
 370        /// </para>
 371        /// </summary>
 372        public TimeSpan KeepAlivePingTimeout
 373        {
 0374            get => _settings._keepAlivePingTimeout;
 375            set
 0376            {
 0377                if (value.Ticks < TimeSpan.TicksPerSecond && value != Timeout.InfiniteTimeSpan)
 0378                {
 0379                    throw new ArgumentOutOfRangeException(nameof(value), value, SR.Format(SR.net_http_value_must_be_grea
 380                }
 381
 0382                CheckDisposedOrStarted();
 0383                _settings._keepAlivePingTimeout = value;
 0384            }
 385        }
 386
 387        /// <summary>
 388        /// Gets or sets the keep alive ping behaviour. Keep alive pings are sent when a period of inactivity exceeds
 389        /// the configured <see cref="KeepAlivePingDelay"/> value.
 390        /// </summary>
 391        public HttpKeepAlivePingPolicy KeepAlivePingPolicy
 392        {
 0393            get => _settings._keepAlivePingPolicy;
 394            set
 0395            {
 0396                CheckDisposedOrStarted();
 0397                _settings._keepAlivePingPolicy = value;
 0398            }
 399        }
 400
 401        /// <summary>
 402        /// Gets or sets a value that indicates whether additional HTTP/2 connections can be established to the same ser
 403        /// </summary>
 404        /// <remarks>
 405        /// Enabling multiple connections to the same server explicitly goes against <see href="https://www.rfc-editor.o
 406        /// </remarks>
 407        public bool EnableMultipleHttp2Connections
 408        {
 0409            get => _settings._enableMultipleHttp2Connections;
 410            set
 0411            {
 0412                CheckDisposedOrStarted();
 413
 0414                _settings._enableMultipleHttp2Connections = value;
 0415            }
 416        }
 417
 418        /// <summary>
 419        /// Gets or sets a value that indicates whether additional HTTP/3 connections can be established to the same ser
 420        /// </summary>
 421        /// <remarks>
 422        /// Enabling multiple connections to the same server explicitly goes against <see href="https://www.rfc-editor.o
 423        /// </remarks>
 424        public bool EnableMultipleHttp3Connections
 425        {
 0426            get => _settings._enableMultipleHttp3Connections;
 427            set
 0428            {
 0429                CheckDisposedOrStarted();
 430
 0431                _settings._enableMultipleHttp3Connections = value;
 0432            }
 433        }
 434
 435        internal const bool SupportsAutomaticDecompression = true;
 436        internal const bool SupportsProxy = true;
 437        internal const bool SupportsRedirectConfiguration = true;
 438
 439        /// <summary>
 440        /// When non-null, a custom callback used to open new connections.
 441        /// </summary>
 442        public Func<SocketsHttpConnectionContext, CancellationToken, ValueTask<Stream>>? ConnectCallback
 443        {
 0444            get => _settings._connectCallback;
 445            set
 0446            {
 0447                CheckDisposedOrStarted();
 0448                _settings._connectCallback = value;
 0449            }
 450        }
 451
 452        /// <summary>
 453        /// Gets or sets a custom callback that provides access to the plaintext HTTP protocol stream.
 454        /// </summary>
 455        public Func<SocketsHttpPlaintextStreamFilterContext, CancellationToken, ValueTask<Stream>>? PlaintextStreamFilte
 456        {
 0457            get => _settings._plaintextStreamFilter;
 458            set
 0459            {
 0460                CheckDisposedOrStarted();
 0461                _settings._plaintextStreamFilter = value;
 0462            }
 463        }
 464
 465        /// <summary>
 466        /// Gets or sets a callback that decides whether a pooled connection should be evicted.
 467        /// </summary>
 468        /// <remarks>
 469        /// <para>
 470        /// When set, the callback is invoked for pooled connections with a
 471        /// <see cref="SocketsHttpConnectionEvictionContext"/> describing the connection. Returning <see langword="true"
 472        /// marks the connection for eviction: it will not be used to serve new requests and is retired once it
 473        /// becomes idle (an in-flight request on the connection is allowed to complete first).
 474        /// </para>
 475        /// <para>
 476        /// The callback is not guaranteed to run for every request, and it may run concurrently with the connection
 477        /// serving requests as well as concurrently for different connections. Because it is asynchronous, a caller
 478        /// may perform work such as a name resolution inside it; however, it is invoked for each pooled connection, so
 479        /// keeping it inexpensive (for example, consulting a cached resolution result) is recommended. The supplied
 480        /// <see cref="CancellationToken"/> is canceled if the connection is disposed while the callback is running.
 481        /// </para>
 482        /// <para>
 483        /// This callback complements <see cref="PooledConnectionLifetime"/>. Because a caller can use it to evict
 484        /// connections in response to their own DNS resolution, it makes it possible to set
 485        /// <see cref="PooledConnectionLifetime"/> to <see cref="Timeout.InfiniteTimeSpan"/> and
 486        /// retain otherwise healthy connections rather than recycling them purely to observe address changes.
 487        /// </para>
 488        /// </remarks>
 489        [Experimental(Experimentals.SocketsHttpHandlerExperimentalDiagId, UrlFormat = Experimentals.SharedUrlFormat)]
 490        public Func<SocketsHttpConnectionEvictionContext, CancellationToken, Task<bool>>? ShouldEvictConnection
 491        {
 0492            get => _settings._shouldEvictConnection;
 493            set
 0494            {
 0495                CheckDisposedOrStarted();
 0496                _settings._shouldEvictConnection = value;
 0497            }
 498        }
 499
 500        /// <summary>
 501        /// Gets a writable dictionary (that is, a map) of custom properties for the HttpClient requests. The dictionary
 502        /// </summary>
 503        public IDictionary<string, object?> Properties =>
 0504            _settings._properties ??= new Dictionary<string, object?>();
 505
 506        /// <summary>
 507        /// Gets or sets a callback that returns the <see cref="Encoding"/> to encode the value for the specified reques
 508        /// or <see langword="null"/> to use the default behavior.
 509        /// </summary>
 510        public HeaderEncodingSelector<HttpRequestMessage>? RequestHeaderEncodingSelector
 511        {
 0512            get => _settings._requestHeaderEncodingSelector;
 513            set
 0514            {
 0515                CheckDisposedOrStarted();
 0516                _settings._requestHeaderEncodingSelector = value;
 0517            }
 518        }
 519
 520        /// <summary>
 521        /// Gets or sets a callback that returns the <see cref="Encoding"/> to decode the value for the specified respon
 522        /// or <see langword="null"/> to use the default behavior.
 523        /// </summary>
 524        public HeaderEncodingSelector<HttpRequestMessage>? ResponseHeaderEncodingSelector
 525        {
 0526            get => _settings._responseHeaderEncodingSelector;
 527            set
 0528            {
 0529                CheckDisposedOrStarted();
 0530                _settings._responseHeaderEncodingSelector = value;
 0531            }
 532        }
 533
 534        /// <summary>
 535        /// Gets or sets the <see cref="DistributedContextPropagator"/> to use when propagating the distributed trace an
 536        /// Use <see langword="null"/> to disable propagation.
 537        /// Defaults to <see cref="DistributedContextPropagator.Current"/>.
 538        /// </summary>
 539        [CLSCompliant(false)]
 540        public DistributedContextPropagator? ActivityHeadersPropagator
 541        {
 0542            get => _settings._activityHeadersPropagator;
 543            set
 0544            {
 0545                CheckDisposedOrStarted();
 0546                _settings._activityHeadersPropagator = value;
 0547            }
 548        }
 549
 550        /// <summary>
 551        /// Gets or sets the <see cref="IMeterFactory"/> to create a custom <see cref="Meter"/> for the <see cref="Socke
 552        /// </summary>
 553        /// <remarks>
 554        /// When <see cref="MeterFactory"/> is set to a non-<see langword="null"/> value, all metrics emitted by the <se
 555        /// will be recorded using the <see cref="Meter"/> provided by the <see cref="IMeterFactory"/>.
 556        /// </remarks>
 557        [CLSCompliant(false)]
 558        public IMeterFactory? MeterFactory
 559        {
 0560            get => _settings._meterFactory;
 561            set
 0562            {
 0563                CheckDisposedOrStarted();
 0564                _settings._meterFactory = value;
 0565            }
 566        }
 567
 568        internal ClientCertificateOption ClientCertificateOptions
 569        {
 0570            get => _settings._clientCertificateOptions;
 571            set
 0572            {
 0573                CheckDisposedOrStarted();
 0574                _settings._clientCertificateOptions = value;
 0575            }
 576        }
 577
 578        protected override void Dispose(bool disposing)
 0579        {
 0580            if (disposing && !_disposed)
 0581            {
 0582                _disposed = true;
 0583                _handler?.Dispose();
 0584            }
 585
 0586            base.Dispose(disposing);
 0587        }
 588
 589        private HttpMessageHandlerStage SetupHandlerChain()
 0590        {
 591            // Clone the settings to get a relatively consistent view that won't change after this point.
 592            // (This isn't entirely complete, as some of the collections it contains aren't currently deeply cloned.)
 0593            HttpConnectionSettings settings = _settings.CloneAndNormalize();
 594
 0595            HttpConnectionPoolManager poolManager = new HttpConnectionPoolManager(settings);
 0596            HttpMessageHandlerStage handler = new HttpConnectionHandler(poolManager, doRequestAuth: settings._credential
 597
 598            // MetricsHandler should be descendant of DiagnosticsHandler in the handler chain to make sure the 'http.req
 599            // metric is recorded before stopping the request Activity. This is needed to make sure that our telemetry s
 0600            if (GlobalHttpSettings.MetricsHandler.IsGloballyEnabled)
 0601            {
 0602                handler = new MetricsHandler(handler, settings._meterFactory, settings._proxy, out Meter meter);
 0603                settings._metrics = new SocketsHttpHandlerMetrics(meter);
 0604            }
 605
 606            // DiagnosticsHandler is inserted before RedirectHandler so that trace propagation is done on redirects as w
 0607            if (GlobalHttpSettings.DiagnosticsHandler.EnableActivityPropagation && settings._activityHeadersPropagator i
 0608            {
 0609                handler = new DiagnosticsHandler(handler, propagator, settings._proxy, settings._allowAutoRedirect);
 0610            }
 611
 0612            if (settings._allowAutoRedirect)
 0613            {
 614                // Just as with WinHttpHandler, for security reasons, we do not support authentication on redirects
 615                // if the credential is anything other than a CredentialCache.
 616                // We allow credentials in a CredentialCache since they are specifically tied to URIs.
 0617                handler = new RedirectHandler(settings._maxAutomaticRedirections, handler, disableAuthOnRedirect: settin
 0618            }
 619
 0620            if ((settings._automaticDecompression & SupportedDecompressionMethods) != DecompressionMethods.None)
 0621            {
 0622                Debug.Assert(_decompressionHandlerFactory is not null);
 0623                handler = _decompressionHandlerFactory(settings, handler);
 0624            }
 625
 626            // Ensure a single handler is used for all requests.
 0627            if (Interlocked.CompareExchange(ref _handler, handler, null) != null)
 0628            {
 0629                handler.Dispose();
 0630            }
 631
 0632            return _handler;
 0633        }
 634
 635        // Allows for DecompressionHandler (and its compression dependencies) to be trimmed when
 636        // AutomaticDecompression is not being used.
 637        private void EnsureDecompressionHandlerFactory()
 0638        {
 0639            _decompressionHandlerFactory ??= (settings, handler) => new DecompressionHandler(settings._automaticDecompre
 0640        }
 641
 642        // Not stored as a constant on the DecompressionHandler to allow it to get trimmed.
 643        private const DecompressionMethods SupportedDecompressionMethods = DecompressionMethods.GZip | DecompressionMeth
 644
 645        protected internal override HttpResponseMessage Send(HttpRequestMessage request,
 646            CancellationToken cancellationToken)
 0647        {
 0648            ArgumentNullException.ThrowIfNull(request);
 649
 0650            if (request.Version.Major >= 2)
 0651            {
 0652                throw new NotSupportedException(SR.Format(SR.net_http_http2_sync_not_supported, GetType()));
 653            }
 654
 655            // Do not allow upgrades for synchronous requests, that might lead to asynchronous code-paths.
 0656            if (request.VersionPolicy == HttpVersionPolicy.RequestVersionOrHigher)
 0657            {
 0658                throw new NotSupportedException(SR.Format(SR.net_http_upgrade_not_enabled_sync, nameof(Send), request.Ve
 659            }
 660
 0661            ObjectDisposedException.ThrowIf(_disposed, this);
 662
 0663            cancellationToken.ThrowIfCancellationRequested();
 664
 0665            Exception? error = ValidateAndNormalizeRequest(request);
 0666            if (error != null)
 0667            {
 0668                throw error;
 669            }
 670
 0671            HttpMessageHandlerStage handler = _handler ?? SetupHandlerChain();
 672
 0673            return handler.Send(request, cancellationToken);
 0674        }
 675
 676        protected internal override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken ca
 0677        {
 0678            ArgumentNullException.ThrowIfNull(request);
 679
 0680            ObjectDisposedException.ThrowIf(_disposed, this);
 681
 0682            if (cancellationToken.IsCancellationRequested)
 0683            {
 0684                return Task.FromCanceled<HttpResponseMessage>(cancellationToken);
 685            }
 686
 0687            Exception? error = ValidateAndNormalizeRequest(request);
 0688            if (error != null)
 0689            {
 0690                return Task.FromException<HttpResponseMessage>(error);
 691            }
 692
 0693            return _handler is { } handler
 0694                ? handler.SendAsync(request, cancellationToken)
 0695                : CreateHandlerAndSendAsync(request, cancellationToken);
 696
 697            // SetupHandlerChain may block for a few seconds in some environments.
 698            // E.g. during the first access of HttpClient.DefaultProxy - https://github.com/dotnet/runtime/issues/115301
 699            // The setup procedure is enqueued to thread pool to prevent the caller from blocking.
 700            async Task<HttpResponseMessage> CreateHandlerAndSendAsync(HttpRequestMessage request, CancellationToken canc
 0701            {
 0702                _handlerChainSetupTask ??= Task.Run(SetupHandlerChain);
 0703                HttpMessageHandlerStage handler = await _handlerChainSetupTask.ConfigureAwait(false);
 0704                return await handler.SendAsync(request, cancellationToken).ConfigureAwait(false);
 0705            }
 0706        }
 707
 708        private static Exception? ValidateAndNormalizeRequest(HttpRequestMessage request)
 0709        {
 0710            if (request.Version != HttpVersion.Version10 && request.Version != HttpVersion.Version11 && request.Version 
 0711            {
 0712                return ExceptionDispatchInfo.SetCurrentStackTrace(new NotSupportedException(SR.net_http_unsupported_vers
 713            }
 714
 715            // Add headers to define content transfer, if not present
 0716            if (request.HasHeaders && request.Headers.TransferEncodingChunked.GetValueOrDefault())
 0717            {
 0718                if (request.Content == null)
 0719                {
 0720                    return ExceptionDispatchInfo.SetCurrentStackTrace(new HttpRequestException(SR.net_http_client_execut
 0721                        ExceptionDispatchInfo.SetCurrentStackTrace(new InvalidOperationException(SR.net_http_chunked_not
 722                }
 723
 724                // Since the user explicitly set TransferEncodingChunked to true, we need to remove
 725                // the Content-Length header if present, as sending both is invalid.
 0726                request.Content.Headers.ContentLength = null;
 0727            }
 0728            else if (request.Content != null && request.Content.Headers.ContentLength == null)
 0729            {
 730                // We have content, but neither Transfer-Encoding nor Content-Length is set.
 0731                request.Headers.TransferEncodingChunked = true;
 0732            }
 733
 0734            if (request.Version.Minor == 0 && request.Version.Major == 1 && request.HasHeaders)
 0735            {
 736                // HTTP 1.0 does not support chunking
 0737                if (request.Headers.TransferEncodingChunked == true)
 0738                {
 0739                    return ExceptionDispatchInfo.SetCurrentStackTrace(new NotSupportedException(SR.net_http_unsupported_
 740                }
 741
 742                // HTTP 1.0 does not support Expect: 100-continue; just disable it.
 0743                if (request.Headers.ExpectContinue == true)
 0744                {
 0745                    request.Headers.ExpectContinue = false;
 0746                }
 0747            }
 748
 0749            Uri? requestUri = request.RequestUri;
 0750            if (requestUri is null || !requestUri.IsAbsoluteUri)
 0751            {
 0752                return ExceptionDispatchInfo.SetCurrentStackTrace(new InvalidOperationException(SR.net_http_client_inval
 753            }
 754
 0755            if (!HttpUtilities.IsSupportedScheme(requestUri.Scheme))
 0756            {
 0757                return ExceptionDispatchInfo.SetCurrentStackTrace(new NotSupportedException(SR.Format(SR.net_http_unsupp
 758            }
 759
 0760            return null;
 0761        }
 762    }
 763}
 764

Methods/Properties

.ctor()
Settings()
CheckDisposedOrStarted()
IsSupported()
UseCookies()
UseCookies(System.Boolean)
CookieContainer()
CookieContainer(System.Net.CookieContainer)
AutomaticDecompression()
AutomaticDecompression(System.Net.DecompressionMethods)
UseProxy()
UseProxy(System.Boolean)
Proxy()
Proxy(System.Net.IWebProxy)
DefaultProxyCredentials()
DefaultProxyCredentials(System.Net.ICredentials)
PreAuthenticate()
PreAuthenticate(System.Boolean)
Credentials()
Credentials(System.Net.ICredentials)
AllowAutoRedirect()
AllowAutoRedirect(System.Boolean)
MaxAutomaticRedirections()
MaxAutomaticRedirections(System.Int32)
MaxConnectionsPerServer()
MaxConnectionsPerServer(System.Int32)
MaxResponseDrainSize()
MaxResponseDrainSize(System.Int32)
ResponseDrainTimeout()
ResponseDrainTimeout(System.TimeSpan)
MaxResponseHeadersLength()
MaxResponseHeadersLength(System.Int32)
SslOptions()
SslOptions(System.Net.Security.SslClientAuthenticationOptions)
PooledConnectionLifetime()
PooledConnectionLifetime(System.TimeSpan)
PooledConnectionIdleTimeout()
PooledConnectionIdleTimeout(System.TimeSpan)
ConnectTimeout()
ConnectTimeout(System.TimeSpan)
Expect100ContinueTimeout()
Expect100ContinueTimeout(System.TimeSpan)
InitialHttp2StreamWindowSize()
InitialHttp2StreamWindowSize(System.Int32)
InitialHttp2MaxConcurrentStreams()
InitialHttp2MaxConcurrentStreams(System.Int32)
KeepAlivePingDelay()
KeepAlivePingDelay(System.TimeSpan)
KeepAlivePingTimeout()
KeepAlivePingTimeout(System.TimeSpan)
KeepAlivePingPolicy()
KeepAlivePingPolicy(System.Net.Http.HttpKeepAlivePingPolicy)
EnableMultipleHttp2Connections()
EnableMultipleHttp2Connections(System.Boolean)
EnableMultipleHttp3Connections()
EnableMultipleHttp3Connections(System.Boolean)
ConnectCallback()
ConnectCallback(System.Func`3<System.Net.Http.SocketsHttpConnectionContext,System.Threading.CancellationToken,System.Threading.Tasks.ValueTask`1<System.IO.Stream>>)
PlaintextStreamFilter()
PlaintextStreamFilter(System.Func`3<System.Net.Http.SocketsHttpPlaintextStreamFilterContext,System.Threading.CancellationToken,System.Threading.Tasks.ValueTask`1<System.IO.Stream>>)
ShouldEvictConnection()
ShouldEvictConnection(System.Func`3<System.Net.Http.SocketsHttpConnectionEvictionContext,System.Threading.CancellationToken,System.Threading.Tasks.Task`1<System.Boolean>>)
Properties()
RequestHeaderEncodingSelector()
RequestHeaderEncodingSelector(System.Net.Http.HeaderEncodingSelector`1<System.Net.Http.HttpRequestMessage>)
ResponseHeaderEncodingSelector()
ResponseHeaderEncodingSelector(System.Net.Http.HeaderEncodingSelector`1<System.Net.Http.HttpRequestMessage>)
ActivityHeadersPropagator()
ActivityHeadersPropagator(System.Diagnostics.DistributedContextPropagator)
MeterFactory()
MeterFactory(System.Diagnostics.Metrics.IMeterFactory)
ClientCertificateOptions()
ClientCertificateOptions(System.Net.Http.ClientCertificateOption)
Dispose(System.Boolean)
SetupHandlerChain()
EnsureDecompressionHandlerFactory()
Send(System.Net.Http.HttpRequestMessage,System.Threading.CancellationToken)
SendAsync(System.Net.Http.HttpRequestMessage,System.Threading.CancellationToken)
CreateHandlerAndSendAsync(System.Net.Http.HttpRequestMessage,System.Threading.CancellationToken)
ValidateAndNormalizeRequest(System.Net.Http.HttpRequestMessage)