Skip to content

Commit f4e10ed

Browse files
[release/6.0] Remove two async state machines for typical HTTP/1.1 request path (#58252)
* Remove two async state machines for typical HTTP/1.1 request path * Remove unused doRequestAuth parameter for HTTP/2 and HTTP/3 * Inline HTTP/1.x handling into SendWithRetryAsync * Inline HTTP/2 as well * Add back assert Co-authored-by: Stephen Toub <stoub@microsoft.com>
1 parent 6493e83 commit f4e10ed

File tree

2 files changed

+86
-111
lines changed

2 files changed

+86
-111
lines changed

src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ private static async ValueTask<bool> TrySetDigestAuthToken(HttpRequestMessage re
206206
private static ValueTask<HttpResponseMessage> InnerSendAsync(HttpRequestMessage request, bool async, bool isProxyAuth, bool doRequestAuth, HttpConnectionPool pool, CancellationToken cancellationToken)
207207
{
208208
return isProxyAuth ?
209-
pool.SendWithRetryAsync(request, async, doRequestAuth, cancellationToken) :
209+
pool.SendWithVersionDetectionAndRetryAsync(request, async, doRequestAuth, cancellationToken) :
210210
pool.SendWithProxyAuthAsync(request, async, doRequestAuth, cancellationToken);
211211
}
212212

src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs

Lines changed: 85 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -413,11 +413,12 @@ private object SyncObj
413413
// we will remove it from the list of available connections, if it is present there.
414414
// If not, then it must be unavailable at the moment; we will detect this and ensure it is not added back to the available pool.
415415

416-
private static HttpRequestException GetVersionException(HttpRequestMessage request, int desiredVersion)
416+
[DoesNotReturn]
417+
private static void ThrowGetVersionException(HttpRequestMessage request, int desiredVersion)
417418
{
418419
Debug.Assert(desiredVersion == 2 || desiredVersion == 3);
419420

420-
return new HttpRequestException(SR.Format(SR.net_http_requested_version_cannot_establish, request.Version, request.VersionPolicy, desiredVersion));
421+
throw new HttpRequestException(SR.Format(SR.net_http_requested_version_cannot_establish, request.Version, request.VersionPolicy, desiredVersion));
421422
}
422423

423424
private bool CheckExpirationOnGet(HttpConnectionBase connection)
@@ -887,144 +888,118 @@ private async ValueTask<Http3Connection> GetHttp3ConnectionAsync(HttpRequestMess
887888
[SupportedOSPlatform("windows")]
888889
[SupportedOSPlatform("linux")]
889890
[SupportedOSPlatform("macos")]
890-
private async ValueTask<HttpResponseMessage?> TrySendUsingHttp3Async(HttpRequestMessage request, bool async, bool doRequestAuth, CancellationToken cancellationToken)
891+
private async ValueTask<HttpResponseMessage?> TrySendUsingHttp3Async(HttpRequestMessage request, bool async, CancellationToken cancellationToken)
891892
{
892-
if (_http3Enabled && (request.Version.Major >= 3 || (request.VersionPolicy == HttpVersionPolicy.RequestVersionOrHigher && IsSecure)))
893+
// Loop in case we get a 421 and need to send the request to a different authority.
894+
while (true)
893895
{
894-
// Loop in case we get a 421 and need to send the request to a different authority.
895-
while (true)
896-
{
897-
HttpAuthority? authority = _http3Authority;
898-
899-
// If H3 is explicitly requested, assume prenegotiated H3.
900-
if (request.Version.Major >= 3 && request.VersionPolicy != HttpVersionPolicy.RequestVersionOrLower)
901-
{
902-
authority = authority ?? _originAuthority;
903-
}
904-
905-
if (authority == null)
906-
{
907-
break;
908-
}
909-
910-
if (IsAltSvcBlocked(authority))
911-
{
912-
throw GetVersionException(request, 3);
913-
}
914-
915-
Http3Connection connection = await GetHttp3ConnectionAsync(request, authority, cancellationToken).ConfigureAwait(false);
916-
HttpResponseMessage response = await connection.SendAsync(request, async, cancellationToken).ConfigureAwait(false);
917-
918-
// If an Alt-Svc authority returns 421, it means it can't actually handle the request.
919-
// An authority is supposed to be able to handle ALL requests to the origin, so this is a server bug.
920-
// In this case, we blocklist the authority and retry the request at the origin.
921-
if (response.StatusCode == HttpStatusCode.MisdirectedRequest && connection.Authority != _originAuthority)
922-
{
923-
response.Dispose();
924-
BlocklistAuthority(connection.Authority);
925-
continue;
926-
}
896+
HttpAuthority? authority = _http3Authority;
927897

928-
return response;
898+
// If H3 is explicitly requested, assume prenegotiated H3.
899+
if (request.Version.Major >= 3 && request.VersionPolicy != HttpVersionPolicy.RequestVersionOrLower)
900+
{
901+
authority ??= _originAuthority;
929902
}
930-
}
931-
932-
return null;
933-
}
934903

935-
// Returns null if HTTP2 cannot be used.
936-
private async ValueTask<HttpResponseMessage?> TrySendUsingHttp2Async(HttpRequestMessage request, bool async, bool doRequestAuth, CancellationToken cancellationToken)
937-
{
938-
// Send using HTTP/2 if we can.
939-
if (_http2Enabled && (request.Version.Major >= 2 || (request.VersionPolicy == HttpVersionPolicy.RequestVersionOrHigher && IsSecure)) &&
940-
// If the connection is not secured and downgrade is possible, prefer HTTP/1.1.
941-
(request.VersionPolicy != HttpVersionPolicy.RequestVersionOrLower || IsSecure))
942-
{
943-
Http2Connection? connection = await GetHttp2ConnectionAsync(request, async, cancellationToken).ConfigureAwait(false);
944-
if (connection is null)
904+
if (authority == null)
945905
{
946-
Debug.Assert(!_http2Enabled);
947906
return null;
948907
}
949908

950-
return await connection.SendAsync(request, async, cancellationToken).ConfigureAwait(false);
951-
}
952-
953-
return null;
954-
}
955-
956-
private async ValueTask<HttpResponseMessage> SendUsingHttp11Async(HttpRequestMessage request, bool async, bool doRequestAuth, CancellationToken cancellationToken)
957-
{
958-
HttpConnection connection = await GetHttp11ConnectionAsync(request, async, cancellationToken).ConfigureAwait(false);
959-
960-
// In case we are doing Windows (i.e. connection-based) auth, we need to ensure that we hold on to this specific connection while auth is underway.
961-
connection.Acquire();
962-
try
963-
{
964-
return await SendWithNtConnectionAuthAsync(connection, request, async, doRequestAuth, cancellationToken).ConfigureAwait(false);
965-
}
966-
finally
967-
{
968-
connection.Release();
969-
}
970-
}
909+
if (IsAltSvcBlocked(authority))
910+
{
911+
ThrowGetVersionException(request, 3);
912+
}
971913

972-
private async ValueTask<HttpResponseMessage> DetermineVersionAndSendAsync(HttpRequestMessage request, bool async, bool doRequestAuth, CancellationToken cancellationToken)
973-
{
974-
HttpResponseMessage? response;
914+
Http3Connection connection = await GetHttp3ConnectionAsync(request, authority, cancellationToken).ConfigureAwait(false);
915+
HttpResponseMessage response = await connection.SendAsync(request, async, cancellationToken).ConfigureAwait(false);
975916

976-
if (IsHttp3Supported())
977-
{
978-
response = await TrySendUsingHttp3Async(request, async, doRequestAuth, cancellationToken).ConfigureAwait(false);
979-
if (response is not null)
917+
// If an Alt-Svc authority returns 421, it means it can't actually handle the request.
918+
// An authority is supposed to be able to handle ALL requests to the origin, so this is a server bug.
919+
// In this case, we blocklist the authority and retry the request at the origin.
920+
if (response.StatusCode == HttpStatusCode.MisdirectedRequest && connection.Authority != _originAuthority)
980921
{
981-
return response;
922+
response.Dispose();
923+
BlocklistAuthority(connection.Authority);
924+
continue;
982925
}
983-
}
984-
985-
// We cannot use HTTP/3. Do not continue if downgrade is not allowed.
986-
if (request.Version.Major >= 3 && request.VersionPolicy != HttpVersionPolicy.RequestVersionOrLower)
987-
{
988-
throw GetVersionException(request, 3);
989-
}
990926

991-
response = await TrySendUsingHttp2Async(request, async, doRequestAuth, cancellationToken).ConfigureAwait(false);
992-
if (response is not null)
993-
{
994927
return response;
995928
}
996-
997-
// We cannot use HTTP/2. Do not continue if downgrade is not allowed.
998-
if (request.Version.Major >= 2 && request.VersionPolicy != HttpVersionPolicy.RequestVersionOrLower)
999-
{
1000-
throw GetVersionException(request, 2);
1001-
}
1002-
1003-
return await SendUsingHttp11Async(request, async, doRequestAuth, cancellationToken).ConfigureAwait(false);
1004929
}
1005930

1006-
private async ValueTask<HttpResponseMessage> SendAndProcessAltSvcAsync(HttpRequestMessage request, bool async, bool doRequestAuth, CancellationToken cancellationToken)
931+
/// <summary>Check for the Alt-Svc header, to upgrade to HTTP/3.</summary>
932+
private void ProcessAltSvc(HttpResponseMessage response)
1007933
{
1008-
HttpResponseMessage response = await DetermineVersionAndSendAsync(request, async, doRequestAuth, cancellationToken).ConfigureAwait(false);
1009-
1010-
// Check for the Alt-Svc header, to upgrade to HTTP/3.
1011934
if (_altSvcEnabled && response.Headers.TryGetValues(KnownHeaders.AltSvc.Descriptor, out IEnumerable<string>? altSvcHeaderValues))
1012935
{
1013936
HandleAltSvc(altSvcHeaderValues, response.Headers.Age);
1014937
}
1015-
1016-
return response;
1017938
}
1018939

1019-
public async ValueTask<HttpResponseMessage> SendWithRetryAsync(HttpRequestMessage request, bool async, bool doRequestAuth, CancellationToken cancellationToken)
940+
public async ValueTask<HttpResponseMessage> SendWithVersionDetectionAndRetryAsync(HttpRequestMessage request, bool async, bool doRequestAuth, CancellationToken cancellationToken)
1020941
{
942+
// Loop on connection failures (or other problems like version downgrade) and retry if possible.
1021943
int retryCount = 0;
1022944
while (true)
1023945
{
1024-
// Loop on connection failures (or other problems like version downgrade) and retry if possible.
1025946
try
1026947
{
1027-
return await SendAndProcessAltSvcAsync(request, async, doRequestAuth, cancellationToken).ConfigureAwait(false);
948+
HttpResponseMessage? response = null;
949+
950+
// Use HTTP/3 if possible.
951+
if (IsHttp3Supported() && // guard to enable trimming HTTP/3 support
952+
_http3Enabled &&
953+
(request.Version.Major >= 3 || (request.VersionPolicy == HttpVersionPolicy.RequestVersionOrHigher && IsSecure)))
954+
{
955+
response = await TrySendUsingHttp3Async(request, async, cancellationToken).ConfigureAwait(false);
956+
}
957+
958+
if (response is null)
959+
{
960+
// We could not use HTTP/3. Do not continue if downgrade is not allowed.
961+
if (request.Version.Major >= 3 && request.VersionPolicy != HttpVersionPolicy.RequestVersionOrLower)
962+
{
963+
ThrowGetVersionException(request, 3);
964+
}
965+
966+
// Use HTTP/2 if possible.
967+
if (_http2Enabled &&
968+
(request.Version.Major >= 2 || (request.VersionPolicy == HttpVersionPolicy.RequestVersionOrHigher && IsSecure)) &&
969+
(request.VersionPolicy != HttpVersionPolicy.RequestVersionOrLower || IsSecure)) // prefer HTTP/1.1 if connection is not secured and downgrade is possible
970+
{
971+
Http2Connection? connection = await GetHttp2ConnectionAsync(request, async, cancellationToken).ConfigureAwait(false);
972+
Debug.Assert(connection is not null || !_http2Enabled);
973+
if (connection is not null)
974+
{
975+
response = await connection.SendAsync(request, async, cancellationToken).ConfigureAwait(false);
976+
}
977+
}
978+
979+
if (response is null)
980+
{
981+
// We could not use HTTP/2. Do not continue if downgrade is not allowed.
982+
if (request.Version.Major >= 2 && request.VersionPolicy != HttpVersionPolicy.RequestVersionOrLower)
983+
{
984+
ThrowGetVersionException(request, 2);
985+
}
986+
987+
// Use HTTP/1.x.
988+
HttpConnection connection = await GetHttp11ConnectionAsync(request, async, cancellationToken).ConfigureAwait(false);
989+
connection.Acquire(); // In case we are doing Windows (i.e. connection-based) auth, we need to ensure that we hold on to this specific connection while auth is underway.
990+
try
991+
{
992+
response = await SendWithNtConnectionAuthAsync(connection, request, async, doRequestAuth, cancellationToken).ConfigureAwait(false);
993+
}
994+
finally
995+
{
996+
connection.Release();
997+
}
998+
}
999+
}
1000+
1001+
ProcessAltSvc(response);
1002+
return response;
10281003
}
10291004
catch (HttpRequestException e) when (e.AllowRetry == RequestRetryType.RetryOnConnectionFailure)
10301005
{
@@ -1332,7 +1307,7 @@ public ValueTask<HttpResponseMessage> SendWithProxyAuthAsync(HttpRequestMessage
13321307
return AuthenticationHelper.SendWithProxyAuthAsync(request, _proxyUri!, async, ProxyCredentials, doRequestAuth, this, cancellationToken);
13331308
}
13341309

1335-
return SendWithRetryAsync(request, async, doRequestAuth, cancellationToken);
1310+
return SendWithVersionDetectionAndRetryAsync(request, async, doRequestAuth, cancellationToken);
13361311
}
13371312

13381313
public ValueTask<HttpResponseMessage> SendAsync(HttpRequestMessage request, bool async, bool doRequestAuth, CancellationToken cancellationToken)

0 commit comments

Comments
 (0)