Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove two async state machines for typical HTTP/1.1 request path #58092

Merged
merged 5 commits into from
Aug 27, 2021
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ private static async ValueTask<bool> TrySetDigestAuthToken(HttpRequestMessage re
private static ValueTask<HttpResponseMessage> InnerSendAsync(HttpRequestMessage request, bool async, bool isProxyAuth, bool doRequestAuth, HttpConnectionPool pool, CancellationToken cancellationToken)
{
return isProxyAuth ?
pool.SendWithRetryAsync(request, async, doRequestAuth, cancellationToken) :
pool.SendWithVersionDetectionAndRetryAsync(request, async, doRequestAuth, cancellationToken) :
pool.SendWithProxyAuthAsync(request, async, doRequestAuth, cancellationToken);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -413,11 +413,12 @@ private object SyncObj
// we will remove it from the list of available connections, if it is present there.
// 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.

private static HttpRequestException GetVersionException(HttpRequestMessage request, int desiredVersion)
[DoesNotReturn]
private static void ThrowGetVersionException(HttpRequestMessage request, int desiredVersion)
{
Debug.Assert(desiredVersion == 2 || desiredVersion == 3);

return new HttpRequestException(SR.Format(SR.net_http_requested_version_cannot_establish, request.Version, request.VersionPolicy, desiredVersion));
throw new HttpRequestException(SR.Format(SR.net_http_requested_version_cannot_establish, request.Version, request.VersionPolicy, desiredVersion));
}

private bool CheckExpirationOnGet(HttpConnectionBase connection)
Expand Down Expand Up @@ -887,9 +888,7 @@ private async ValueTask<Http3Connection> GetHttp3ConnectionAsync(HttpRequestMess
[SupportedOSPlatform("windows")]
[SupportedOSPlatform("linux")]
[SupportedOSPlatform("macos")]
private async ValueTask<HttpResponseMessage?> TrySendUsingHttp3Async(HttpRequestMessage request, bool async, bool doRequestAuth, CancellationToken cancellationToken)
{
if (_http3Enabled && (request.Version.Major >= 3 || (request.VersionPolicy == HttpVersionPolicy.RequestVersionOrHigher && IsSecure)))
private async ValueTask<HttpResponseMessage?> TrySendUsingHttp3Async(HttpRequestMessage request, bool async, CancellationToken cancellationToken)
{
// Loop in case we get a 421 and need to send the request to a different authority.
while (true)
Expand All @@ -899,17 +898,17 @@ private async ValueTask<Http3Connection> GetHttp3ConnectionAsync(HttpRequestMess
// If H3 is explicitly requested, assume prenegotiated H3.
if (request.Version.Major >= 3 && request.VersionPolicy != HttpVersionPolicy.RequestVersionOrLower)
{
authority = authority ?? _originAuthority;
authority ??= _originAuthority;
}

if (authority == null)
{
break;
return null;
}

if (IsAltSvcBlocked(authority))
{
throw GetVersionException(request, 3);
ThrowGetVersionException(request, 3);
}

Http3Connection connection = await GetHttp3ConnectionAsync(request, authority, cancellationToken).ConfigureAwait(false);
Expand All @@ -929,103 +928,79 @@ private async ValueTask<Http3Connection> GetHttp3ConnectionAsync(HttpRequestMess
}
}

return null;
}

// Returns null if HTTP2 cannot be used.
private async ValueTask<HttpResponseMessage?> TrySendUsingHttp2Async(HttpRequestMessage request, bool async, bool doRequestAuth, CancellationToken cancellationToken)
/// <summary>Check for the Alt-Svc header, to upgrade to HTTP/3.</summary>
private void ProcessAltSvc(HttpResponseMessage response)
{
// Send using HTTP/2 if we can.
if (_http2Enabled && (request.Version.Major >= 2 || (request.VersionPolicy == HttpVersionPolicy.RequestVersionOrHigher && IsSecure)) &&
// If the connection is not secured and downgrade is possible, prefer HTTP/1.1.
(request.VersionPolicy != HttpVersionPolicy.RequestVersionOrLower || IsSecure))
{
Http2Connection? connection = await GetHttp2ConnectionAsync(request, async, cancellationToken).ConfigureAwait(false);
if (connection is null)
if (_altSvcEnabled && response.Headers.TryGetValues(KnownHeaders.AltSvc.Descriptor, out IEnumerable<string>? altSvcHeaderValues))
{
Debug.Assert(!_http2Enabled);
stephentoub marked this conversation as resolved.
Show resolved Hide resolved
return null;
}

return await connection.SendAsync(request, async, cancellationToken).ConfigureAwait(false);
HandleAltSvc(altSvcHeaderValues, response.Headers.Age);
}

return null;
}

private async ValueTask<HttpResponseMessage> SendUsingHttp11Async(HttpRequestMessage request, bool async, bool doRequestAuth, CancellationToken cancellationToken)
public async ValueTask<HttpResponseMessage> SendWithVersionDetectionAndRetryAsync(HttpRequestMessage request, bool async, bool doRequestAuth, CancellationToken cancellationToken)
{
// Loop on connection failures (or other problems like version downgrade) and retry if possible.
int retryCount = 0;
while (true)
{
HttpConnection connection = await GetHttp11ConnectionAsync(request, async, cancellationToken).ConfigureAwait(false);

// 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.
connection.Acquire();
try
{
return await SendWithNtConnectionAuthAsync(connection, request, async, doRequestAuth, cancellationToken).ConfigureAwait(false);
}
finally
HttpResponseMessage? response = null;

// Use HTTP/3 if possible.
if (IsHttp3Supported() && // guard to enable trimming HTTP/3 support
_http3Enabled &&
(request.Version.Major >= 3 || (request.VersionPolicy == HttpVersionPolicy.RequestVersionOrHigher && IsSecure)))
{
connection.Release();
}
response = await TrySendUsingHttp3Async(request, async, cancellationToken).ConfigureAwait(false);
}

private async ValueTask<HttpResponseMessage> DetermineVersionAndSendAsync(HttpRequestMessage request, bool async, bool doRequestAuth, CancellationToken cancellationToken)
if (response is null)
stephentoub marked this conversation as resolved.
Show resolved Hide resolved
{
HttpResponseMessage? response;
// We could not use HTTP/3. Do not continue if downgrade is not allowed.
if (request.Version.Major >= 3 && request.VersionPolicy != HttpVersionPolicy.RequestVersionOrLower)
{
ThrowGetVersionException(request, 3);
}

if (IsHttp3Supported())
// Use HTTP/2 if possible.
if (_http2Enabled &&
(request.Version.Major >= 2 || (request.VersionPolicy == HttpVersionPolicy.RequestVersionOrHigher && IsSecure)) &&
(request.VersionPolicy != HttpVersionPolicy.RequestVersionOrLower || IsSecure)) // prefer HTTP/1.1 if connection is not secured and downgrade is possible
{
response = await TrySendUsingHttp3Async(request, async, doRequestAuth, cancellationToken).ConfigureAwait(false);
if (response is not null)
Http2Connection? connection = await GetHttp2ConnectionAsync(request, async, cancellationToken).ConfigureAwait(false);
Debug.Assert(connection is not null || !_http2Enabled);
if (connection is not null)
{
return response;
response = await connection.SendAsync(request, async, cancellationToken).ConfigureAwait(false);
}
}

// We cannot use HTTP/3. Do not continue if downgrade is not allowed.
if (request.Version.Major >= 3 && request.VersionPolicy != HttpVersionPolicy.RequestVersionOrLower)
if (response is null)
{
throw GetVersionException(request, 3);
// We could not use HTTP/2. Do not continue if downgrade is not allowed.
if (request.Version.Major >= 2 && request.VersionPolicy != HttpVersionPolicy.RequestVersionOrLower)
{
ThrowGetVersionException(request, 2);
}

response = await TrySendUsingHttp2Async(request, async, doRequestAuth, cancellationToken).ConfigureAwait(false);
if (response is not null)
// Use HTTP/1.x.
HttpConnection connection = await GetHttp11ConnectionAsync(request, async, cancellationToken).ConfigureAwait(false);
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.
try
{
return response;
response = await SendWithNtConnectionAuthAsync(connection, request, async, doRequestAuth, cancellationToken).ConfigureAwait(false);
}

// We cannot use HTTP/2. Do not continue if downgrade is not allowed.
if (request.Version.Major >= 2 && request.VersionPolicy != HttpVersionPolicy.RequestVersionOrLower)
finally
{
throw GetVersionException(request, 2);
connection.Release();
}

return await SendUsingHttp11Async(request, async, doRequestAuth, cancellationToken).ConfigureAwait(false);
}

private async ValueTask<HttpResponseMessage> SendAndProcessAltSvcAsync(HttpRequestMessage request, bool async, bool doRequestAuth, CancellationToken cancellationToken)
{
HttpResponseMessage response = await DetermineVersionAndSendAsync(request, async, doRequestAuth, cancellationToken).ConfigureAwait(false);

// Check for the Alt-Svc header, to upgrade to HTTP/3.
if (_altSvcEnabled && response.Headers.TryGetValues(KnownHeaders.AltSvc.Descriptor, out IEnumerable<string>? altSvcHeaderValues))
{
HandleAltSvc(altSvcHeaderValues, response.Headers.Age);
}

ProcessAltSvc(response);
return response;
}

public async ValueTask<HttpResponseMessage> SendWithRetryAsync(HttpRequestMessage request, bool async, bool doRequestAuth, CancellationToken cancellationToken)
{
int retryCount = 0;
while (true)
{
// Loop on connection failures (or other problems like version downgrade) and retry if possible.
try
{
return await SendAndProcessAltSvcAsync(request, async, doRequestAuth, cancellationToken).ConfigureAwait(false);
}
catch (HttpRequestException e) when (e.AllowRetry == RequestRetryType.RetryOnConnectionFailure)
{
Debug.Assert(retryCount >= 0 && retryCount <= MaxConnectionFailureRetries);
Expand Down Expand Up @@ -1332,7 +1307,7 @@ public ValueTask<HttpResponseMessage> SendWithProxyAuthAsync(HttpRequestMessage
return AuthenticationHelper.SendWithProxyAuthAsync(request, _proxyUri!, async, ProxyCredentials, doRequestAuth, this, cancellationToken);
}

return SendWithRetryAsync(request, async, doRequestAuth, cancellationToken);
return SendWithVersionDetectionAndRetryAsync(request, async, doRequestAuth, cancellationToken);
}

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