< Summary

Information
Class: System.Net.Http.HttpConnectionBase
Assembly: System.Net.Http
File(s): D:\runner\runtime\src\libraries\System.Net.Http\src\System\Net\Http\SocketsHttpHandler\HttpConnectionBase.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 153
Coverable lines: 153
Total lines: 257
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 92
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

D:\runner\runtime\src\libraries\System.Net.Http\src\System\Net\Http\SocketsHttpHandler\HttpConnectionBase.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.IO;
 8using System.Net.Http.Headers;
 9using System.Net.Http.Metrics;
 10using System.Net.Security;
 11using System.Runtime.CompilerServices;
 12using System.Text;
 13using System.Threading;
 14using System.Threading.Tasks;
 15
 16namespace System.Net.Http
 17{
 18    internal abstract class HttpConnectionBase : IDisposable, IHttpTrace
 19    {
 20        protected readonly HttpConnectionPool _pool;
 21
 022        private static long s_connectionCounter = -1;
 23
 24        // May be null if none of the counters were enabled when the connection was established.
 25        private ConnectionMetrics? _connectionMetrics;
 26
 27        // Indicates whether we've counted this connection as established, so that we can
 28        // avoid decrementing the counter once it's closed in case telemetry was enabled in between.
 29        private bool _httpTelemetryMarkedConnectionAsOpened;
 30
 031        private readonly long _creationTickCount = Environment.TickCount64;
 32        private long? _idleSinceTickCount;
 33
 34        /// <summary>Cached string for the last Date header received on this connection.</summary>
 35        private string? _lastDateHeaderValue;
 36        /// <summary>Cached string for the last Server header received on this connection.</summary>
 37        private string? _lastServerHeaderValue;
 38
 039        public long Id { get; } = Interlocked.Increment(ref s_connectionCounter);
 40
 041        public Activity? ConnectionSetupActivity { get; private set; }
 42
 043        public HttpConnectionBase(HttpConnectionPool pool)
 044        {
 045            Debug.Assert(this is HttpConnection or Http2Connection or Http3Connection);
 046            Debug.Assert(pool != null);
 047            _pool = pool;
 048        }
 49
 50        public HttpConnectionBase(HttpConnectionPool pool, Activity? connectionSetupActivity, IPEndPoint? remoteEndPoint
 051            : this(pool)
 052        {
 053            MarkConnectionAsEstablished(connectionSetupActivity, remoteEndPoint);
 054        }
 55
 56        protected void MarkConnectionAsEstablished(Activity? connectionSetupActivity, IPEndPoint? remoteEndPoint)
 057        {
 058            ConnectionSetupActivity = connectionSetupActivity;
 059            if (GlobalHttpSettings.MetricsHandler.IsGloballyEnabled)
 060            {
 061                Debug.Assert(_pool.Settings._metrics is not null);
 62
 063                SocketsHttpHandlerMetrics metrics = _pool.Settings._metrics!;
 064                if (metrics.OpenConnections.Enabled || metrics.ConnectionDuration.Enabled)
 065                {
 66                    // While requests may report HTTP/1.0 as the protocol, we treat all HTTP/1.X connections as HTTP/1.1
 067                    string protocol =
 068                        this is HttpConnection ? "1.1" :
 069                        this is Http2Connection ? "2" :
 070                        "3";
 71
 072                    Debug.Assert(_pool.TelemetryServerAddress is not null, "TelemetryServerAddress should not be null wh
 073                    _connectionMetrics = new ConnectionMetrics(
 074                        metrics,
 075                        protocol,
 076                        _pool.IsSecure ? "https" : "http",
 077                        _pool.TelemetryServerAddress,
 078                        _pool.OriginAuthority.Port,
 079                        remoteEndPoint?.Address?.ToString());
 80
 081                    _connectionMetrics.ConnectionEstablished();
 082                }
 083            }
 84
 085            _idleSinceTickCount = _creationTickCount;
 86
 087            if (HttpTelemetry.Log.IsEnabled())
 088            {
 089                _httpTelemetryMarkedConnectionAsOpened = true;
 90
 091                string scheme = _pool.IsSecure ? "https" : "http";
 092                string host = _pool.OriginAuthority.HostValue;
 093                int port = _pool.OriginAuthority.Port;
 94
 095                if (this is HttpConnection) HttpTelemetry.Log.Http11ConnectionEstablished(Id, scheme, host, port, remote
 096                else if (this is Http2Connection) HttpTelemetry.Log.Http20ConnectionEstablished(Id, scheme, host, port, 
 097                else HttpTelemetry.Log.Http30ConnectionEstablished(Id, scheme, host, port, remoteEndPoint);
 098            }
 099        }
 100
 101        public void MarkConnectionAsClosed()
 0102        {
 0103            if (GlobalHttpSettings.MetricsHandler.IsGloballyEnabled) _connectionMetrics?.ConnectionClosed(durationMs: En
 104
 0105            if (HttpTelemetry.Log.IsEnabled())
 0106            {
 107                // Only decrement the connection count if we counted this connection
 0108                if (_httpTelemetryMarkedConnectionAsOpened)
 0109                {
 0110                    if (this is HttpConnection) HttpTelemetry.Log.Http11ConnectionClosed(Id);
 0111                    else if (this is Http2Connection) HttpTelemetry.Log.Http20ConnectionClosed(Id);
 0112                    else HttpTelemetry.Log.Http30ConnectionClosed(Id);
 0113                }
 0114            }
 0115        }
 116
 117        public void MarkConnectionAsIdle()
 0118        {
 0119            _idleSinceTickCount = Environment.TickCount64;
 0120            if (GlobalHttpSettings.MetricsHandler.IsGloballyEnabled) _connectionMetrics?.IdleStateChanged(idle: true);
 0121        }
 122
 123        public void MarkConnectionAsNotIdle()
 0124        {
 0125            _idleSinceTickCount = null;
 0126            if (GlobalHttpSettings.MetricsHandler.IsGloballyEnabled) _connectionMetrics?.IdleStateChanged(idle: false);
 0127        }
 128
 129        /// <summary>Uses <see cref="HeaderDescriptor.GetHeaderValue"/>, but first special-cases several known headers f
 130        public string GetResponseHeaderValueWithCaching(HeaderDescriptor descriptor, ReadOnlySpan<byte> value, Encoding?
 0131        {
 0132            return
 0133                descriptor.Equals(KnownHeaders.Date) ? GetOrAddCachedValue(ref _lastDateHeaderValue, descriptor, value, 
 0134                descriptor.Equals(KnownHeaders.Server) ? GetOrAddCachedValue(ref _lastServerHeaderValue, descriptor, val
 0135                descriptor.GetHeaderValue(value, valueEncoding);
 136
 137            static string GetOrAddCachedValue([NotNull] ref string? cache, HeaderDescriptor descriptor, ReadOnlySpan<byt
 0138            {
 0139                string? lastValue = cache;
 0140                if (lastValue is null || !Ascii.Equals(value, lastValue))
 0141                {
 0142                    cache = lastValue = descriptor.GetHeaderValue(value, encoding);
 0143                }
 0144                Debug.Assert(cache is not null);
 0145                return lastValue;
 0146            }
 0147        }
 148
 149        public abstract void Trace(string message, [CallerMemberName] string? memberName = null);
 150
 151        protected void TraceConnection(Stream stream)
 0152        {
 0153            if (stream is SslStream sslStream)
 0154            {
 155#pragma warning disable SYSLIB0058 // Use NegotiatedCipherSuite.
 0156                Trace(
 0157                    $"{this}. Id:{Id}, " +
 0158                    $"SslProtocol:{sslStream.SslProtocol}, NegotiatedApplicationProtocol:{sslStream.NegotiatedApplicatio
 0159                    $"NegotiatedCipherSuite:{sslStream.NegotiatedCipherSuite}, CipherAlgorithm:{sslStream.CipherAlgorith
 0160                    $"HashAlgorithm:{sslStream.HashAlgorithm}, HashStrength:{sslStream.HashStrength}, " +
 0161                    $"KeyExchangeAlgorithm:{sslStream.KeyExchangeAlgorithm}, KeyExchangeStrength:{sslStream.KeyExchangeS
 0162                    $"LocalCertificate:{sslStream.LocalCertificate}, RemoteCertificate:{sslStream.RemoteCertificate}");
 163#pragma warning restore SYSLIB0058 // Use NegotiatedCipherSuite.
 0164            }
 165            else
 0166            {
 0167                Trace($"{this}. Id:{Id}");
 0168            }
 0169        }
 170
 0171        public long GetLifetimeTicks(long nowTicks) => nowTicks - _creationTickCount;
 172
 0173        public long GetIdleTicks(long nowTicks) => _idleSinceTickCount is long idleSinceTickCount ? nowTicks - idleSince
 174
 175        /// <summary>Check whether a connection is still usable, or should be scavenged.</summary>
 176        /// <returns>True if connection can be used.</returns>
 0177        public virtual bool CheckUsabilityOnScavenge() => true;
 178
 0179        internal static bool IsDigit(byte c) => (uint)(c - '0') <= '9' - '0';
 180
 181        internal static int ParseStatusCode(ReadOnlySpan<byte> value)
 0182        {
 183            byte status1, status2, status3;
 0184            if (value.Length != 3 ||
 0185                !IsDigit(status1 = value[0]) ||
 0186                !IsDigit(status2 = value[1]) ||
 0187                !IsDigit(status3 = value[2]))
 0188            {
 0189                throw new HttpRequestException(HttpRequestError.InvalidResponse, SR.Format(SR.net_http_invalid_response_
 190            }
 191
 0192            return 100 * (status1 - '0') + 10 * (status2 - '0') + (status3 - '0');
 0193        }
 194
 195        /// <summary>Awaits a task, logging any resulting exceptions (which are otherwise ignored).</summary>
 196        internal void LogExceptions(Task task)
 0197        {
 0198            if (task.IsCompleted)
 0199            {
 0200                if (task.IsFaulted)
 0201                {
 0202                    LogFaulted(this, task);
 0203                }
 0204            }
 205            else
 0206            {
 0207                task.ContinueWith(static (t, state) => LogFaulted((HttpConnectionBase)state!, t), this,
 0208                    CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyO
 0209            }
 210
 211            static void LogFaulted(HttpConnectionBase connection, Task task)
 0212            {
 0213                Debug.Assert(task.IsFaulted);
 0214                Exception? e = task.Exception!.InnerException; // Access Exception even if not tracing, to avoid TaskSch
 0215                if (NetEventSource.Log.IsEnabled()) connection.Trace($"Exception from asynchronous processing: {e}");
 0216            }
 0217        }
 218
 219        public abstract void Dispose();
 220
 221        /// <summary>
 222        /// Called by <see cref="HttpConnectionPool.CleanCacheAndDisposeIfUnused"/> while holding the lock.
 223        /// </summary>
 224        public bool IsUsable(long nowTicks, TimeSpan pooledConnectionLifetime, TimeSpan pooledConnectionIdleTimeout)
 0225        {
 226            // Validate that the connection hasn't been idle in the pool for longer than is allowed.
 0227            if (pooledConnectionIdleTimeout != Timeout.InfiniteTimeSpan)
 0228            {
 0229                long idleTicks = GetIdleTicks(nowTicks);
 0230                if (idleTicks > pooledConnectionIdleTimeout.TotalMilliseconds)
 0231                {
 0232                    if (NetEventSource.Log.IsEnabled()) Trace($"Scavenging connection. Idle {TimeSpan.FromMilliseconds(i
 0233                    return false;
 234                }
 0235            }
 236
 237            // Validate that the connection lifetime has not been exceeded.
 0238            if (pooledConnectionLifetime != Timeout.InfiniteTimeSpan)
 0239            {
 0240                long lifetimeTicks = GetLifetimeTicks(nowTicks);
 0241                if (lifetimeTicks > pooledConnectionLifetime.TotalMilliseconds)
 0242                {
 0243                    if (NetEventSource.Log.IsEnabled()) Trace($"Scavenging connection. Lifetime {TimeSpan.FromMillisecon
 0244                    return false;
 245                }
 0246            }
 247
 0248            if (!CheckUsabilityOnScavenge())
 0249            {
 0250                if (NetEventSource.Log.IsEnabled()) Trace($"Scavenging connection. Keep-Alive timeout exceeded, unexpect
 0251                return false;
 252            }
 253
 0254            return true;
 0255        }
 256    }
 257}