private async ValueTask <HttpConnection> 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 = await (_proxyUri == null ? ConnectHelper.ConnectAsync(_host, _port, cancellationToken) : (_sslOptions == null ? ConnectHelper.ConnectAsync(_proxyUri.IdnHost, _proxyUri.Port, cancellationToken) : EstablishProxyTunnel(cancellationToken))).ConfigureAwait(false); TransportContext transportContext = null; if (_sslOptions != null) { // TODO #25206 and #24430: Register/IsCancellationRequested should be removable once SslStream auth and sockets respect cancellation. CancellationTokenRegistration ctr = cancellationToken.Register(s => ((Stream)s).Dispose(), stream); try { SslStream sslStream = await ConnectHelper.EstablishSslConnectionAsync(_sslOptions, request, stream, cancellationToken).ConfigureAwait(false); stream = sslStream; transportContext = sslStream.TransportContext; cancellationToken.ThrowIfCancellationRequested(); // to handle race condition where stream is dispose of by cancellation after auth } catch (Exception exc) { stream.Dispose(); // in case cancellation occurs after successful SSL auth if (HttpConnection.ShouldWrapInOperationCanceledException(exc, cancellationToken)) { throw HttpConnection.CreateOperationCanceledException(exc, cancellationToken); } throw; } finally { ctr.Dispose(); } } return(_maxConnections == int.MaxValue ? new HttpConnection(this, stream, transportContext) : new HttpConnectionWithFinalizer(this, stream, transportContext)); // finalizer needed to signal the pool when a connection is dropped } finally { cancellationWithConnectTimeout?.Dispose(); } }
private static async ValueTask <SslStream> EstablishSslConnectionAsyncCore(Stream stream, SslClientAuthenticationOptions sslOptions, CancellationToken cancellationToken) { SslStream sslStream = new SslStream(stream); // TODO #25206 and #24430: Register/IsCancellationRequested should be removable once SslStream auth and sockets respect cancellation. CancellationTokenRegistration ctr = cancellationToken.Register(s => ((Stream)s).Dispose(), stream); try { await sslStream.AuthenticateAsClientAsync(sslOptions, cancellationToken).ConfigureAwait(false); } catch (Exception e) { sslStream.Dispose(); if (HttpConnection.ShouldWrapInOperationCanceledException(e, cancellationToken)) { throw HttpConnection.CreateOperationCanceledException(e, cancellationToken); } throw new HttpRequestException(SR.net_http_ssl_connection_failed, e); } finally { ctr.Dispose(); } // Handle race condition if cancellation happens after SSL auth completes but before the registration is disposed if (cancellationToken.IsCancellationRequested) { sslStream.Dispose(); throw new OperationCanceledException(cancellationToken); } return(sslStream); }
public static async ValueTask <Stream> ConnectAsync(string host, int port, CancellationToken cancellationToken) { try { // Rather than creating a new Socket and calling ConnectAsync on it, we use the static // Socket.ConnectAsync with a SocketAsyncEventArgs, as we can then use Socket.CancelConnectAsync // to cancel it if needed. using (var saea = new BuilderAndCancellationTokenSocketAsyncEventArgs(cancellationToken)) { // Configure which server to which to connect. saea.RemoteEndPoint = IPAddress.TryParse(host, out IPAddress address) ? (EndPoint) new IPEndPoint(address, port) : new DnsEndPoint(host, port); // Hook up a callback that'll complete the Task when the operation completes. saea.Completed += (s, e) => { var csaea = (BuilderAndCancellationTokenSocketAsyncEventArgs)e; switch (e.SocketError) { case SocketError.Success: csaea.Builder.SetResult(); break; case SocketError.OperationAborted: case SocketError.ConnectionAborted: if (csaea.CancellationToken.IsCancellationRequested) { csaea.Builder.SetException(new OperationCanceledException(csaea.CancellationToken)); break; } goto default; default: csaea.Builder.SetException(new SocketException((int)e.SocketError)); break; } }; // Initiate the connection. if (Socket.ConnectAsync(SocketType.Stream, ProtocolType.Tcp, saea)) { // Connect completing asynchronously. Enable it to be canceled and wait for it. using (cancellationToken.Register(s => Socket.CancelConnectAsync((SocketAsyncEventArgs)s), saea)) { await saea.Builder.Task.ConfigureAwait(false); } } else if (saea.SocketError != SocketError.Success) { // Connect completed synchronously but unsuccessfully. throw new SocketException((int)saea.SocketError); } Debug.Assert(saea.SocketError == SocketError.Success, $"Expected Success, got {saea.SocketError}."); Debug.Assert(saea.ConnectSocket != null, "Expected non-null socket"); // Configure the socket and return a stream for it. Socket socket = saea.ConnectSocket; socket.NoDelay = true; return(new NetworkStream(socket, ownsSocket: true)); } } catch (Exception error) { throw HttpConnection.ShouldWrapInOperationCanceledException(error, cancellationToken) ? HttpConnection.CreateOperationCanceledException(error, cancellationToken) : new HttpRequestException(error.Message, error); } }