Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/project/list-of-diagnostics.md
Original file line number Diff line number Diff line change
Expand Up @@ -320,3 +320,4 @@ Diagnostic id values for experimental APIs must not be recycled, as that could s
| __`SYSLIB5004`__ | .NET 9 | TBD | `X86Base.DivRem` is experimental since performance is not as optimized as `T.DivRem` |
| __`SYSLIB5005`__ | .NET 9 | .NET 10 | `System.Formats.Nrbf` is experimental |
| __`SYSLIB5006`__ | .NET 10 | TBD | Types for Post-Quantum Cryptography (PQC) are experimental. |
| __`SYSLIB5008`__ | .NET 11 | TBD | `SocketsHttpHandler` connection eviction control and `HttpRequestMessage.ConnectionId` APIs are experimental. |
3 changes: 3 additions & 0 deletions src/libraries/Common/src/System/Experimentals.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ internal static class Experimentals
// Types for Post-Quantum Cryptography (PQC) are experimental.
internal const string PostQuantumCryptographyDiagId = "SYSLIB5006";

// SocketsHttpHandler connection eviction control and HttpRequestMessage.ConnectionId APIs are experimental.
internal const string SocketsHttpHandlerExperimentalDiagId = "SYSLIB5008";

// When adding a new diagnostic ID, add it to the table in docs\project\list-of-diagnostics.md as well.
// Keep new const identifiers above this comment.
}
Expand Down
18 changes: 18 additions & 0 deletions src/libraries/System.Net.Http/ref/System.Net.Http.cs
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,8 @@ public partial class HttpRequestMessage : System.IDisposable
public HttpRequestMessage() { }
public HttpRequestMessage(System.Net.Http.HttpMethod method, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("Uri")] string? requestUri) { }
public HttpRequestMessage(System.Net.Http.HttpMethod method, System.Uri? requestUri) { }
[System.Diagnostics.CodeAnalysis.ExperimentalAttribute("SYSLIB5008", UrlFormat="https://aka.ms/dotnet-warnings/{0}")]
public long? ConnectionId { get { throw null; } set { } }
Comment thread
MihaZupan marked this conversation as resolved.
public System.Net.Http.HttpContent? Content { get { throw null; } set { } }
public System.Net.Http.Headers.HttpRequestHeaders Headers { get { throw null; } }
public System.Net.Http.HttpMethod Method { get { throw null; } set { } }
Expand Down Expand Up @@ -424,9 +426,21 @@ protected override void SerializeToStream(System.IO.Stream stream, System.Net.Tr
public sealed partial class SocketsHttpConnectionContext
{
internal SocketsHttpConnectionContext() { }
[System.Diagnostics.CodeAnalysis.ExperimentalAttribute("SYSLIB5008", UrlFormat="https://aka.ms/dotnet-warnings/{0}")]
public long ConnectionId { get { throw null; } }
public System.Net.DnsEndPoint DnsEndPoint { get { throw null; } }
public System.Net.Http.HttpRequestMessage InitialRequestMessage { get { throw null; } }
}
[System.Diagnostics.CodeAnalysis.ExperimentalAttribute("SYSLIB5008", UrlFormat="https://aka.ms/dotnet-warnings/{0}")]
public sealed partial class SocketsHttpConnectionEvictionContext
{
internal SocketsHttpConnectionEvictionContext() { }
public System.TimeSpan Age { get { throw null; } }
public long ConnectionId { get { throw null; } }
public System.Net.DnsEndPoint DnsEndPoint { get { throw null; } }
public System.Version HttpVersion { get { throw null; } }
public System.Net.IPEndPoint? RemoteEndPoint { get { throw null; } }
}
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
public sealed partial class SocketsHttpHandler : System.Net.Http.HttpMessageHandler
{
Expand Down Expand Up @@ -465,6 +479,8 @@ public SocketsHttpHandler() { }
public System.Net.Http.HeaderEncodingSelector<System.Net.Http.HttpRequestMessage>? RequestHeaderEncodingSelector { get { throw null; } set { } }
public System.TimeSpan ResponseDrainTimeout { get { throw null; } set { } }
public System.Net.Http.HeaderEncodingSelector<System.Net.Http.HttpRequestMessage>? ResponseHeaderEncodingSelector { get { throw null; } set { } }
[System.Diagnostics.CodeAnalysis.ExperimentalAttribute("SYSLIB5008", UrlFormat="https://aka.ms/dotnet-warnings/{0}")]
public System.Func<System.Net.Http.SocketsHttpConnectionEvictionContext, System.Threading.CancellationToken, System.Threading.Tasks.Task<bool>>? ShouldEvictConnection { get { throw null; } set { } }
[System.Diagnostics.CodeAnalysis.AllowNullAttribute]
public System.Net.Security.SslClientAuthenticationOptions SslOptions { get { throw null; } set { } }
public bool UseCookies { get { throw null; } set { } }
Expand All @@ -476,6 +492,8 @@ protected override void Dispose(bool disposing) { }
public sealed partial class SocketsHttpPlaintextStreamFilterContext
{
internal SocketsHttpPlaintextStreamFilterContext() { }
[System.Diagnostics.CodeAnalysis.ExperimentalAttribute("SYSLIB5008", UrlFormat="https://aka.ms/dotnet-warnings/{0}")]
public long ConnectionId { get { throw null; } }
public System.Net.Http.HttpRequestMessage InitialRequestMessage { get { throw null; } }
public System.Version NegotiatedHttpVersion { get { throw null; } }
public System.IO.Stream PlaintextStream { get { throw null; } }
Expand Down
7 changes: 7 additions & 0 deletions src/libraries/System.Net.Http/src/System.Net.Http.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
<TargetFrameworks>$(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-linux;$(NetCoreAppCurrent)-osx;$(NetCoreAppCurrent)-freebsd;$(NetCoreAppCurrent)-openbsd;$(NetCoreAppCurrent)-maccatalyst;$(NetCoreAppCurrent)-ios;$(NetCoreAppCurrent)-tvos;$(NetCoreAppCurrent)-browser;$(NetCoreAppCurrent)-wasi;$(NetCoreAppCurrent)-illumos;$(NetCoreAppCurrent)-solaris;$(NetCoreAppCurrent)-haiku;$(NetCoreAppCurrent)-android;$(NetCoreAppCurrent)</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DefineConstants>$(DefineConstants);HTTP_DLL</DefineConstants>
<!-- SocketsHttpHandler connection eviction control and HttpRequestMessage.ConnectionId (SYSLIB5008) APIs are experimental and consumed internally. -->
<NoWarn>$(NoWarn);SYSLIB5008</NoWarn>
<UseCompilerGeneratedDocXmlFile>false</UseCompilerGeneratedDocXmlFile>
</PropertyGroup>

Expand Down Expand Up @@ -160,6 +162,8 @@
Link="Common\System\Text\ValueStringBuilder.AppendSpanFormattable.cs" />
<Compile Include="$(CommonPath)System\Obsoletions.cs"
Link="Common\System\Obsoletions.cs" />
<Compile Include="$(CommonPath)System\Experimentals.cs"
Link="Common\System\Experimentals.cs" />
<Compile Include="$(CommonPath)System\Net\CredentialCacheKey.cs"
Link="Common\System\Net\CredentialCacheKey.cs" />
</ItemGroup>
Expand Down Expand Up @@ -218,6 +222,7 @@
<Compile Include="System\Net\Http\SocketsHttpHandler\PreAuthCredentialCache.cs" />
<Compile Include="System\Net\Http\SocketsHttpHandler\RawConnectionStream.cs" />
<Compile Include="System\Net\Http\SocketsHttpHandler\RedirectHandler.cs" />
<Compile Include="System\Net\Http\SocketsHttpHandler\SocketsHttpConnectionEvictionContext.cs" />
<Compile Include="System\Net\Http\SocketsHttpHandler\SocketsHttpConnectionContext.cs" />
<Compile Include="System\Net\Http\SocketsHttpHandler\SocketsHttpHandler.cs" />
<Compile Include="System\Net\Http\HttpTelemetry.AnyOS.cs" />
Expand Down Expand Up @@ -434,6 +439,7 @@
Link="Common\System\Net\HttpStatusDescription.cs" />
<Compile Include="System\Net\Http\SocketsHttpHandler\HttpKeepAlivePingPolicy.cs" />
<Compile Include="System\Net\Http\SocketsHttpHandler\HttpNoProxy.cs" />
<Compile Include="System\Net\Http\SocketsHttpHandler\SocketsHttpConnectionEvictionContext.cs" />
<Compile Include="System\Net\Http\SocketsHttpHandler\SocketsHttpConnectionContext.cs" />
<Compile Include="System\Net\Http\BrowserHttpHandler\SystemProxyInfo.Browser.cs" />
<Compile Include="System\Net\Http\BrowserHttpHandler\SocketsHttpHandler.cs" />
Expand All @@ -449,6 +455,7 @@
Link="Common\System\Net\HttpStatusDescription.cs" />
<Compile Include="System\Net\Http\SocketsHttpHandler\HttpKeepAlivePingPolicy.cs" />
<Compile Include="System\Net\Http\SocketsHttpHandler\HttpNoProxy.cs" />
<Compile Include="System\Net\Http\SocketsHttpHandler\SocketsHttpConnectionEvictionContext.cs" />
<Compile Include="System\Net\Http\SocketsHttpHandler\SocketsHttpConnectionContext.cs" />
<Compile Include="System\Net\Http\BrowserHttpHandler\SocketsHttpHandler.cs" />
<Compile Include="System\Net\Http\WasiHttpHandler\SystemProxyInfo.Wasi.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,5 +213,12 @@ public Func<SocketsHttpPlaintextStreamFilterContext, CancellationToken, ValueTas
get => throw new PlatformNotSupportedException();
set => throw new PlatformNotSupportedException();
}

[Experimental(Experimentals.SocketsHttpHandlerExperimentalDiagId, UrlFormat = Experimentals.SharedUrlFormat)]
public Func<SocketsHttpConnectionEvictionContext, CancellationToken, Task<bool>>? ShouldEvictConnection
{
get => throw new PlatformNotSupportedException();
set => throw new PlatformNotSupportedException();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,25 @@ public Uri? RequestUri
/// </summary>
public HttpRequestOptions Options => _options ??= new HttpRequestOptions();

/// <summary>
/// Gets or sets the identifier of the connection that this request was most recently sent on, or <see langword="null"/>
/// if it has not been sent over a <see cref="SocketsHttpHandler"/> connection.
/// </summary>
/// <remarks>
/// The value matches the connection id reported through EventSource telemetry, and the id surfaced to
/// <see cref="SocketsHttpHandler.ConnectCallback"/> and <see cref="SocketsHttpHandler.ShouldEvictConnection"/>,
/// allowing a caller to correlate a request with the connection that served it. When a request is sent over
/// multiple connections (for example after a redirect or a retry), the value reflects the most recent attempt.
/// <para>
/// This property is intended to be read after the request has been sent. Assigning a value before the request
/// is sent has no effect on how the request is handled: it does not request or influence the use of a particular
/// connection, and any value set by the caller is overwritten with the id of the connection that actually serves
/// the request.
/// </para>
/// </remarks>
[Experimental(Experimentals.SocketsHttpHandlerExperimentalDiagId, UrlFormat = Experimentals.SharedUrlFormat)]
public long? ConnectionId { get; set; }
Comment thread
MihaZupan marked this conversation as resolved.

public HttpRequestMessage()
: this(HttpMethod.Get, (Uri?)null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -293,14 +293,14 @@ private async Task InjectNewHttp11ConnectionAsync(RequestQueue<HttpConnection>.Q

internal async ValueTask<HttpConnection> CreateHttp11ConnectionAsync(HttpRequestMessage request, bool async, CancellationToken cancellationToken)
{
(Stream stream, TransportContext? transportContext, Activity? activity, IPEndPoint? remoteEndPoint) = await ConnectAsync(request, async, isForHttp2: false, cancellationToken).ConfigureAwait(false);
return await ConstructHttp11ConnectionAsync(async, stream, transportContext, request, activity, remoteEndPoint, cancellationToken).ConfigureAwait(false);
(Stream stream, TransportContext? transportContext, Activity? activity, IPEndPoint? remoteEndPoint, long connectionId) = await ConnectAsync(request, async, isForHttp2: false, cancellationToken).ConfigureAwait(false);
return await ConstructHttp11ConnectionAsync(async, stream, transportContext, request, activity, remoteEndPoint, connectionId, cancellationToken).ConfigureAwait(false);
}

private async ValueTask<HttpConnection> ConstructHttp11ConnectionAsync(bool async, Stream stream, TransportContext? transportContext, HttpRequestMessage request, Activity? activity, IPEndPoint? remoteEndPoint, CancellationToken cancellationToken)
private async ValueTask<HttpConnection> ConstructHttp11ConnectionAsync(bool async, Stream stream, TransportContext? transportContext, HttpRequestMessage request, Activity? activity, IPEndPoint? remoteEndPoint, long connectionId, CancellationToken cancellationToken)
{
Stream newStream = await ApplyPlaintextFilterAsync(async, stream, HttpVersion.Version11, request, cancellationToken).ConfigureAwait(false);
return new HttpConnection(this, newStream, transportContext, activity, remoteEndPoint);
Stream newStream = await ApplyPlaintextFilterAsync(async, stream, HttpVersion.Version11, request, connectionId, cancellationToken).ConfigureAwait(false);
return new HttpConnection(this, newStream, transportContext, activity, remoteEndPoint, connectionId);
}

private void HandleHttp11ConnectionFailure(HttpConnectionWaiter<HttpConnection>? requestWaiter, Exception e)
Expand Down Expand Up @@ -368,6 +368,11 @@ private void ReturnHttp11Connection(HttpConnection connection)
{
connection.MarkConnectionAsIdle();

// Eviction callback checks are normally triggered from a background timer that looks at all idle connections.
// If this connection was in use during that time, the eviction callback may have been skipped for it.
// Check whether that's the case now by comparing the last EvictionGeneration of the connection with that of the pool.
connection.RunEvictionEvaluationIfNeeded();

// The fast path when there are enough connections and no pending requests
// is that we'll see _http11RequestQueueIsEmptyAndNotDisposed being true both
// times, and all we'll have to do as part of returning the connection is
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ private async Task InjectNewHttp2ConnectionAsync(RequestQueue<Http2Connection?>.
CancellationTokenSource cts = GetConnectTimeoutCancellationTokenSource(waiter);
try
{
(Stream stream, TransportContext? transportContext, Activity? activity, IPEndPoint? remoteEndPoint) = await ConnectAsync(queueItem.Request, true, isForHttp2: true, cts.Token).ConfigureAwait(false);
(Stream stream, TransportContext? transportContext, Activity? activity, IPEndPoint? remoteEndPoint, long connectionId) = await ConnectAsync(queueItem.Request, true, isForHttp2: true, cts.Token).ConfigureAwait(false);

if (IsSecure)
{
Expand All @@ -202,19 +202,19 @@ private async Task InjectNewHttp2ConnectionAsync(RequestQueue<Http2Connection?>.
}
else
{
connection = await ConstructHttp2ConnectionAsync(stream, queueItem.Request, activity, remoteEndPoint, cts.Token).ConfigureAwait(false);
connection = await ConstructHttp2ConnectionAsync(stream, queueItem.Request, activity, remoteEndPoint, connectionId, cts.Token).ConfigureAwait(false);
}
}
else
{
// We established an SSL connection, but the server denied our request for HTTP2.
await HandleHttp11Downgrade(queueItem.Request, stream, transportContext, activity, remoteEndPoint, cts.Token).ConfigureAwait(false);
await HandleHttp11Downgrade(queueItem.Request, stream, transportContext, activity, remoteEndPoint, connectionId, cts.Token).ConfigureAwait(false);
return;
}
}
else
{
connection = await ConstructHttp2ConnectionAsync(stream, queueItem.Request, activity, remoteEndPoint, cts.Token).ConfigureAwait(false);
connection = await ConstructHttp2ConnectionAsync(stream, queueItem.Request, activity, remoteEndPoint, connectionId, cts.Token).ConfigureAwait(false);
}
}
catch (Exception e)
Expand Down Expand Up @@ -244,11 +244,11 @@ private async Task InjectNewHttp2ConnectionAsync(RequestQueue<Http2Connection?>.
}
}

private async ValueTask<Http2Connection> ConstructHttp2ConnectionAsync(Stream stream, HttpRequestMessage request, Activity? activity, IPEndPoint? remoteEndPoint, CancellationToken cancellationToken)
private async ValueTask<Http2Connection> ConstructHttp2ConnectionAsync(Stream stream, HttpRequestMessage request, Activity? activity, IPEndPoint? remoteEndPoint, long connectionId, CancellationToken cancellationToken)
{
stream = await ApplyPlaintextFilterAsync(async: true, stream, HttpVersion.Version20, request, cancellationToken).ConfigureAwait(false);
stream = await ApplyPlaintextFilterAsync(async: true, stream, HttpVersion.Version20, request, connectionId, cancellationToken).ConfigureAwait(false);

Http2Connection http2Connection = new Http2Connection(this, stream, activity, remoteEndPoint);
Http2Connection http2Connection = new Http2Connection(this, stream, activity, remoteEndPoint, connectionId);
try
{
await http2Connection.SetupAsync(cancellationToken).ConfigureAwait(false);
Expand Down Expand Up @@ -299,7 +299,7 @@ internal void OnSessionAuthenticationChallengeSeen()
_http2SessionAuthSeen = true;
}

private async Task HandleHttp11Downgrade(HttpRequestMessage request, Stream stream, TransportContext? transportContext, Activity? activity, IPEndPoint? remoteEndPoint, CancellationToken cancellationToken)
private async Task HandleHttp11Downgrade(HttpRequestMessage request, Stream stream, TransportContext? transportContext, Activity? activity, IPEndPoint? remoteEndPoint, long connectionId, CancellationToken cancellationToken)
{
if (NetEventSource.Log.IsEnabled()) Trace("Server does not support HTTP2; disabling HTTP2 use and proceeding with HTTP/1.1 connection");

Expand Down Expand Up @@ -357,7 +357,7 @@ private async Task HandleHttp11Downgrade(HttpRequestMessage request, Stream stre
try
{
// Note, the same CancellationToken from the original HTTP2 connection establishment still applies here.
http11Connection = await ConstructHttp11ConnectionAsync(true, stream, transportContext, request, activity, remoteEndPoint, cancellationToken).ConfigureAwait(false);
http11Connection = await ConstructHttp11ConnectionAsync(true, stream, transportContext, request, activity, remoteEndPoint, connectionId, cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException oce) when (oce.CancellationToken == cancellationToken)
{
Expand Down
Loading
Loading