namespace System.Net.Http
{
- internal partial class HttpConnection : IDisposable
+ internal partial class HttpConnection : HttpConnectionBase, IDisposable
{
/// <summary>Default size of the read buffer used for the connection.</summary>
private const int InitialReadBufferSize =
return null;
}
- public bool IsNewConnection
- {
- get
- {
- // This is only valid when we are not actually processing a request.
- Debug.Assert(_currentRequest == null);
- return (_readAheadTask == null);
- }
- }
-
- public bool CanRetry
- {
- get
- {
- // Should only be called when we have been disposed.
- Debug.Assert(_disposed != 0);
- return _canRetry;
- }
- }
-
public DateTimeOffset CreationTime { get; } = DateTimeOffset.UtcNow;
public TransportContext TransportContext => _transportContext;
// original information as the inner exception, for diagnostic purposes.
throw CancellationHelper.CreateOperationCanceledException(error, cancellationToken);
}
- else if (error is InvalidOperationException || error is IOException)
+ else if (error is InvalidOperationException)
{
- // If it's an InvalidOperationException or an IOException, for consistency
- // with other handlers we wrap the exception in an HttpRequestException.
+ // For consistency with other handlers we wrap the exception in an HttpRequestException.
throw new HttpRequestException(SR.net_http_client_execution_error, error);
}
+ else if (error is IOException ioe)
+ {
+ // For consistency with other handlers we wrap the exception in an HttpRequestException.
+ // If the request is retryable, indicate that on the exception.
+ throw new HttpRequestException(SR.net_http_client_execution_error, ioe, _canRetry);
+ }
else
{
// Otherwise, just allow the original exception to propagate.
return SendAsyncCore(request, cancellationToken);
}
- public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, bool doRequestAuth, CancellationToken cancellationToken)
+ private Task<HttpResponseMessage> SendAsyncInternal(HttpRequestMessage request, bool doRequestAuth, CancellationToken cancellationToken)
{
if (doRequestAuth && _pool.Settings._credentials != null)
{
return SendWithNtProxyAuthAsync(request, cancellationToken);
}
+ public sealed override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, bool doRequestAuth, CancellationToken cancellationToken)
+ {
+ Acquire();
+ try
+ {
+ return await SendAsyncInternal(request, doRequestAuth, cancellationToken).ConfigureAwait(false);
+ }
+ finally
+ {
+ Release();
+ }
+ }
+
private HttpContentWriteStream CreateRequestContentStream(HttpRequestMessage request)
{
bool requestTransferEncodingChunked = request.HasHeaders && request.Headers.TransferEncodingChunked == true;
}
}
- public void Acquire()
+ private void Acquire()
{
Debug.Assert(_currentRequest == null);
Debug.Assert(!_inUse);
_inUse = true;
}
- public void Release()
+ private void Release()
{
Debug.Assert(_inUse);
/// <summary>Object used to synchronize access to state in the pool.</summary>
private object SyncObj => _idleConnections;
- private ValueTask<(HttpConnection, HttpResponseMessage)> GetConnectionAsync(HttpRequestMessage request, CancellationToken cancellationToken)
+ private async ValueTask<(HttpConnectionBase connection, bool isNewConnection, HttpResponseMessage failureResponse)>
+ GetConnectionAsync(HttpRequestMessage request, CancellationToken cancellationToken)
+ {
+ (HttpConnection connection, bool isNewConnection, HttpResponseMessage failureResponse) = await GetHttp11ConnectionAsync(request, cancellationToken);
+ return ((HttpConnectionBase)connection, isNewConnection, failureResponse);
+ }
+
+ private ValueTask<(HttpConnection connection, bool isNewConnection, HttpResponseMessage failureResponse)> GetHttp11ConnectionAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
{
- return new ValueTask<(HttpConnection, HttpResponseMessage)>(Task.FromCanceled<(HttpConnection, HttpResponseMessage)>(cancellationToken));
+ return new ValueTask<(HttpConnection, bool, HttpResponseMessage)>(Task.FromCanceled<(HttpConnection, bool, HttpResponseMessage)>(cancellationToken));
}
TimeSpan pooledConnectionLifetime = _poolManager.Settings._pooledConnectionLifetime;
{
// We found a valid connection. Return it.
if (NetEventSource.IsEnabled) conn.Trace("Found usable connection in pool.");
- return new ValueTask<(HttpConnection, HttpResponseMessage)>((conn, null));
+ return new ValueTask<(HttpConnection, bool, HttpResponseMessage)>((conn, false, null));
}
// We got a connection, but it was already closed by the server or the
}
}, waiter);
}
- return new ValueTask<(HttpConnection, HttpResponseMessage)>(waiter.Task);
+ return new ValueTask<(HttpConnection, bool, HttpResponseMessage)>(waiter.Task);
}
// Note that we don't check for _disposed. We may end up disposing the
{
// Loop on connection failures and retry if possible.
- (HttpConnection connection, HttpResponseMessage response) = await GetConnectionAsync(request, cancellationToken).ConfigureAwait(false);
- if (response != null)
+ (HttpConnectionBase connection, bool isNewConnection, HttpResponseMessage failureResponse) = await GetConnectionAsync(request, cancellationToken).ConfigureAwait(false);
+ if (failureResponse != null)
{
// Proxy tunnel failure; return proxy response
- return response;
+ Debug.Assert(isNewConnection);
+ Debug.Assert(connection == null);
+ return failureResponse;
}
- bool isNewConnection = connection.IsNewConnection;
-
- connection.Acquire();
try
{
return await connection.SendAsync(request, doRequestAuth, cancellationToken).ConfigureAwait(false);
}
- catch (HttpRequestException e) when (!isNewConnection && e.InnerException is IOException && connection.CanRetry)
+ catch (HttpRequestException e) when (!isNewConnection && e.AllowRetry)
{
// Eat exception and try again.
}
- finally
- {
- connection.Release();
- }
}
}
}
/// <summary>Waits for and returns the created connection, decrementing the associated connection count if it fails.</summary>
- private async ValueTask<(HttpConnection, HttpResponseMessage)> WaitForCreatedConnectionAsync(ValueTask<(HttpConnection, HttpResponseMessage)> creationTask)
+ private async ValueTask<(HttpConnection connection, bool isNewConnection, HttpResponseMessage failureResponse)> WaitForCreatedConnectionAsync(ValueTask<(HttpConnection, HttpResponseMessage)> creationTask)
{
try
{
{
DecrementConnectionCount();
}
- return (connection, response);
+ return (connection, true, response);
}
catch
{
// Transfer the connection to the waiter. Since we already have a count
// that's inflated due to the connection being disassociated, we don't
// need to change the count here.
- waiter.SetResult(connectionTask.Result);
+ (HttpConnection connection, HttpResponseMessage failureResponse) = connectionTask.Result;
+ waiter.SetResult((connection, true, failureResponse));
}
else
{
try
{
// Get the resulting connection.
- (HttpConnection result, HttpResponseMessage response) = innerConnectionTask.GetAwaiter().GetResult();
+ (HttpConnection connection, HttpResponseMessage failureResponse) = innerConnectionTask.GetAwaiter().GetResult();
- if (response != null)
+ if (failureResponse != null)
{
+ Debug.Assert(connection == null);
+
// Proxy tunnel connect failed, so decrement the connection count.
innerWaiter._pool.DecrementConnectionCount();
}
// Store the resulting connection into the waiter. As in the synchronous case,
// since we already have a count that's inflated due to the connection being
// disassociated, we don't need to change the count here.
- innerWaiter.SetResult(innerConnectionTask.Result);
+ innerWaiter.SetResult((connection, true, failureResponse));
}
catch (Exception e)
{
/// into the waiter as a result, and if no connection is available from the pool,
/// this waiter's logic is used to create the connection.
/// </summary>
- private class ConnectionWaiter : TaskCompletionSource<(HttpConnection, HttpResponseMessage)>
+ private class ConnectionWaiter : TaskCompletionSource<(HttpConnection connection, bool isNewConnection, HttpResponseMessage failureResponse)>
{
/// <summary>The pool with which this waiter is associated.</summary>
internal readonly HttpConnectionPool _pool;