@@ -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