private static Task <HttpResponseMessage> InnerSendAsync(HttpRequestMessage request, bool isProxyAuth, HttpConnectionPool pool, HttpConnection connection, CancellationToken cancellationToken) { return(isProxyAuth ? connection.SendAsyncCore(request, cancellationToken) : pool.SendWithNtProxyAuthAsync(connection, request, cancellationToken)); }
public static Task <HttpResponseMessage> SendWithNtProxyAuthAsync(HttpRequestMessage request, Uri proxyUri, ICredentials proxyCredentials, HttpConnection connection, HttpConnectionPool connectionPool, CancellationToken cancellationToken) { return(SendWithNtAuthAsync(request, proxyUri, proxyCredentials, isProxyAuth: true, connection, connectionPool, cancellationToken)); }
private ValueTask <(HttpConnection, HttpResponseMessage)> GetConnectionAsync(HttpRequestMessage request, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { return(new ValueTask <(HttpConnection, HttpResponseMessage)>(Task.FromCanceled <(HttpConnection, HttpResponseMessage)>(cancellationToken))); } TimeSpan pooledConnectionLifetime = _poolManager.Settings._pooledConnectionLifetime; TimeSpan pooledConnectionIdleTimeout = _poolManager.Settings._pooledConnectionIdleTimeout; DateTimeOffset now = DateTimeOffset.UtcNow; List <CachedConnection> list = _idleConnections; lock (SyncObj) { // Try to return a cached connection. We need to loop in case the connection // we get from the list is unusable. while (list.Count > 0) { CachedConnection cachedConnection = list[list.Count - 1]; HttpConnection conn = cachedConnection._connection; Debug.Assert(!conn.IsNewConnection); list.RemoveAt(list.Count - 1); if (cachedConnection.IsUsable(now, pooledConnectionLifetime, pooledConnectionIdleTimeout)) { // We found a valid collection. Return it. if (NetEventSource.IsEnabled) { conn.Trace("Found usable connection in pool."); } return(new ValueTask <(HttpConnection, HttpResponseMessage)>((conn, null))); } // We got a connection, but it was already closed by the server or the // server sent unexpected data or the connection is too old. In any case, // we can't use the connection, so get rid of it and try again. if (NetEventSource.IsEnabled) { conn.Trace("Found invalid connection in pool."); } conn.Dispose(); } // No valid cached connections, so we need to create a new one. If // there's no limit on the number of connections associated with this // pool, or if we haven't reached such a limit, simply create a new // connection. if (_associatedConnectionCount < _maxConnections) { if (NetEventSource.IsEnabled) { Trace("Creating new connection for pool."); } IncrementConnectionCountNoLock(); return(WaitForCreatedConnectionAsync(CreateConnectionAsync(request, cancellationToken))); } else { // There is a limit, and we've reached it, which means we need to // wait for a connection to be returned to the pool or for a connection // associated with the pool to be dropped before we can create a // new one. Create a waiter object and register it with the pool; it'll // be signaled with the created connection when one is returned or // space is available and the provided creation func has successfully // created the connection to be used. if (NetEventSource.IsEnabled) { Trace("Limit reached. Waiting to create new connection."); } var waiter = new ConnectionWaiter(this, request, cancellationToken); EnqueueWaiter(waiter); if (cancellationToken.CanBeCanceled) { // If cancellation could be requested, register a callback for it that'll cancel // the waiter and remove the waiter from the queue. Note that this registration needs // to happen under the reentrant lock and after enqueueing the waiter. waiter._cancellationTokenRegistration = cancellationToken.Register(s => { var innerWaiter = (ConnectionWaiter)s; lock (innerWaiter._pool.SyncObj) { // If it's in the list, remove it and cancel it. if (innerWaiter._pool.RemoveWaiterForCancellation(innerWaiter)) { bool canceled = innerWaiter.TrySetCanceled(innerWaiter._cancellationToken); Debug.Assert(canceled); } } }, waiter); } return(new ValueTask <(HttpConnection, HttpResponseMessage)>(waiter.Task)); } // Note that we don't check for _disposed. We may end up disposing the // created connection when it's returned, but we don't want to block use // of the pool if it's already been disposed, as there's a race condition // between getting a pool and someone disposing of it, and we don't want // to complicate the logic about trying to get a different pool when the // retrieved one has been disposed of. In the future we could alternatively // try returning such connections to whatever pool is currently considered // current for that endpoint, if there is one. } }
public HttpContentWriteStream(HttpConnection connection, CancellationToken cancellationToken) { Debug.Assert(connection != null); _connection = connection; RequestCancellationToken = cancellationToken; }
public ChunkedEncodingWriteStream(HttpConnection connection) : base(connection) { }
private int _requestStopCalled; // 0==no, 1==yes public HttpContentStream(HttpConnection connection) { _connection = connection; }
/// <summary> /// Decrements the number of connections associated with the pool. /// If there are waiters on the pool due to having reached the maximum, /// this will instead try to transfer the count to one of them. /// </summary> public void DecrementConnectionCount() { if (NetEventSource.IsEnabled) { Trace(null); } lock (SyncObj) { Debug.Assert(_associatedConnectionCount > 0 && _associatedConnectionCount <= _maxConnections, $"Expected 0 < {_associatedConnectionCount} <= {_maxConnections}"); // Mark the pool as not being stale. _usedSinceLastCleanup = true; if (_waitersHead == null) { // There are no waiters to which the count should logically be transferred, // so simply decrement the count. _associatedConnectionCount--; } else { // There's at least one waiter to which we should try to logically transfer // the associated count. Get the waiter. Debug.Assert(_idleConnections.Count == 0, $"With {_idleConnections} connections, we shouldn't have a waiter."); ConnectionWaiter waiter = DequeueWaiter(); Debug.Assert(waiter != null, "Expected non-null waiter"); Debug.Assert(waiter.Task.Status == TaskStatus.WaitingForActivation, $"Expected {waiter.Task.Status} == {nameof(TaskStatus.WaitingForActivation)}"); waiter._cancellationTokenRegistration.Dispose(); // Having a waiter means there must not be any idle connections, so we need to create // one, and we do so using the logic associated with the waiter. ValueTask <HttpConnection> connectionTask = waiter.CreateConnectionAsync(); if (connectionTask.IsCompletedSuccessfully) { // We synchronously and successfully created a connection (this is rare). // 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); } else { // We initiated a connection creation. When it completes, transfer the result to the waiter. connectionTask.AsTask().ContinueWith((innerConnectionTask, state) => { var innerWaiter = (ConnectionWaiter)state; try { // Get the resulting connection. HttpConnection result = innerConnectionTask.GetAwaiter().GetResult(); // 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); } catch (Exception e) { // The creation operation failed. Store the exception into the waiter. innerWaiter.SetException(e); // At this point, a connection was dropped and we failed to replace it, // which means our connection count still needs to be decremented. innerWaiter._pool.DecrementConnectionCount(); } }, waiter, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); } } } }
private int _disposed; // 0==no, 1==yes public HttpContentReadStream(HttpConnection connection) : base(connection) { }
private void Finish(HttpConnection connection) { // We cannot reuse this connection, so close it. _connection = null; connection.Dispose(); }
public HttpContentWriteStream(HttpConnection connection) : base(connection) =>
public HttpContentDuplexStream(HttpConnection connection) : base(connection) { }
private async Task <HttpResponseMessage> SendWithProxyAsync( Uri proxyUri, HttpRequestMessage request, CancellationToken cancellationToken) { if (proxyUri.Scheme != UriScheme.Http) { throw new InvalidOperationException(SR.net_http_invalid_proxy_scheme); } if (request.RequestUri.Scheme == UriScheme.Https) { // TODO #23136: Implement SSL tunneling through proxy throw new NotImplementedException("no support for SSL tunneling through proxy"); } HttpConnection connection = await GetOrCreateConnection(request, proxyUri).ConfigureAwait(false); HttpResponseMessage response = await connection.SendAsync(request, cancellationToken).ConfigureAwait(false); // Handle proxy authentication if (response.StatusCode == HttpStatusCode.ProxyAuthenticationRequired) { foreach (AuthenticationHeaderValue h in response.Headers.ProxyAuthenticate) { // We only support Basic auth, ignore others if (h.Scheme == AuthenticationHelper.Basic) { NetworkCredential credential = _proxy.Credentials?.GetCredential(proxyUri, AuthenticationHelper.Basic) ?? _defaultCredentials?.GetCredential(proxyUri, AuthenticationHelper.Basic); if (credential != null) { response.Dispose(); request.Headers.ProxyAuthorization = new AuthenticationHeaderValue(AuthenticationHelper.Basic, AuthenticationHelper.GetBasicTokenForCredential(credential)); connection = await GetOrCreateConnection(request, proxyUri).ConfigureAwait(false); response = await connection.SendAsync(request, cancellationToken).ConfigureAwait(false); } break; } else if (h.Scheme == AuthenticationHelper.Digest) { NetworkCredential credential = _proxy.Credentials?.GetCredential(proxyUri, AuthenticationHelper.Digest) ?? _defaultCredentials?.GetCredential(proxyUri, AuthenticationHelper.Digest); if (credential != null) { // Update digest response with new parameter from Proxy-Authenticate AuthenticationHelper.DigestResponse digestResponse = new AuthenticationHelper.DigestResponse(h.Parameter); if (await AuthenticationHelper.TrySetDigestAuthToken(request, credential, digestResponse, HttpKnownHeaderNames.ProxyAuthorization).ConfigureAwait(false)) { response.Dispose(); response = await _innerHandler.SendAsync(request, cancellationToken).ConfigureAwait(false); // Retry in case of nonce timeout in server. if (response.StatusCode == HttpStatusCode.Unauthorized) { foreach (AuthenticationHeaderValue ahv in response.Headers.ProxyAuthenticate) { if (ahv.Scheme == AuthenticationHelper.Digest) { digestResponse = new AuthenticationHelper.DigestResponse(ahv.Parameter); if (AuthenticationHelper.IsServerNonceStale(digestResponse) && await AuthenticationHelper.TrySetDigestAuthToken(request, credential, digestResponse, HttpKnownHeaderNames.ProxyAuthorization).ConfigureAwait(false)) { response.Dispose(); response = await _innerHandler.SendAsync(request, cancellationToken).ConfigureAwait(false); } break; } } } } break; } } } } return(response); }
public ContentLengthWriteStream(HttpConnection connection, CancellationToken cancellationToken) : base(connection, cancellationToken) { }
public static Task <HttpResponseMessage> SendWithNtConnectionAuthAsync(HttpRequestMessage request, ICredentials credentials, HttpConnection connection, HttpConnectionPool connectionPool, CancellationToken cancellationToken) { return(SendWithNtAuthAsync(request, request.RequestUri, credentials, isProxyAuth: false, connection, connectionPool, cancellationToken)); }
/// <summary>Initializes the cached connection and its associated metadata.</summary> /// <param name="connection">The connection.</param> public CachedConnection(HttpConnection connection) { Debug.Assert(connection != null); _connection = connection; _returnedTime = DateTimeOffset.UtcNow; }
private static async Task <HttpResponseMessage> SendWithNtAuthAsync(HttpRequestMessage request, Uri authUri, ICredentials credentials, bool isProxyAuth, HttpConnection connection, HttpConnectionPool connectionPool, CancellationToken cancellationToken) { HttpResponseMessage response = await InnerSendAsync(request, isProxyAuth, connectionPool, connection, cancellationToken).ConfigureAwait(false); if (!isProxyAuth && connection.Kind == HttpConnectionKind.Proxy && !ProxySupportsConnectionAuth(response)) { // Proxy didn't indicate that it supports connection-based auth, so we can't proceed. if (NetEventSource.IsEnabled) { NetEventSource.Error(connection, $"Proxy doesn't support connection-based auth, uri={authUri}"); } return(response); } if (TryGetAuthenticationChallenge(response, isProxyAuth, authUri, credentials, out AuthenticationChallenge challenge)) { if (challenge.AuthenticationType == AuthenticationType.Negotiate || challenge.AuthenticationType == AuthenticationType.Ntlm) { bool isNewConnection = false; bool needDrain = true; try { if (response.Headers.ConnectionClose.GetValueOrDefault()) { // Server is closing the connection and asking us to authenticate on a new connection. (connection, response) = await connectionPool.CreateHttp11ConnectionAsync(request, cancellationToken).ConfigureAwait(false); if (response != null) { return(response); } connectionPool.IncrementConnectionCount(); connection.Acquire(); isNewConnection = true; needDrain = false; } if (NetEventSource.IsEnabled) { NetEventSource.Info(connection, $"Authentication: {challenge.AuthenticationType}, Uri: {authUri.AbsoluteUri.ToString()}"); } // Calculate SPN (Service Principal Name) using the host name of the request. // Use the request's 'Host' header if available. Otherwise, use the request uri. // Ignore the 'Host' header if this is proxy authentication since we need to use // the host name of the proxy itself for SPN calculation. string hostName; if (!isProxyAuth && request.HasHeaders && request.Headers.Host != null) { // Use the host name without any normalization. hostName = request.Headers.Host; if (NetEventSource.IsEnabled) { NetEventSource.Info(connection, $"Authentication: {challenge.AuthenticationType}, Host: {hostName}"); } } else { // Need to use FQDN normalized host so that CNAME's are traversed. // Use DNS to do the forward lookup to an A (host) record. // But skip DNS lookup on IP literals. Otherwise, we would end up // doing an unintended reverse DNS lookup. UriHostNameType hnt = authUri.HostNameType; if (hnt == UriHostNameType.IPv6 || hnt == UriHostNameType.IPv4) { hostName = authUri.IdnHost; } else { IPHostEntry result = await Dns.GetHostEntryAsync(authUri.IdnHost).ConfigureAwait(false); hostName = result.HostName; } } string spn = "HTTP/" + hostName; if (NetEventSource.IsEnabled) { NetEventSource.Info(connection, $"Authentication: {challenge.AuthenticationType}, SPN: {spn}"); } ChannelBinding channelBinding = connection.TransportContext?.GetChannelBinding(ChannelBindingKind.Endpoint); NTAuthentication authContext = new NTAuthentication(isServer: false, challenge.SchemeName, challenge.Credential, spn, ContextFlagsPal.Connection, channelBinding); string challengeData = challenge.ChallengeData; try { while (true) { string challengeResponse = authContext.GetOutgoingBlob(challengeData); if (challengeResponse == null) { // Response indicated denial even after login, so stop processing and return current response. break; } if (needDrain) { await connection.DrainResponseAsync(response).ConfigureAwait(false); } SetRequestAuthenticationHeaderValue(request, new AuthenticationHeaderValue(challenge.SchemeName, challengeResponse), isProxyAuth); response = await InnerSendAsync(request, isProxyAuth, connectionPool, connection, cancellationToken).ConfigureAwait(false); if (authContext.IsCompleted || !TryGetRepeatedChallenge(response, challenge.SchemeName, isProxyAuth, out challengeData)) { break; } needDrain = true; } } finally { authContext.CloseContext(); } } finally { if (isNewConnection) { connection.Release(); } } } } return(response); }
public ContentLengthWriteStream(HttpConnection connection) : base(connection) { }
public ContentLengthReadStream(HttpConnection connection, ulong contentLength) : base(connection) { Debug.Assert(contentLength > 0, "Caller should have checked for 0."); _contentBytesRemaining = contentLength; }
public ChunkedEncodingReadStream(HttpConnection connection, HttpResponseMessage response) : base(connection) { Debug.Assert(response != null, "The HttpResponseMessage cannot be null."); _response = response; }
public ChunkedEncodingReadStream(HttpConnection connection) : base(connection) { _chunkBytesRemaining = 0; }
public RawConnectionStream(HttpConnection connection) : base(connection) { }
public ChunkedEncodingReadStream(HttpConnection connection) : base(connection) { }
public ConnectionCloseReadStream(HttpConnection connection) : base(connection) { }
private async ValueTask <(HttpConnection, HttpResponseMessage)> CreateConnectionAsync(HttpRequestMessage request, CancellationToken cancellationToken) { // If a non-infinite connect timeout has been set, create and use a new CancellationToken that'll be canceled // when either the original token is canceled or a connect timeout occurs. CancellationTokenSource cancellationWithConnectTimeout = null; if (Settings._connectTimeout != Timeout.InfiniteTimeSpan) { cancellationWithConnectTimeout = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, default); cancellationWithConnectTimeout.CancelAfter(Settings._connectTimeout); cancellationToken = cancellationWithConnectTimeout.Token; } try { Stream stream = null; switch (_kind) { case HttpConnectionKind.Http: case HttpConnectionKind.Https: case HttpConnectionKind.ProxyConnect: stream = await ConnectHelper.ConnectAsync(_host, _port, cancellationToken).ConfigureAwait(false); break; case HttpConnectionKind.Proxy: stream = await ConnectHelper.ConnectAsync(_proxyUri.IdnHost, _proxyUri.Port, cancellationToken).ConfigureAwait(false); break; case HttpConnectionKind.SslProxyTunnel: HttpResponseMessage response; (stream, response) = await EstablishProxyTunnel(cancellationToken).ConfigureAwait(false); if (response != null) { // Return non-success response from proxy. response.RequestMessage = request; return(null, response); } break; } TransportContext transportContext = null; if (_sslOptions != null) { SslStream sslStream = await ConnectHelper.EstablishSslConnectionAsync(_sslOptions, request, stream, cancellationToken).ConfigureAwait(false); stream = sslStream; transportContext = sslStream.TransportContext; } HttpConnection connection = _maxConnections == int.MaxValue ? new HttpConnection(this, stream, transportContext) : new HttpConnectionWithFinalizer(this, stream, transportContext); // finalizer needed to signal the pool when a connection is dropped return(connection, null); } finally { cancellationWithConnectTimeout?.Dispose(); } }
public ChunkedEncodingWriteStream(HttpConnection connection, CancellationToken cancellationToken) : base(connection, cancellationToken) { }