88using System . Net . Cache ;
99using System . Net . Http ;
1010using System . Net . Security ;
11+ using System . Net . Sockets ;
1112using System . Runtime . Serialization ;
1213using System . Security . Authentication ;
1314using System . Security . Cryptography . X509Certificates ;
@@ -97,11 +98,13 @@ private enum Booleans : uint
9798
9899 private class HttpClientParameters
99100 {
101+ public readonly bool Async ;
100102 public readonly DecompressionMethods AutomaticDecompression ;
101103 public readonly bool AllowAutoRedirect ;
102104 public readonly int MaximumAutomaticRedirections ;
103105 public readonly int MaximumResponseHeadersLength ;
104106 public readonly bool PreAuthenticate ;
107+ public readonly int ReadWriteTimeout ;
105108 public readonly TimeSpan Timeout ;
106109 public readonly SecurityProtocolType SslProtocols ;
107110 public readonly bool CheckCertificateRevocationList ;
@@ -111,13 +114,15 @@ private class HttpClientParameters
111114 public readonly X509CertificateCollection ? ClientCertificates ;
112115 public readonly CookieContainer ? CookieContainer ;
113116
114- public HttpClientParameters ( HttpWebRequest webRequest )
117+ public HttpClientParameters ( HttpWebRequest webRequest , bool async )
115118 {
119+ Async = async ;
116120 AutomaticDecompression = webRequest . AutomaticDecompression ;
117121 AllowAutoRedirect = webRequest . AllowAutoRedirect ;
118122 MaximumAutomaticRedirections = webRequest . MaximumAutomaticRedirections ;
119123 MaximumResponseHeadersLength = webRequest . MaximumResponseHeadersLength ;
120124 PreAuthenticate = webRequest . PreAuthenticate ;
125+ ReadWriteTimeout = webRequest . ReadWriteTimeout ;
121126 Timeout = webRequest . Timeout == Threading . Timeout . Infinite
122127 ? Threading . Timeout . InfiniteTimeSpan
123128 : TimeSpan . FromMilliseconds ( webRequest . Timeout ) ;
@@ -132,11 +137,13 @@ public HttpClientParameters(HttpWebRequest webRequest)
132137
133138 public bool Matches ( HttpClientParameters requestParameters )
134139 {
135- return AutomaticDecompression == requestParameters . AutomaticDecompression
140+ return Async == requestParameters . Async
141+ && AutomaticDecompression == requestParameters . AutomaticDecompression
136142 && AllowAutoRedirect == requestParameters . AllowAutoRedirect
137143 && MaximumAutomaticRedirections == requestParameters . MaximumAutomaticRedirections
138144 && MaximumResponseHeadersLength == requestParameters . MaximumResponseHeadersLength
139145 && PreAuthenticate == requestParameters . PreAuthenticate
146+ && ReadWriteTimeout == requestParameters . ReadWriteTimeout
140147 && Timeout == requestParameters . Timeout
141148 && SslProtocols == requestParameters . SslProtocols
142149 && CheckCertificateRevocationList == requestParameters . CheckCertificateRevocationList
@@ -1122,7 +1129,7 @@ private async Task<WebResponse> SendRequest(bool async)
11221129 HttpClient ? client = null ;
11231130 try
11241131 {
1125- client = GetCachedOrCreateHttpClient ( out disposeRequired ) ;
1132+ client = GetCachedOrCreateHttpClient ( async , out disposeRequired ) ;
11261133 if ( _requestStream != null )
11271134 {
11281135 ArraySegment < byte > bytes = _requestStream. GetBuffer( ) ;
@@ -1443,9 +1450,9 @@ private bool TryGetHostUri(string hostName, [NotNullWhen(true)] out Uri? hostUri
14431450 return Uri . TryCreate ( s , UriKind . Absolute , out hostUri ) ;
14441451 }
14451452
1446- private HttpClient GetCachedOrCreateHttpClient ( out bool disposeRequired )
1453+ private HttpClient GetCachedOrCreateHttpClient ( bool async , out bool disposeRequired )
14471454 {
1448- var parameters = new HttpClientParameters ( this ) ;
1455+ var parameters = new HttpClientParameters ( this , async ) ;
14491456 if ( parameters . AreParametersAcceptableForCaching ( ) )
14501457 {
14511458 disposeRequired = false ;
@@ -1477,7 +1484,7 @@ private static HttpClient CreateHttpClient(HttpClientParameters parameters, Http
14771484 HttpClient ? client = null ;
14781485 try
14791486 {
1480- var handler = new HttpClientHandler ( ) ;
1487+ var handler = new SocketsHttpHandler ( ) ;
14811488 client = new HttpClient ( handler ) ;
14821489 handler . AutomaticDecompression = parameters . AutomaticDecompression ;
14831490 handler . Credentials = parameters . Credentials ;
@@ -1528,20 +1535,55 @@ private static HttpClient CreateHttpClient(HttpClientParameters parameters, Http
15281535
15291536 if ( parameters . ClientCertificates != null )
15301537 {
1531- handler . ClientCertificates . AddRange ( parameters . ClientCertificates ) ;
1538+ handler . SslOptions . ClientCertificates = new X509CertificateCollection ( parameters . ClientCertificates ) ;
15321539 }
15331540
15341541 // Set relevant properties from ServicePointManager
1535- handler . SslProtocols = ( SslProtocols ) parameters . SslProtocols ;
1536- handler . CheckCertificateRevocationList = parameters . CheckCertificateRevocationList ;
1542+ handler . SslOptions . EnabledSslProtocols = ( SslProtocols ) parameters . SslProtocols ;
1543+ handler . SslOptions . CertificateRevocationCheckMode = parameters . CheckCertificateRevocationList ? X509RevocationMode . Online : X509RevocationMode . NoCheck ;
15371544 RemoteCertificateValidationCallback ? rcvc = parameters . ServerCertificateValidationCallback ;
15381545 if ( rcvc != null )
15391546 {
1540- RemoteCertificateValidationCallback localRcvc = rcvc ;
1541- HttpWebRequest localRequest = request ! ;
1542- handler . ServerCertificateCustomValidationCallback = ( message , cert , chain , errors ) => localRcvc ( localRequest , cert , chain , errors ) ;
1547+ handler . SslOptions . RemoteCertificateValidationCallback = ( message , cert , chain , errors ) => rcvc ( request ! , cert , chain , errors ) ;
15431548 }
15441549
1550+ // Set up a ConnectCallback so that we can control Socket-specific settings, like ReadWriteTimeout => socket.Send/ReceiveTimeout.
1551+ handler . ConnectCallback = async ( context , cancellationToken ) =>
1552+ {
1553+ var socket = new Socket ( SocketType . Stream , ProtocolType . Tcp ) ;
1554+
1555+ try
1556+ {
1557+ socket . NoDelay = true ;
1558+ if ( parameters . ReadWriteTimeout > 0 ) // default is 5 minutes, so this is generally going to be true
1559+ {
1560+ socket . SendTimeout = socket . ReceiveTimeout = parameters . ReadWriteTimeout ;
1561+ }
1562+
1563+ if ( parameters . Async )
1564+ {
1565+ await socket . ConnectAsync ( context . DnsEndPoint , cancellationToken ) . ConfigureAwait ( false ) ;
1566+ }
1567+ else
1568+ {
1569+ using ( cancellationToken . UnsafeRegister ( s => ( ( Socket ) s ! ) . Dispose ( ) , socket ) )
1570+ {
1571+ socket . Connect ( context . DnsEndPoint ) ;
1572+ }
1573+
1574+ // Throw in case cancellation caused the socket to be disposed after the Connect completed
1575+ cancellationToken . ThrowIfCancellationRequested ( ) ;
1576+ }
1577+ }
1578+ catch
1579+ {
1580+ socket . Dispose ( ) ;
1581+ throw ;
1582+ }
1583+
1584+ return new NetworkStream ( socket , ownsSocket : true ) ;
1585+ } ;
1586+
15451587 return client ;
15461588 }
15471589 catch
0 commit comments