diff --git a/src/System.Net.Http/ref/System.Net.Http.cs b/src/System.Net.Http/ref/System.Net.Http.cs index 5bfa47a7e322..d33c8607a854 100644 --- a/src/System.Net.Http/ref/System.Net.Http.cs +++ b/src/System.Net.Http/ref/System.Net.Http.cs @@ -236,6 +236,28 @@ public ReadOnlyMemoryContent(System.ReadOnlyMemory content) { } protected override System.Threading.Tasks.Task SerializeToStreamAsync(System.IO.Stream stream, System.Net.TransportContext context) => throw null; protected internal override bool TryComputeLength(out long length) => throw null; } + public sealed class SocketsHttpHandler : HttpMessageHandler + { + public SocketsHttpHandler() { } + public bool AllowAutoRedirect { get { throw null; } set { } } + public System.Net.DecompressionMethods AutomaticDecompression { get { throw null; } set { } } + public System.Net.CookieContainer CookieContainer { get { throw null; } set { } } + public System.Net.ICredentials Credentials { get { throw null; } set { } } + public System.Net.ICredentials DefaultProxyCredentials { get { throw null; } set { } } + public int MaxAutomaticRedirections { get { throw null; } set { } } + public int MaxConnectionsPerServer { get { throw null; } set { } } + public int MaxResponseHeadersLength { get { throw null; } set { } } + public bool PreAuthenticate { get { throw null; } set { } } + public System.TimeSpan PooledConnectionIdleTimeout { get { throw null; } set { } } + public System.TimeSpan PooledConnectionLifetime { get { throw null; } set { } } + public System.Collections.Generic.IDictionary Properties { get { throw null; } } + public System.Net.IWebProxy Proxy { get { throw null; } set { } } + public System.Net.Security.SslClientAuthenticationOptions SslOptions { get { throw null; } set { } } + public bool UseCookies { get { throw null; } set { } } + public bool UseProxy { get { throw null; } set { } } + protected override void Dispose(bool disposing) { } + protected internal override System.Threading.Tasks.Task SendAsync(System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) { throw null; } + } public partial class StreamContent : System.Net.Http.HttpContent { public StreamContent(System.IO.Stream content) { } diff --git a/src/System.Net.Http/ref/System.Net.Http.csproj b/src/System.Net.Http/ref/System.Net.Http.csproj index bc043b82d797..ffad024b4207 100644 --- a/src/System.Net.Http/ref/System.Net.Http.csproj +++ b/src/System.Net.Http/ref/System.Net.Http.csproj @@ -16,6 +16,7 @@ + diff --git a/src/System.Net.Http/src/ILLinkTrim.xml b/src/System.Net.Http/src/ILLinkTrim.xml index f6345944a034..3957caf8eafe 100644 --- a/src/System.Net.Http/src/ILLinkTrim.xml +++ b/src/System.Net.Http/src/ILLinkTrim.xml @@ -2,8 +2,5 @@ - - - - \ No newline at end of file + diff --git a/src/System.Net.Http/src/System.Net.Http.csproj b/src/System.Net.Http/src/System.Net.Http.csproj index 296d26dc67ff..ba10a2f73dab 100644 --- a/src/System.Net.Http/src/System.Net.Http.csproj +++ b/src/System.Net.Http/src/System.Net.Http.csproj @@ -121,42 +121,42 @@ Common\System\Net\Logging\NetEventSource.Common.cs - + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - + + - + @@ -187,7 +187,7 @@ Common\System\Net\Mail\WhitespaceReader.cs - + @@ -206,19 +206,19 @@ - + true - - - - - - + + + + + + Common\Interop\Unix\StrongToWeakReference.cs @@ -377,11 +377,11 @@ Common\System\Net\Security\CertificateValidation.Unix.cs - - + + - + diff --git a/src/System.Net.Http/src/System/Net/Http/Unix/CurlException.cs b/src/System.Net.Http/src/System/Net/Http/CurlHandler/CurlException.cs similarity index 100% rename from src/System.Net.Http/src/System/Net/Http/Unix/CurlException.cs rename to src/System.Net.Http/src/System/Net/Http/CurlHandler/CurlException.cs diff --git a/src/System.Net.Http/src/System/Net/Http/Unix/CurlHandler.ClientCertificateProvider.cs b/src/System.Net.Http/src/System/Net/Http/CurlHandler/CurlHandler.ClientCertificateProvider.cs similarity index 100% rename from src/System.Net.Http/src/System/Net/Http/Unix/CurlHandler.ClientCertificateProvider.cs rename to src/System.Net.Http/src/System/Net/Http/CurlHandler/CurlHandler.ClientCertificateProvider.cs diff --git a/src/System.Net.Http/src/System/Net/Http/Unix/CurlHandler.CurlResponseMessage.cs b/src/System.Net.Http/src/System/Net/Http/CurlHandler/CurlHandler.CurlResponseMessage.cs similarity index 100% rename from src/System.Net.Http/src/System/Net/Http/Unix/CurlHandler.CurlResponseMessage.cs rename to src/System.Net.Http/src/System/Net/Http/CurlHandler/CurlHandler.CurlResponseMessage.cs diff --git a/src/System.Net.Http/src/System/Net/Http/Unix/CurlHandler.EasyRequest.cs b/src/System.Net.Http/src/System/Net/Http/CurlHandler/CurlHandler.EasyRequest.cs similarity index 100% rename from src/System.Net.Http/src/System/Net/Http/Unix/CurlHandler.EasyRequest.cs rename to src/System.Net.Http/src/System/Net/Http/CurlHandler/CurlHandler.EasyRequest.cs diff --git a/src/System.Net.Http/src/System/Net/Http/Unix/CurlHandler.MultiAgent.cs b/src/System.Net.Http/src/System/Net/Http/CurlHandler/CurlHandler.MultiAgent.cs similarity index 100% rename from src/System.Net.Http/src/System/Net/Http/Unix/CurlHandler.MultiAgent.cs rename to src/System.Net.Http/src/System/Net/Http/CurlHandler/CurlHandler.MultiAgent.cs diff --git a/src/System.Net.Http/src/System/Net/Http/Unix/CurlHandler.SslProvider.cs b/src/System.Net.Http/src/System/Net/Http/CurlHandler/CurlHandler.SslProvider.Linux.cs similarity index 100% rename from src/System.Net.Http/src/System/Net/Http/Unix/CurlHandler.SslProvider.cs rename to src/System.Net.Http/src/System/Net/Http/CurlHandler/CurlHandler.SslProvider.Linux.cs diff --git a/src/System.Net.Http/src/System/Net/Http/OSX/CurlHandler.SslProvider.cs b/src/System.Net.Http/src/System/Net/Http/CurlHandler/CurlHandler.SslProvider.OSX.cs similarity index 100% rename from src/System.Net.Http/src/System/Net/Http/OSX/CurlHandler.SslProvider.cs rename to src/System.Net.Http/src/System/Net/Http/CurlHandler/CurlHandler.SslProvider.OSX.cs diff --git a/src/System.Net.Http/src/System/Net/Http/Unix/CurlHandler.cs b/src/System.Net.Http/src/System/Net/Http/CurlHandler/CurlHandler.cs similarity index 100% rename from src/System.Net.Http/src/System/Net/Http/Unix/CurlHandler.cs rename to src/System.Net.Http/src/System/Net/Http/CurlHandler/CurlHandler.cs diff --git a/src/System.Net.Http/src/System/Net/Http/Unix/CurlResponseHeaderReader.cs b/src/System.Net.Http/src/System/Net/Http/CurlHandler/CurlResponseHeaderReader.cs similarity index 100% rename from src/System.Net.Http/src/System/Net/Http/Unix/CurlResponseHeaderReader.cs rename to src/System.Net.Http/src/System/Net/Http/CurlHandler/CurlResponseHeaderReader.cs diff --git a/src/System.Net.Http/src/System/Net/Http/HttpClientHandler.Core.cs b/src/System.Net.Http/src/System/Net/Http/HttpClientHandler.Core.cs index 08852592ffda..ce586eaa8004 100644 --- a/src/System.Net.Http/src/System/Net/Http/HttpClientHandler.Core.cs +++ b/src/System.Net.Http/src/System/Net/Http/HttpClientHandler.Core.cs @@ -67,7 +67,7 @@ private void ThrowForModifiedManagedSslOptionsIfStarted() { // Hack to trigger an InvalidOperationException if a property that's stored on // SslOptions is changed, since SslOptions itself does not do any such checks. - _managedHandler.SslOptions = _managedHandler.SslOptions; + _socketsHttpHandler.SslOptions = _socketsHttpHandler.SslOptions; } } } diff --git a/src/System.Net.Http/src/System/Net/Http/HttpClientHandler.Unix.cs b/src/System.Net.Http/src/System/Net/Http/HttpClientHandler.Unix.cs index 941223b6cb7c..04de3861adf2 100644 --- a/src/System.Net.Http/src/System/Net/Http/HttpClientHandler.Unix.cs +++ b/src/System.Net.Http/src/System/Net/Http/HttpClientHandler.Unix.cs @@ -15,15 +15,17 @@ public partial class HttpClientHandler : HttpMessageHandler { // Only one of these two handlers will be initialized. private readonly CurlHandler _curlHandler; - private readonly ManagedHandler _managedHandler; + private readonly SocketsHttpHandler _socketsHttpHandler; private readonly DiagnosticsHandler _diagnosticsHandler; - public HttpClientHandler() + public HttpClientHandler() : this(UseSocketsHttpHandler) { } + + private HttpClientHandler(bool useSocketsHttpHandler) // used by parameterless ctor and as hook for testing { - if (UseManagedHandler) + if (useSocketsHttpHandler) { - _managedHandler = new ManagedHandler() { SslOptions = new SslClientAuthenticationOptions() }; - _diagnosticsHandler = new DiagnosticsHandler(_managedHandler); + _socketsHttpHandler = new SocketsHttpHandler(); + _diagnosticsHandler = new DiagnosticsHandler(_socketsHttpHandler); } else { @@ -36,7 +38,7 @@ protected override void Dispose(bool disposing) { if (disposing) { - ((HttpMessageHandler)_curlHandler ?? _managedHandler).Dispose(); + ((HttpMessageHandler)_curlHandler ?? _socketsHttpHandler).Dispose(); } base.Dispose(disposing); } @@ -49,7 +51,7 @@ protected override void Dispose(bool disposing) public bool UseCookies { - get => _curlHandler != null ? _curlHandler.UseCookies : _managedHandler.UseCookies; + get => _curlHandler != null ? _curlHandler.UseCookies : _socketsHttpHandler.UseCookies; set { if (_curlHandler != null) @@ -58,14 +60,14 @@ public bool UseCookies } else { - _managedHandler.UseCookies = value; + _socketsHttpHandler.UseCookies = value; } } } public CookieContainer CookieContainer { - get => _curlHandler != null ? _curlHandler.CookieContainer : _managedHandler.CookieContainer; + get => _curlHandler != null ? _curlHandler.CookieContainer : _socketsHttpHandler.CookieContainer; set { if (_curlHandler != null) @@ -74,7 +76,7 @@ public CookieContainer CookieContainer } else { - _managedHandler.CookieContainer = value; + _socketsHttpHandler.CookieContainer = value; } } } @@ -89,7 +91,7 @@ public ClientCertificateOption ClientCertificateOptions } else { - return _managedHandler.SslOptions.LocalCertificateSelectionCallback != null ? + return _socketsHttpHandler.SslOptions.LocalCertificateSelectionCallback != null ? ClientCertificateOption.Automatic : ClientCertificateOption.Manual; } @@ -106,12 +108,12 @@ public ClientCertificateOption ClientCertificateOptions { case ClientCertificateOption.Manual: ThrowForModifiedManagedSslOptionsIfStarted(); - _managedHandler.SslOptions.LocalCertificateSelectionCallback = null; + _socketsHttpHandler.SslOptions.LocalCertificateSelectionCallback = null; break; case ClientCertificateOption.Automatic: ThrowForModifiedManagedSslOptionsIfStarted(); - _managedHandler.SslOptions.LocalCertificateSelectionCallback = (sender, targetHost, localCertificates, remoteCertificate, acceptableIssuers) => CertificateHelper.GetEligibleClientCertificate(); + _socketsHttpHandler.SslOptions.LocalCertificateSelectionCallback = (sender, targetHost, localCertificates, remoteCertificate, acceptableIssuers) => CertificateHelper.GetEligibleClientCertificate(); break; default: @@ -136,8 +138,8 @@ public X509CertificateCollection ClientCertificates throw new InvalidOperationException(SR.Format(SR.net_http_invalid_enable_first, nameof(ClientCertificateOptions), nameof(ClientCertificateOption.Manual))); } - return _managedHandler.SslOptions.ClientCertificates ?? - (_managedHandler.SslOptions.ClientCertificates = new X509CertificateCollection()); + return _socketsHttpHandler.SslOptions.ClientCertificates ?? + (_socketsHttpHandler.SslOptions.ClientCertificates = new X509CertificateCollection()); } } } @@ -148,7 +150,7 @@ public Func _curlHandler != null ? _curlHandler.CheckCertificateRevocationList : _managedHandler.SslOptions.CertificateRevocationCheckMode == X509RevocationMode.Online; + get => _curlHandler != null ? _curlHandler.CheckCertificateRevocationList : _socketsHttpHandler.SslOptions.CertificateRevocationCheckMode == X509RevocationMode.Online; set { if (_curlHandler != null) @@ -178,14 +180,14 @@ public bool CheckCertificateRevocationList else { ThrowForModifiedManagedSslOptionsIfStarted(); - _managedHandler.SslOptions.CertificateRevocationCheckMode = value ? X509RevocationMode.Online : X509RevocationMode.NoCheck; + _socketsHttpHandler.SslOptions.CertificateRevocationCheckMode = value ? X509RevocationMode.Online : X509RevocationMode.NoCheck; } } } public SslProtocols SslProtocols { - get => _curlHandler != null ? _curlHandler.SslProtocols : _managedHandler.SslOptions.EnabledSslProtocols; + get => _curlHandler != null ? _curlHandler.SslProtocols : _socketsHttpHandler.SslOptions.EnabledSslProtocols; set { if (_curlHandler != null) @@ -196,14 +198,14 @@ public SslProtocols SslProtocols { SecurityProtocol.ThrowOnNotAllowed(value, allowNone: true); ThrowForModifiedManagedSslOptionsIfStarted(); - _managedHandler.SslOptions.EnabledSslProtocols = value; + _socketsHttpHandler.SslOptions.EnabledSslProtocols = value; } } } public DecompressionMethods AutomaticDecompression { - get => _curlHandler != null ? _curlHandler.AutomaticDecompression : _managedHandler.AutomaticDecompression; + get => _curlHandler != null ? _curlHandler.AutomaticDecompression : _socketsHttpHandler.AutomaticDecompression; set { if (_curlHandler != null) @@ -212,14 +214,14 @@ public DecompressionMethods AutomaticDecompression } else { - _managedHandler.AutomaticDecompression = value; + _socketsHttpHandler.AutomaticDecompression = value; } } } public bool UseProxy { - get => _curlHandler != null ? _curlHandler.UseProxy : _managedHandler.UseProxy; + get => _curlHandler != null ? _curlHandler.UseProxy : _socketsHttpHandler.UseProxy; set { if (_curlHandler != null) @@ -228,14 +230,14 @@ public bool UseProxy } else { - _managedHandler.UseProxy = value; + _socketsHttpHandler.UseProxy = value; } } } public IWebProxy Proxy { - get => _curlHandler != null ? _curlHandler.Proxy : _managedHandler.Proxy; + get => _curlHandler != null ? _curlHandler.Proxy : _socketsHttpHandler.Proxy; set { if (_curlHandler != null) @@ -244,14 +246,14 @@ public IWebProxy Proxy } else { - _managedHandler.Proxy = value; + _socketsHttpHandler.Proxy = value; } } } public ICredentials DefaultProxyCredentials { - get => _curlHandler != null ? _curlHandler.DefaultProxyCredentials : _managedHandler.DefaultProxyCredentials; + get => _curlHandler != null ? _curlHandler.DefaultProxyCredentials : _socketsHttpHandler.DefaultProxyCredentials; set { if (_curlHandler != null) @@ -260,14 +262,14 @@ public ICredentials DefaultProxyCredentials } else { - _managedHandler.DefaultProxyCredentials = value; + _socketsHttpHandler.DefaultProxyCredentials = value; } } } public bool PreAuthenticate { - get => _curlHandler != null ? _curlHandler.PreAuthenticate : _managedHandler.PreAuthenticate; + get => _curlHandler != null ? _curlHandler.PreAuthenticate : _socketsHttpHandler.PreAuthenticate; set { if (_curlHandler != null) @@ -276,7 +278,7 @@ public bool PreAuthenticate } else { - _managedHandler.PreAuthenticate = value; + _socketsHttpHandler.PreAuthenticate = value; } } } @@ -295,7 +297,7 @@ public bool UseDefaultCredentials public ICredentials Credentials { - get => _curlHandler != null ? _curlHandler.Credentials : _managedHandler.Credentials; + get => _curlHandler != null ? _curlHandler.Credentials : _socketsHttpHandler.Credentials; set { if (_curlHandler != null) @@ -304,14 +306,14 @@ public ICredentials Credentials } else { - _managedHandler.Credentials = value; + _socketsHttpHandler.Credentials = value; } } } public bool AllowAutoRedirect { - get => _curlHandler != null ? _curlHandler.AllowAutoRedirect : _managedHandler.AllowAutoRedirect; + get => _curlHandler != null ? _curlHandler.AllowAutoRedirect : _socketsHttpHandler.AllowAutoRedirect; set { if (_curlHandler != null) @@ -320,14 +322,14 @@ public bool AllowAutoRedirect } else { - _managedHandler.AllowAutoRedirect = value; + _socketsHttpHandler.AllowAutoRedirect = value; } } } public int MaxAutomaticRedirections { - get => _curlHandler != null ? _curlHandler.MaxAutomaticRedirections : _managedHandler.MaxAutomaticRedirections; + get => _curlHandler != null ? _curlHandler.MaxAutomaticRedirections : _socketsHttpHandler.MaxAutomaticRedirections; set { if (_curlHandler != null) @@ -336,14 +338,14 @@ public int MaxAutomaticRedirections } else { - _managedHandler.MaxAutomaticRedirections = value; + _socketsHttpHandler.MaxAutomaticRedirections = value; } } } public int MaxConnectionsPerServer { - get => _curlHandler != null ? _curlHandler.MaxConnectionsPerServer : _managedHandler.MaxConnectionsPerServer; + get => _curlHandler != null ? _curlHandler.MaxConnectionsPerServer : _socketsHttpHandler.MaxConnectionsPerServer; set { if (_curlHandler != null) @@ -352,14 +354,14 @@ public int MaxConnectionsPerServer } else { - _managedHandler.MaxConnectionsPerServer = value; + _socketsHttpHandler.MaxConnectionsPerServer = value; } } } public int MaxResponseHeadersLength { - get => _curlHandler != null ? _curlHandler.MaxResponseHeadersLength : _managedHandler.MaxResponseHeadersLength; + get => _curlHandler != null ? _curlHandler.MaxResponseHeadersLength : _socketsHttpHandler.MaxResponseHeadersLength; set { if (_curlHandler != null) @@ -368,18 +370,18 @@ public int MaxResponseHeadersLength } else { - _managedHandler.MaxResponseHeadersLength = value; + _socketsHttpHandler.MaxResponseHeadersLength = value; } } } public IDictionary Properties => _curlHandler != null ? _curlHandler.Properties : - _managedHandler.Properties; + _socketsHttpHandler.Properties; protected internal override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) => DiagnosticsHandler.IsEnabled() ? _diagnosticsHandler.SendAsync(request, cancellationToken) : _curlHandler != null ? _curlHandler.SendAsync(request, cancellationToken) : - _managedHandler.SendAsync(request, cancellationToken); + _socketsHttpHandler.SendAsync(request, cancellationToken); } } diff --git a/src/System.Net.Http/src/System/Net/Http/HttpClientHandler.Windows.cs b/src/System.Net.Http/src/System/Net/Http/HttpClientHandler.Windows.cs index fb924f6a8cec..0c3e7d4ad59c 100644 --- a/src/System.Net.Http/src/System/Net/Http/HttpClientHandler.Windows.cs +++ b/src/System.Net.Http/src/System/Net/Http/HttpClientHandler.Windows.cs @@ -16,16 +16,18 @@ namespace System.Net.Http public partial class HttpClientHandler : HttpMessageHandler { private readonly WinHttpHandler _winHttpHandler; - private readonly ManagedHandler _managedHandler; + private readonly SocketsHttpHandler _socketsHttpHandler; private readonly DiagnosticsHandler _diagnosticsHandler; private bool _useProxy; - public HttpClientHandler() + public HttpClientHandler() : this(UseSocketsHttpHandler) { } + + private HttpClientHandler(bool useSocketsHttpHandler) // used by parameterless ctor and as hook for testing { - if (UseManagedHandler) + if (useSocketsHttpHandler) { - _managedHandler = new ManagedHandler() { SslOptions = new SslClientAuthenticationOptions() }; - _diagnosticsHandler = new DiagnosticsHandler(_managedHandler); + _socketsHttpHandler = new SocketsHttpHandler(); + _diagnosticsHandler = new DiagnosticsHandler(_socketsHttpHandler); } else { @@ -60,7 +62,7 @@ protected override void Dispose(bool disposing) if (disposing && !_disposed) { _disposed = true; - ((HttpMessageHandler)_winHttpHandler ?? _managedHandler).Dispose(); + ((HttpMessageHandler)_winHttpHandler ?? _socketsHttpHandler).Dispose(); } base.Dispose(disposing); @@ -72,7 +74,7 @@ protected override void Dispose(bool disposing) public bool UseCookies { - get => _winHttpHandler != null ? _winHttpHandler.CookieUsePolicy == CookieUsePolicy.UseSpecifiedCookieContainer : _managedHandler.UseCookies; + get => _winHttpHandler != null ? _winHttpHandler.CookieUsePolicy == CookieUsePolicy.UseSpecifiedCookieContainer : _socketsHttpHandler.UseCookies; set { if (_winHttpHandler != null) @@ -81,14 +83,14 @@ public bool UseCookies } else { - _managedHandler.UseCookies = value; + _socketsHttpHandler.UseCookies = value; } } } public CookieContainer CookieContainer { - get => _winHttpHandler != null ? _winHttpHandler.CookieContainer : _managedHandler.CookieContainer; + get => _winHttpHandler != null ? _winHttpHandler.CookieContainer : _socketsHttpHandler.CookieContainer; set { if (_winHttpHandler != null) @@ -97,14 +99,14 @@ public CookieContainer CookieContainer } else { - _managedHandler.CookieContainer = value; + _socketsHttpHandler.CookieContainer = value; } } } public DecompressionMethods AutomaticDecompression { - get => _winHttpHandler != null ? _winHttpHandler.AutomaticDecompression : _managedHandler.AutomaticDecompression; + get => _winHttpHandler != null ? _winHttpHandler.AutomaticDecompression : _socketsHttpHandler.AutomaticDecompression; set { if (_winHttpHandler != null) @@ -113,14 +115,14 @@ public DecompressionMethods AutomaticDecompression } else { - _managedHandler.AutomaticDecompression = value; + _socketsHttpHandler.AutomaticDecompression = value; } } } public bool UseProxy { - get => _winHttpHandler != null ? _useProxy : _managedHandler.UseProxy; + get => _winHttpHandler != null ? _useProxy : _socketsHttpHandler.UseProxy; set { if (_winHttpHandler != null) @@ -129,14 +131,14 @@ public bool UseProxy } else { - _managedHandler.UseProxy = value; + _socketsHttpHandler.UseProxy = value; } } } public IWebProxy Proxy { - get => _winHttpHandler != null ? _winHttpHandler.Proxy : _managedHandler.Proxy; + get => _winHttpHandler != null ? _winHttpHandler.Proxy : _socketsHttpHandler.Proxy; set { if (_winHttpHandler != null) @@ -145,14 +147,14 @@ public IWebProxy Proxy } else { - _managedHandler.Proxy = value; + _socketsHttpHandler.Proxy = value; } } } public ICredentials DefaultProxyCredentials { - get => _winHttpHandler != null ? _winHttpHandler.DefaultProxyCredentials : _managedHandler.DefaultProxyCredentials; + get => _winHttpHandler != null ? _winHttpHandler.DefaultProxyCredentials : _socketsHttpHandler.DefaultProxyCredentials; set { if (_winHttpHandler != null) @@ -161,14 +163,14 @@ public ICredentials DefaultProxyCredentials } else { - _managedHandler.DefaultProxyCredentials = value; + _socketsHttpHandler.DefaultProxyCredentials = value; } } } public bool PreAuthenticate { - get => _winHttpHandler != null ? _winHttpHandler.PreAuthenticate : _managedHandler.PreAuthenticate; + get => _winHttpHandler != null ? _winHttpHandler.PreAuthenticate : _socketsHttpHandler.PreAuthenticate; set { if (_winHttpHandler != null) @@ -177,7 +179,7 @@ public bool PreAuthenticate } else { - _managedHandler.PreAuthenticate = value; + _socketsHttpHandler.PreAuthenticate = value; } } } @@ -212,7 +214,7 @@ public bool UseDefaultCredentials public ICredentials Credentials { - get => _winHttpHandler != null ? _winHttpHandler.ServerCredentials : _managedHandler.Credentials; + get => _winHttpHandler != null ? _winHttpHandler.ServerCredentials : _socketsHttpHandler.Credentials; set { if (_winHttpHandler != null) @@ -221,14 +223,14 @@ public ICredentials Credentials } else { - _managedHandler.Credentials = value; + _socketsHttpHandler.Credentials = value; } } } public bool AllowAutoRedirect { - get => _winHttpHandler != null ? _winHttpHandler.AutomaticRedirection : _managedHandler.AllowAutoRedirect; + get => _winHttpHandler != null ? _winHttpHandler.AutomaticRedirection : _socketsHttpHandler.AllowAutoRedirect; set { if (_winHttpHandler != null) @@ -237,14 +239,14 @@ public bool AllowAutoRedirect } else { - _managedHandler.AllowAutoRedirect = value; + _socketsHttpHandler.AllowAutoRedirect = value; } } } public int MaxAutomaticRedirections { - get => _winHttpHandler != null ? _winHttpHandler.MaxAutomaticRedirections : _managedHandler.MaxAutomaticRedirections; + get => _winHttpHandler != null ? _winHttpHandler.MaxAutomaticRedirections : _socketsHttpHandler.MaxAutomaticRedirections; set { if (_winHttpHandler != null) @@ -253,14 +255,14 @@ public int MaxAutomaticRedirections } else { - _managedHandler.MaxAutomaticRedirections = value; + _socketsHttpHandler.MaxAutomaticRedirections = value; } } } public int MaxConnectionsPerServer { - get => _winHttpHandler != null ? _winHttpHandler.MaxConnectionsPerServer : _managedHandler.MaxConnectionsPerServer; + get => _winHttpHandler != null ? _winHttpHandler.MaxConnectionsPerServer : _socketsHttpHandler.MaxConnectionsPerServer; set { if (_winHttpHandler != null) @@ -269,14 +271,14 @@ public int MaxConnectionsPerServer } else { - _managedHandler.MaxConnectionsPerServer = value; + _socketsHttpHandler.MaxConnectionsPerServer = value; } } } public int MaxResponseHeadersLength { - get => _winHttpHandler != null ? _winHttpHandler.MaxResponseHeadersLength : _managedHandler.MaxResponseHeadersLength; + get => _winHttpHandler != null ? _winHttpHandler.MaxResponseHeadersLength : _socketsHttpHandler.MaxResponseHeadersLength; set { if (_winHttpHandler != null) @@ -285,7 +287,7 @@ public int MaxResponseHeadersLength } else { - _managedHandler.MaxResponseHeadersLength = value; + _socketsHttpHandler.MaxResponseHeadersLength = value; } } } @@ -300,7 +302,7 @@ public ClientCertificateOption ClientCertificateOptions } else { - return _managedHandler.SslOptions.LocalCertificateSelectionCallback != null ? + return _socketsHttpHandler.SslOptions.LocalCertificateSelectionCallback != null ? ClientCertificateOption.Automatic : ClientCertificateOption.Manual; } @@ -317,12 +319,12 @@ public ClientCertificateOption ClientCertificateOptions { case ClientCertificateOption.Manual: ThrowForModifiedManagedSslOptionsIfStarted(); - _managedHandler.SslOptions.LocalCertificateSelectionCallback = null; + _socketsHttpHandler.SslOptions.LocalCertificateSelectionCallback = null; break; case ClientCertificateOption.Automatic: ThrowForModifiedManagedSslOptionsIfStarted(); - _managedHandler.SslOptions.LocalCertificateSelectionCallback = (sender, targetHost, localCertificates, remoteCertificate, acceptableIssuers) => CertificateHelper.GetEligibleClientCertificate(); + _socketsHttpHandler.SslOptions.LocalCertificateSelectionCallback = (sender, targetHost, localCertificates, remoteCertificate, acceptableIssuers) => CertificateHelper.GetEligibleClientCertificate(); break; default: @@ -347,8 +349,8 @@ public X509CertificateCollection ClientCertificates throw new InvalidOperationException(SR.Format(SR.net_http_invalid_enable_first, nameof(ClientCertificateOptions), nameof(ClientCertificateOption.Manual))); } - return _managedHandler.SslOptions.ClientCertificates ?? - (_managedHandler.SslOptions.ClientCertificates = new X509CertificateCollection()); + return _socketsHttpHandler.SslOptions.ClientCertificates ?? + (_socketsHttpHandler.SslOptions.ClientCertificates = new X509CertificateCollection()); } } } @@ -359,7 +361,7 @@ public Func _winHttpHandler != null ? _winHttpHandler.CheckCertificateRevocationList : _managedHandler.SslOptions.CertificateRevocationCheckMode == X509RevocationMode.Online; + get => _winHttpHandler != null ? _winHttpHandler.CheckCertificateRevocationList : _socketsHttpHandler.SslOptions.CertificateRevocationCheckMode == X509RevocationMode.Online; set { if (_winHttpHandler != null) @@ -389,14 +391,14 @@ public bool CheckCertificateRevocationList else { ThrowForModifiedManagedSslOptionsIfStarted(); - _managedHandler.SslOptions.CertificateRevocationCheckMode = value ? X509RevocationMode.Online : X509RevocationMode.NoCheck; + _socketsHttpHandler.SslOptions.CertificateRevocationCheckMode = value ? X509RevocationMode.Online : X509RevocationMode.NoCheck; } } } public SslProtocols SslProtocols { - get => _winHttpHandler != null ? _winHttpHandler.SslProtocols : _managedHandler.SslOptions.EnabledSslProtocols; + get => _winHttpHandler != null ? _winHttpHandler.SslProtocols : _socketsHttpHandler.SslOptions.EnabledSslProtocols; set { if (_winHttpHandler != null) @@ -407,14 +409,14 @@ public SslProtocols SslProtocols { SecurityProtocol.ThrowOnNotAllowed(value, allowNone: true); ThrowForModifiedManagedSslOptionsIfStarted(); - _managedHandler.SslOptions.EnabledSslProtocols = value; + _socketsHttpHandler.SslOptions.EnabledSslProtocols = value; } } } public IDictionary Properties => _winHttpHandler != null ? _winHttpHandler.Properties : - _managedHandler.Properties; + _socketsHttpHandler.Properties; protected internal override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) @@ -458,7 +460,7 @@ protected internal override Task SendAsync(HttpRequestMessa { return DiagnosticsHandler.IsEnabled() ? _diagnosticsHandler.SendAsync(request, cancellationToken) : - _managedHandler.SendAsync(request, cancellationToken); + _socketsHttpHandler.SendAsync(request, cancellationToken); } } } diff --git a/src/System.Net.Http/src/System/Net/Http/HttpClientHandler.cs b/src/System.Net.Http/src/System/Net/Http/HttpClientHandler.cs index 00c557fcc768..8d3de7d979f5 100644 --- a/src/System.Net.Http/src/System/Net/Http/HttpClientHandler.cs +++ b/src/System.Net.Http/src/System/Net/Http/HttpClientHandler.cs @@ -5,58 +5,34 @@ using System.Diagnostics; using System.Net.Security; using System.Security.Cryptography.X509Certificates; -using System.Threading; namespace System.Net.Http { public partial class HttpClientHandler : HttpMessageHandler { // This partial implementation contains members common to all HttpClientHandler implementations. - private const string ManagedHandlerEnvironmentVariableSettingName = "DOTNET_SYSTEM_NET_HTTP_USEMANAGEDHTTPCLIENTHANDLER"; - private const string ManagedHandlerAppCtxSettingName = "System.Net.Http.UseManagedHttpClientHandler"; + private const string SocketsHttpHandlerEnvironmentVariableSettingName = "DOTNET_SYSTEM_NET_HTTP_USESOCKETSHTTPHANDLER"; + private const string SocketsHttpHandlerAppCtxSettingName = "System.Net.Http.UseSocketsHttpHandler"; - private static LocalDataStoreSlot s_useManagedHandlerSlot; - - private static bool UseManagedHandler + private static bool UseSocketsHttpHandler { get { // First check for the AppContext switch, giving it priority over over the environment variable. - if (AppContext.TryGetSwitch(ManagedHandlerAppCtxSettingName, out bool isManagedEnabled)) + if (AppContext.TryGetSwitch(SocketsHttpHandlerAppCtxSettingName, out bool useSocketsHttpHandler)) { - return isManagedEnabled; + return useSocketsHttpHandler; } // AppContext switch wasn't used. Check the environment variable to see if it's been set to true. - string envVar = Environment.GetEnvironmentVariable(ManagedHandlerEnvironmentVariableSettingName); + string envVar = Environment.GetEnvironmentVariable(SocketsHttpHandlerEnvironmentVariableSettingName); if (envVar != null && (envVar.Equals("true", StringComparison.OrdinalIgnoreCase) || envVar.Equals("1"))) { return true; } - // TODO #23166: Remove the following TLS check assuming the type is exposed publicly. If it's not, - // re-evaluate the priority ordering of this with regards to the AppContext and environment settings. - - // Then check whether a thread local has been set with the same name. - // If it's been set to a Boolean true, also use the managed handler. - LocalDataStoreSlot slot = LazyInitializer.EnsureInitialized(ref s_useManagedHandlerSlot, () => - { - LocalDataStoreSlot local = Thread.GetNamedDataSlot(ManagedHandlerEnvironmentVariableSettingName); - if (local == null) - { - try - { - local = Thread.AllocateNamedDataSlot(ManagedHandlerEnvironmentVariableSettingName); - } - catch (ArgumentException) - { - local = Thread.GetNamedDataSlot(ManagedHandlerEnvironmentVariableSettingName); - } - } - return local; - }); - Debug.Assert(slot != null); - return Thread.GetData(slot) is bool result && result; + // Default to using WinHttpHandler on Windows and CurlHandler on Unix + return false; } } diff --git a/src/System.Net.Http/src/System/Net/Http/Managed/README.md b/src/System.Net.Http/src/System/Net/Http/Managed/README.md deleted file mode 100644 index 9b7cf657e77e..000000000000 --- a/src/System.Net.Http/src/System/Net/Http/Managed/README.md +++ /dev/null @@ -1,3 +0,0 @@ -#### Enabling the ManagedHandler - -The shipping version of HttpClientHandler is a wrapper for WinHTTP on Windows and libcurl on Unix. This directory contains a managed implementation that's still under development and that's not intended for production use. By default it's disabled, and the WinHTTP/libcurl versions will be used. However, by setting the "DOTNET_SYSTEM_NET_HTTP_USEMANAGEDHTTPCLIENTHANDLER" environment variable to "true" or "1", this managed implementation will be used. It can also be enabled via the "System.Net.Http.UseManagedHttpClientHandler" AppContext setting. diff --git a/src/System.Net.Http/src/System/Net/Http/Managed/AuthenticateAndRedirectHandler.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticateAndRedirectHandler.cs similarity index 100% rename from src/System.Net.Http/src/System/Net/Http/Managed/AuthenticateAndRedirectHandler.cs rename to src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticateAndRedirectHandler.cs diff --git a/src/System.Net.Http/src/System/Net/Http/Managed/AuthenticationHelper.Basic.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.Basic.cs similarity index 100% rename from src/System.Net.Http/src/System/Net/Http/Managed/AuthenticationHelper.Basic.cs rename to src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.Basic.cs diff --git a/src/System.Net.Http/src/System/Net/Http/Managed/AuthenticationHelper.Digest.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.Digest.cs similarity index 100% rename from src/System.Net.Http/src/System/Net/Http/Managed/AuthenticationHelper.Digest.cs rename to src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.Digest.cs diff --git a/src/System.Net.Http/src/System/Net/Http/Managed/ChunkedEncodingReadStream.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ChunkedEncodingReadStream.cs similarity index 100% rename from src/System.Net.Http/src/System/Net/Http/Managed/ChunkedEncodingReadStream.cs rename to src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ChunkedEncodingReadStream.cs diff --git a/src/System.Net.Http/src/System/Net/Http/Managed/ChunkedEncodingWriteStream.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ChunkedEncodingWriteStream.cs similarity index 100% rename from src/System.Net.Http/src/System/Net/Http/Managed/ChunkedEncodingWriteStream.cs rename to src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ChunkedEncodingWriteStream.cs diff --git a/src/System.Net.Http/src/System/Net/Http/Managed/ConnectHelper.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ConnectHelper.cs similarity index 97% rename from src/System.Net.Http/src/System/Net/Http/Managed/ConnectHelper.cs rename to src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ConnectHelper.cs index 088bcd295a7b..dc777519ff56 100644 --- a/src/System.Net.Http/src/System/Net/Http/Managed/ConnectHelper.cs +++ b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ConnectHelper.cs @@ -16,18 +16,18 @@ namespace System.Net.Http internal static class ConnectHelper { /// - /// Helper type used by HttpClientHandler when wrapping ManagedHandler to map its + /// Helper type used by HttpClientHandler when wrapping SocketsHttpHandler to map its /// certificate validation callback to the one used by SslStream. /// internal sealed class CertificateCallbackMapper { public readonly Func FromHttpClientHandler; - public readonly RemoteCertificateValidationCallback ForManagedHandler; + public readonly RemoteCertificateValidationCallback ForSocketsHttpHandler; public CertificateCallbackMapper(Func fromHttpClientHandler) { FromHttpClientHandler = fromHttpClientHandler; - ForManagedHandler = (object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) => + ForSocketsHttpHandler = (object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) => FromHttpClientHandler(sender as HttpRequestMessage, certificate as X509Certificate2, chain, sslPolicyErrors); } } diff --git a/src/System.Net.Http/src/System/Net/Http/Managed/ConnectionCloseReadStream.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ConnectionCloseReadStream.cs similarity index 100% rename from src/System.Net.Http/src/System/Net/Http/Managed/ConnectionCloseReadStream.cs rename to src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ConnectionCloseReadStream.cs diff --git a/src/System.Net.Http/src/System/Net/Http/Managed/ContentLengthReadStream.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ContentLengthReadStream.cs similarity index 100% rename from src/System.Net.Http/src/System/Net/Http/Managed/ContentLengthReadStream.cs rename to src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ContentLengthReadStream.cs diff --git a/src/System.Net.Http/src/System/Net/Http/Managed/ContentLengthWriteStream.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ContentLengthWriteStream.cs similarity index 100% rename from src/System.Net.Http/src/System/Net/Http/Managed/ContentLengthWriteStream.cs rename to src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ContentLengthWriteStream.cs diff --git a/src/System.Net.Http/src/System/Net/Http/Managed/CookieHandler.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/CookieHandler.cs similarity index 100% rename from src/System.Net.Http/src/System/Net/Http/Managed/CookieHandler.cs rename to src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/CookieHandler.cs diff --git a/src/System.Net.Http/src/System/Net/Http/Managed/DecompressionHandler.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/DecompressionHandler.cs similarity index 100% rename from src/System.Net.Http/src/System/Net/Http/Managed/DecompressionHandler.cs rename to src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/DecompressionHandler.cs diff --git a/src/System.Net.Http/src/System/Net/Http/Managed/EmptyReadStream.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/EmptyReadStream.cs similarity index 100% rename from src/System.Net.Http/src/System/Net/Http/Managed/EmptyReadStream.cs rename to src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/EmptyReadStream.cs diff --git a/src/System.Net.Http/src/System/Net/Http/Managed/HttpConnection.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnection.cs similarity index 100% rename from src/System.Net.Http/src/System/Net/Http/Managed/HttpConnection.cs rename to src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnection.cs diff --git a/src/System.Net.Http/src/System/Net/Http/Managed/HttpConnectionContent.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionContent.cs similarity index 100% rename from src/System.Net.Http/src/System/Net/Http/Managed/HttpConnectionContent.cs rename to src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionContent.cs diff --git a/src/System.Net.Http/src/System/Net/Http/Managed/HttpConnectionHandler.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionHandler.cs similarity index 100% rename from src/System.Net.Http/src/System/Net/Http/Managed/HttpConnectionHandler.cs rename to src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionHandler.cs diff --git a/src/System.Net.Http/src/System/Net/Http/Managed/HttpConnectionKey.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionKey.cs similarity index 100% rename from src/System.Net.Http/src/System/Net/Http/Managed/HttpConnectionKey.cs rename to src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionKey.cs diff --git a/src/System.Net.Http/src/System/Net/Http/Managed/HttpConnectionPool.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs similarity index 100% rename from src/System.Net.Http/src/System/Net/Http/Managed/HttpConnectionPool.cs rename to src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs diff --git a/src/System.Net.Http/src/System/Net/Http/Managed/HttpConnectionPools.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPools.cs similarity index 100% rename from src/System.Net.Http/src/System/Net/Http/Managed/HttpConnectionPools.cs rename to src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPools.cs diff --git a/src/System.Net.Http/src/System/Net/Http/Managed/HttpConnectionSettings.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs similarity index 100% rename from src/System.Net.Http/src/System/Net/Http/Managed/HttpConnectionSettings.cs rename to src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs diff --git a/src/System.Net.Http/src/System/Net/Http/Managed/HttpContentDuplexStream.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpContentDuplexStream.cs similarity index 100% rename from src/System.Net.Http/src/System/Net/Http/Managed/HttpContentDuplexStream.cs rename to src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpContentDuplexStream.cs diff --git a/src/System.Net.Http/src/System/Net/Http/Managed/HttpContentReadStream.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpContentReadStream.cs similarity index 100% rename from src/System.Net.Http/src/System/Net/Http/Managed/HttpContentReadStream.cs rename to src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpContentReadStream.cs diff --git a/src/System.Net.Http/src/System/Net/Http/Managed/HttpContentStream.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpContentStream.cs similarity index 100% rename from src/System.Net.Http/src/System/Net/Http/Managed/HttpContentStream.cs rename to src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpContentStream.cs diff --git a/src/System.Net.Http/src/System/Net/Http/Managed/HttpContentWriteStream.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpContentWriteStream.cs similarity index 100% rename from src/System.Net.Http/src/System/Net/Http/Managed/HttpContentWriteStream.cs rename to src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpContentWriteStream.cs diff --git a/src/System.Net.Http/src/System/Net/Http/Managed/HttpEnvironmentProxy.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpEnvironmentProxy.cs similarity index 100% rename from src/System.Net.Http/src/System/Net/Http/Managed/HttpEnvironmentProxy.cs rename to src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpEnvironmentProxy.cs diff --git a/src/System.Net.Http/src/System/Net/Http/Managed/HttpProxyConnectionHandler.Unix.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpProxyConnectionHandler.Unix.cs similarity index 100% rename from src/System.Net.Http/src/System/Net/Http/Managed/HttpProxyConnectionHandler.Unix.cs rename to src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpProxyConnectionHandler.Unix.cs diff --git a/src/System.Net.Http/src/System/Net/Http/Managed/HttpProxyConnectionHandler.Windows.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpProxyConnectionHandler.Windows.cs similarity index 100% rename from src/System.Net.Http/src/System/Net/Http/Managed/HttpProxyConnectionHandler.Windows.cs rename to src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpProxyConnectionHandler.Windows.cs diff --git a/src/System.Net.Http/src/System/Net/Http/Managed/HttpProxyConnectionHandler.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpProxyConnectionHandler.cs similarity index 100% rename from src/System.Net.Http/src/System/Net/Http/Managed/HttpProxyConnectionHandler.cs rename to src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpProxyConnectionHandler.cs diff --git a/src/System.Net.Http/src/System/Net/Http/Managed/RawConnectionStream.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/RawConnectionStream.cs similarity index 100% rename from src/System.Net.Http/src/System/Net/Http/Managed/RawConnectionStream.cs rename to src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/RawConnectionStream.cs diff --git a/src/System.Net.Http/src/System/Net/Http/Managed/ManagedHandler.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs similarity index 97% rename from src/System.Net.Http/src/System/Net/Http/Managed/ManagedHandler.cs rename to src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs index 66df25941a4e..05a73c201242 100644 --- a/src/System.Net.Http/src/System/Net/Http/Managed/ManagedHandler.cs +++ b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs @@ -9,7 +9,7 @@ namespace System.Net.Http { - internal sealed class ManagedHandler : HttpMessageHandler + public sealed class SocketsHttpHandler : HttpMessageHandler { private readonly HttpConnectionSettings _settings = new HttpConnectionSettings(); private HttpMessageHandler _handler; @@ -19,7 +19,7 @@ private void CheckDisposed() { if (_disposed) { - throw new ObjectDisposedException(nameof(ManagedHandler)); + throw new ObjectDisposedException(nameof(SocketsHttpHandler)); } } @@ -169,7 +169,7 @@ public int MaxResponseHeadersLength public SslClientAuthenticationOptions SslOptions { - get => _settings._sslOptions; + get => _settings._sslOptions ?? (_settings._sslOptions = new SslClientAuthenticationOptions()); set { CheckDisposedOrStarted(); diff --git a/src/System.Net.Http/tests/FunctionalTests/DiagnosticsTests.cs b/src/System.Net.Http/tests/FunctionalTests/DiagnosticsTests.cs index 5a991d1d942d..362745ad476d 100644 --- a/src/System.Net.Http/tests/FunctionalTests/DiagnosticsTests.cs +++ b/src/System.Net.Http/tests/FunctionalTests/DiagnosticsTests.cs @@ -47,7 +47,7 @@ public static void EventSource_ExistsWithCorrectId() [Fact] public void SendAsync_ExpectedDiagnosticSourceLogging() { - RemoteInvoke(useManagedHandlerString => + RemoteInvoke(useSocketsHttpHandlerString => { bool requestLogged = false; Guid requestGuid = Guid.Empty; @@ -89,7 +89,7 @@ public void SendAsync_ExpectedDiagnosticSourceLogging() using (DiagnosticListener.AllListeners.Subscribe(diagnosticListenerObserver)) { diagnosticListenerObserver.Enable( s => !s.Contains("HttpRequestOut")); - using (HttpClient client = CreateHttpClient(useManagedHandlerString)) + using (HttpClient client = CreateHttpClient(useSocketsHttpHandlerString)) { client.GetAsync(Configuration.Http.RemoteEchoServer).Result.Dispose(); } @@ -104,7 +104,7 @@ public void SendAsync_ExpectedDiagnosticSourceLogging() } return SuccessExitCode; - }, UseManagedHandler.ToString()).Dispose(); + }, UseSocketsHttpHandler.ToString()).Dispose(); } /// @@ -115,7 +115,7 @@ public void SendAsync_ExpectedDiagnosticSourceLogging() [Fact] public void SendAsync_ExpectedDiagnosticSourceNoLogging() { - RemoteInvoke(useManagedHandlerString => + RemoteInvoke(useSocketsHttpHandlerString => { bool requestLogged = false; bool responseLogged = false; @@ -144,7 +144,7 @@ public void SendAsync_ExpectedDiagnosticSourceNoLogging() using (DiagnosticListener.AllListeners.Subscribe(diagnosticListenerObserver)) { - using (HttpClient client = CreateHttpClient(useManagedHandlerString)) + using (HttpClient client = CreateHttpClient(useSocketsHttpHandlerString)) { LoopbackServer.CreateServerAsync(async (server, url) => { @@ -164,7 +164,7 @@ public void SendAsync_ExpectedDiagnosticSourceNoLogging() Assert.False(activityStopLogged, "HttpRequestOut.Stop was logged while logging disabled."); } return SuccessExitCode; - }, UseManagedHandler.ToString()).Dispose(); + }, UseSocketsHttpHandler.ToString()).Dispose(); } [ActiveIssue(23771, TestPlatforms.AnyUnix)] @@ -172,7 +172,7 @@ public void SendAsync_ExpectedDiagnosticSourceNoLogging() [Fact] public void SendAsync_HttpTracingEnabled_Succeeds() { - RemoteInvoke(async useManagedHandlerString => + RemoteInvoke(async useSocketsHttpHandlerString => { using (var listener = new TestEventListener("Microsoft-System-Net-Http", EventLevel.Verbose)) { @@ -180,7 +180,7 @@ public void SendAsync_HttpTracingEnabled_Succeeds() await listener.RunWithCallbackAsync(events.Enqueue, async () => { // Exercise various code paths to get coverage of tracing - using (HttpClient client = CreateHttpClient(useManagedHandlerString)) + using (HttpClient client = CreateHttpClient(useSocketsHttpHandlerString)) { // Do a get to a loopback server await LoopbackServer.CreateServerAsync(async (server, url) => @@ -209,14 +209,14 @@ await TestHelper.WhenAllCompletedOrAnyFailed( } return SuccessExitCode; - }, UseManagedHandler.ToString()).Dispose(); + }, UseSocketsHttpHandler.ToString()).Dispose(); } [OuterLoop] // TODO: Issue #11345 [Fact] public void SendAsync_ExpectedDiagnosticExceptionLogging() { - RemoteInvoke(useManagedHandlerString => + RemoteInvoke(useSocketsHttpHandlerString => { bool exceptionLogged = false; bool responseLogged = false; @@ -242,7 +242,7 @@ public void SendAsync_ExpectedDiagnosticExceptionLogging() using (DiagnosticListener.AllListeners.Subscribe(diagnosticListenerObserver)) { diagnosticListenerObserver.Enable(s => !s.Contains("HttpRequestOut")); - using (HttpClient client = CreateHttpClient(useManagedHandlerString)) + using (HttpClient client = CreateHttpClient(useSocketsHttpHandlerString)) { Assert.ThrowsAsync(() => client.GetAsync($"http://{Guid.NewGuid()}.com")).Wait(); } @@ -254,7 +254,7 @@ public void SendAsync_ExpectedDiagnosticExceptionLogging() } return SuccessExitCode; - }, UseManagedHandler.ToString()).Dispose(); + }, UseSocketsHttpHandler.ToString()).Dispose(); } [ActiveIssue(23209)] @@ -262,7 +262,7 @@ public void SendAsync_ExpectedDiagnosticExceptionLogging() [Fact] public void SendAsync_ExpectedDiagnosticCancelledLogging() { - RemoteInvoke(useManagedHandlerString => + RemoteInvoke(useSocketsHttpHandlerString => { bool cancelLogged = false; var diagnosticListenerObserver = new FakeDiagnosticListenerObserver(kvp => @@ -279,7 +279,7 @@ public void SendAsync_ExpectedDiagnosticCancelledLogging() using (DiagnosticListener.AllListeners.Subscribe(diagnosticListenerObserver)) { diagnosticListenerObserver.Enable(s => !s.Contains("HttpRequestOut")); - using (HttpClient client = CreateHttpClient(useManagedHandlerString)) + using (HttpClient client = CreateHttpClient(useSocketsHttpHandlerString)) { LoopbackServer.CreateServerAsync(async (server, url) => { @@ -301,14 +301,14 @@ public void SendAsync_ExpectedDiagnosticCancelledLogging() diagnosticListenerObserver.Disable(); return SuccessExitCode; - }, UseManagedHandler.ToString()).Dispose(); + }, UseSocketsHttpHandler.ToString()).Dispose(); } [OuterLoop] // TODO: Issue #11345 [Fact] public void SendAsync_ExpectedDiagnosticSourceActivityLogging() { - RemoteInvoke(useManagedHandlerString => + RemoteInvoke(useSocketsHttpHandlerString => { bool requestLogged = false; bool responseLogged = false; @@ -354,7 +354,7 @@ public void SendAsync_ExpectedDiagnosticSourceActivityLogging() using (DiagnosticListener.AllListeners.Subscribe(diagnosticListenerObserver)) { diagnosticListenerObserver.Enable(s => s.Contains("HttpRequestOut")); - using (HttpClient client = CreateHttpClient(useManagedHandlerString)) + using (HttpClient client = CreateHttpClient(useSocketsHttpHandlerString)) { LoopbackServer.CreateServerAsync(async (server, url) => { @@ -378,14 +378,14 @@ public void SendAsync_ExpectedDiagnosticSourceActivityLogging() } return SuccessExitCode; - }, UseManagedHandler.ToString()).Dispose(); + }, UseSocketsHttpHandler.ToString()).Dispose(); } [OuterLoop] // TODO: Issue #11345 [Fact] public void SendAsync_ExpectedDiagnosticSourceUrlFilteredActivityLogging() { - RemoteInvoke(useManagedHandlerString => + RemoteInvoke(useSocketsHttpHandlerString => { bool activityStartLogged = false; bool activityStopLogged = false; @@ -408,7 +408,7 @@ public void SendAsync_ExpectedDiagnosticSourceUrlFilteredActivityLogging() } return true; }); - using (HttpClient client = CreateHttpClient(useManagedHandlerString)) + using (HttpClient client = CreateHttpClient(useSocketsHttpHandlerString)) { client.GetAsync(Configuration.Http.RemoteEchoServer).Result.Dispose(); } @@ -419,14 +419,14 @@ public void SendAsync_ExpectedDiagnosticSourceUrlFilteredActivityLogging() } return SuccessExitCode; - }, UseManagedHandler.ToString()).Dispose(); + }, UseSocketsHttpHandler.ToString()).Dispose(); } [OuterLoop] // TODO: Issue #11345 [Fact] public void SendAsync_ExpectedDiagnosticExceptionActivityLogging() { - RemoteInvoke(useManagedHandlerString => + RemoteInvoke(useSocketsHttpHandlerString => { bool exceptionLogged = false; bool activityStopLogged = false; @@ -453,7 +453,7 @@ public void SendAsync_ExpectedDiagnosticExceptionActivityLogging() using (DiagnosticListener.AllListeners.Subscribe(diagnosticListenerObserver)) { diagnosticListenerObserver.Enable(); - using (HttpClient client = CreateHttpClient(useManagedHandlerString)) + using (HttpClient client = CreateHttpClient(useSocketsHttpHandlerString)) { Assert.ThrowsAsync(() => client.GetAsync($"http://{Guid.NewGuid()}.com")).Wait(); } @@ -465,14 +465,14 @@ public void SendAsync_ExpectedDiagnosticExceptionActivityLogging() } return SuccessExitCode; - }, UseManagedHandler.ToString()).Dispose(); + }, UseSocketsHttpHandler.ToString()).Dispose(); } [OuterLoop] // TODO: Issue #11345 [Fact] public void SendAsync_ExpectedDiagnosticSourceNewAndDeprecatedEventsLogging() { - RemoteInvoke(useManagedHandlerString => + RemoteInvoke(useSocketsHttpHandlerString => { bool requestLogged = false; bool responseLogged = false; @@ -490,7 +490,7 @@ public void SendAsync_ExpectedDiagnosticSourceNewAndDeprecatedEventsLogging() using (DiagnosticListener.AllListeners.Subscribe(diagnosticListenerObserver)) { diagnosticListenerObserver.Enable(); - using (HttpClient client = CreateHttpClient(useManagedHandlerString)) + using (HttpClient client = CreateHttpClient(useSocketsHttpHandlerString)) { client.GetAsync(Configuration.Http.RemoteEchoServer).Result.Dispose(); } @@ -504,14 +504,14 @@ public void SendAsync_ExpectedDiagnosticSourceNewAndDeprecatedEventsLogging() } return SuccessExitCode; - }, UseManagedHandler.ToString()).Dispose(); + }, UseSocketsHttpHandler.ToString()).Dispose(); } [OuterLoop] // TODO: Issue #11345 [Fact] public void SendAsync_ExpectedDiagnosticExceptionOnlyActivityLogging() { - RemoteInvoke(useManagedHandlerString => + RemoteInvoke(useSocketsHttpHandlerString => { bool exceptionLogged = false; bool activityLogged = false; @@ -530,7 +530,7 @@ public void SendAsync_ExpectedDiagnosticExceptionOnlyActivityLogging() using (DiagnosticListener.AllListeners.Subscribe(diagnosticListenerObserver)) { diagnosticListenerObserver.Enable(s => s.Equals("System.Net.Http.Exception")); - using (HttpClient client = CreateHttpClient(useManagedHandlerString)) + using (HttpClient client = CreateHttpClient(useSocketsHttpHandlerString)) { Assert.ThrowsAsync(() => client.GetAsync($"http://{Guid.NewGuid()}.com")).Wait(); } @@ -542,14 +542,14 @@ public void SendAsync_ExpectedDiagnosticExceptionOnlyActivityLogging() } return SuccessExitCode; - }, UseManagedHandler.ToString()).Dispose(); + }, UseSocketsHttpHandler.ToString()).Dispose(); } [OuterLoop] // TODO: Issue #11345 [Fact] public void SendAsync_ExpectedDiagnosticStopOnlyActivityLogging() { - RemoteInvoke(useManagedHandlerString => + RemoteInvoke(useSocketsHttpHandlerString => { bool activityStartLogged = false; bool activityStopLogged = false; @@ -567,7 +567,7 @@ public void SendAsync_ExpectedDiagnosticStopOnlyActivityLogging() using (DiagnosticListener.AllListeners.Subscribe(diagnosticListenerObserver)) { diagnosticListenerObserver.Enable(s => s.Equals("System.Net.Http.HttpRequestOut")); - using (HttpClient client = CreateHttpClient(useManagedHandlerString)) + using (HttpClient client = CreateHttpClient(useSocketsHttpHandlerString)) { client.GetAsync(Configuration.Http.RemoteEchoServer).Result.Dispose(); } @@ -579,7 +579,7 @@ public void SendAsync_ExpectedDiagnosticStopOnlyActivityLogging() } return SuccessExitCode; - }, UseManagedHandler.ToString()).Dispose(); + }, UseSocketsHttpHandler.ToString()).Dispose(); } [ActiveIssue(23209)] @@ -587,7 +587,7 @@ public void SendAsync_ExpectedDiagnosticStopOnlyActivityLogging() [Fact] public void SendAsync_ExpectedDiagnosticCancelledActivityLogging() { - RemoteInvoke(useManagedHandlerString => + RemoteInvoke(useSocketsHttpHandlerString => { bool cancelLogged = false; var diagnosticListenerObserver = new FakeDiagnosticListenerObserver(kvp => @@ -605,7 +605,7 @@ public void SendAsync_ExpectedDiagnosticCancelledActivityLogging() using (DiagnosticListener.AllListeners.Subscribe(diagnosticListenerObserver)) { diagnosticListenerObserver.Enable(); - using (HttpClient client = CreateHttpClient(useManagedHandlerString)) + using (HttpClient client = CreateHttpClient(useSocketsHttpHandlerString)) { LoopbackServer.CreateServerAsync(async (server, url) => { @@ -627,7 +627,7 @@ public void SendAsync_ExpectedDiagnosticCancelledActivityLogging() diagnosticListenerObserver.Disable(); return SuccessExitCode; - }, UseManagedHandler.ToString()).Dispose(); + }, UseSocketsHttpHandler.ToString()).Dispose(); } private static T GetPropertyValueFromAnonymousTypeInstance(object obj, string propertyName) diff --git a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.ClientCertificates.cs b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.ClientCertificates.cs index 9530ce22fe0a..801c5d2d824a 100644 --- a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.ClientCertificates.cs +++ b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.ClientCertificates.cs @@ -75,7 +75,7 @@ public void ClientCertificates_ClientCertificateOptionsAutomatic_ThrowsException [Fact] public async Task Automatic_SSLBackendNotSupported_ThrowsPlatformNotSupportedException() { - if (BackendSupportsCustomCertificateHandling) // can't use [Conditional*] right now as it's evaluated at the wrong time for the managed handler + if (BackendSupportsCustomCertificateHandling) // can't use [Conditional*] right now as it's evaluated at the wrong time for SocketsHttpHandler { return; } @@ -92,7 +92,7 @@ public async Task Automatic_SSLBackendNotSupported_ThrowsPlatformNotSupportedExc [Fact] public async Task Manual_SSLBackendNotSupported_ThrowsPlatformNotSupportedException() { - if (BackendSupportsCustomCertificateHandling) // can't use [Conditional*] right now as it's evaluated at the wrong time for the managed handler + if (BackendSupportsCustomCertificateHandling) // can't use [Conditional*] right now as it's evaluated at the wrong time for SocketsHttpHandler { return; } @@ -109,7 +109,7 @@ public async Task Manual_SSLBackendNotSupported_ThrowsPlatformNotSupportedExcept [Fact] public void Manual_SendClientCertificateWithClientAuthEKUToRemoteServer_OK() { - if (!CanTestClientCertificates) // can't use [Conditional*] right now as it's evaluated at the wrong time for the managed handler + if (!CanTestClientCertificates) // can't use [Conditional*] right now as it's evaluated at the wrong time for SocketsHttpHandler { _output.WriteLine($"Skipping {nameof(Manual_SendClientCertificateWithClientAuthEKUToRemoteServer_OK)}()"); return; @@ -118,10 +118,10 @@ public void Manual_SendClientCertificateWithClientAuthEKUToRemoteServer_OK() // UAP HTTP stack caches connections per-process. This causes interference when these tests run in // the same process as the other tests. Each test needs to be isolated to its own process. // See dicussion: https://github.com/dotnet/corefx/issues/21945 - RemoteInvoke(async useManagedHandlerString => + RemoteInvoke(async useSocketsHttpHandlerString => { var cert = Configuration.Certificates.GetClientCertificate(); - HttpClientHandler handler = CreateHttpClientHandler(useManagedHandlerString); + HttpClientHandler handler = CreateHttpClientHandler(useSocketsHttpHandlerString); handler.ClientCertificates.Add(cert); using (var client = new HttpClient(handler)) { @@ -135,20 +135,20 @@ public void Manual_SendClientCertificateWithClientAuthEKUToRemoteServer_OK() return SuccessExitCode; } - }, UseManagedHandler.ToString()).Dispose(); + }, UseSocketsHttpHandler.ToString()).Dispose(); } [OuterLoop] // TODO: Issue #11345 [Fact] public void Manual_SendClientCertificateWithServerAuthEKUToRemoteServer_Forbidden() { - if (UseManagedHandler) + if (UseSocketsHttpHandler) { - // TODO #23128: The managed handler is currently sending out client certificates when it shouldn't. + // TODO #23128: SocketsHttpHandler is currently sending out client certificates when it shouldn't. return; } - if (!CanTestClientCertificates) // can't use [Conditional*] right now as it's evaluated at the wrong time for the managed handler + if (!CanTestClientCertificates) // can't use [Conditional*] right now as it's evaluated at the wrong time for SocketsHttpHandler { _output.WriteLine($"Skipping {nameof(Manual_SendClientCertificateWithServerAuthEKUToRemoteServer_Forbidden)}()"); return; @@ -157,10 +157,10 @@ public void Manual_SendClientCertificateWithServerAuthEKUToRemoteServer_Forbidde // UAP HTTP stack caches connections per-process. This causes interference when these tests run in // the same process as the other tests. Each test needs to be isolated to its own process. // See dicussion: https://github.com/dotnet/corefx/issues/21945 - RemoteInvoke(async useManagedHandlerString => + RemoteInvoke(async useSocketsHttpHandlerString => { var cert = Configuration.Certificates.GetServerCertificate(); - HttpClientHandler handler = CreateHttpClientHandler(useManagedHandlerString); + HttpClientHandler handler = CreateHttpClientHandler(useSocketsHttpHandlerString); handler.ClientCertificates.Add(cert); using (var client = new HttpClient(handler)) { @@ -169,14 +169,14 @@ public void Manual_SendClientCertificateWithServerAuthEKUToRemoteServer_Forbidde return SuccessExitCode; } - }, UseManagedHandler.ToString()).Dispose(); + }, UseSocketsHttpHandler.ToString()).Dispose(); } [OuterLoop] // TODO: Issue #11345 [Fact] public void Manual_SendClientCertificateWithNoEKUToRemoteServer_OK() { - if (!CanTestClientCertificates) // can't use [Conditional*] right now as it's evaluated at the wrong time for the managed handler + if (!CanTestClientCertificates) // can't use [Conditional*] right now as it's evaluated at the wrong time for SocketsHttpHandler { _output.WriteLine($"Skipping {nameof(Manual_SendClientCertificateWithNoEKUToRemoteServer_OK)}()"); return; @@ -185,10 +185,10 @@ public void Manual_SendClientCertificateWithNoEKUToRemoteServer_OK() // UAP HTTP stack caches connections per-process. This causes interference when these tests run in // the same process as the other tests. Each test needs to be isolated to its own process. // See dicussion: https://github.com/dotnet/corefx/issues/21945 - RemoteInvoke(async useManagedHandlerString => + RemoteInvoke(async useSocketsHttpHandlerString => { var cert = Configuration.Certificates.GetNoEKUCertificate(); - HttpClientHandler handler = CreateHttpClientHandler(useManagedHandlerString); + HttpClientHandler handler = CreateHttpClientHandler(useSocketsHttpHandlerString); handler.ClientCertificates.Add(cert); using (var client = new HttpClient(handler)) { @@ -202,7 +202,7 @@ public void Manual_SendClientCertificateWithNoEKUToRemoteServer_OK() return SuccessExitCode; } - }, UseManagedHandler.ToString()).Dispose(); + }, UseSocketsHttpHandler.ToString()).Dispose(); } [SkipOnTargetFramework(TargetFrameworkMonikers.Uap, "dotnet/corefx #20010")] @@ -215,7 +215,7 @@ public async Task Manual_CertificateSentMatchesCertificateReceived_Success( int numberOfRequests, bool reuseClient) // validate behavior with and without connection pooling, which impacts client cert usage { - if (!BackendSupportsCustomCertificateHandling) // can't use [Conditional*] right now as it's evaluated at the wrong time for the managed handler + if (!BackendSupportsCustomCertificateHandling) // can't use [Conditional*] right now as it's evaluated at the wrong time for SocketsHttpHandler { _output.WriteLine($"Skipping {nameof(Manual_CertificateSentMatchesCertificateReceived_Success)}()"); return; @@ -282,7 +282,7 @@ await LoopbackServer.CreateServerAsync(async (server, url) => } private bool BackendSupportsCustomCertificateHandling => - UseManagedHandler || + UseSocketsHttpHandler || new HttpClientHandler_ServerCertificates_Test().BackendSupportsCustomCertificateHandling; } } diff --git a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.DefaultProxyCredentials.cs b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.DefaultProxyCredentials.cs index ebdf3a648d46..7e91844a903a 100644 --- a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.DefaultProxyCredentials.cs +++ b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.DefaultProxyCredentials.cs @@ -80,7 +80,7 @@ public async Task ProxyExplicitlyProvided_DefaultCredentials_Ignored() [Theory] [InlineData(false)] [InlineData(true)] - [ActiveIssue(25640, TestPlatforms.Windows)] // TODO It should be enabled for managed Handler on all platforms + [ActiveIssue(25640, TestPlatforms.Windows)] // TODO It should be enabled for SocketsHttpHandler on all platforms public void ProxySetViaEnvironmentVariable_DefaultProxyCredentialsUsed(bool useProxy) { int port = 0; @@ -98,9 +98,9 @@ public void ProxySetViaEnvironmentVariable_DefaultProxyCredentialsUsed(bool useP // test in another process. var psi = new ProcessStartInfo(); psi.Environment.Add("http_proxy", $"http://localhost:{port}"); - RemoteInvoke((useProxyString, useManagedHandlerString) => + RemoteInvoke((useProxyString, useSocketsHttpHandlerString) => { - using (HttpClientHandler handler = CreateHttpClientHandler(useManagedHandlerString)) + using (HttpClientHandler handler = CreateHttpClientHandler(useSocketsHttpHandlerString)) using (var client = new HttpClient(handler)) { var creds = new NetworkCredential(ExpectedUsername, ExpectedPassword); @@ -117,7 +117,7 @@ public void ProxySetViaEnvironmentVariable_DefaultProxyCredentialsUsed(bool useP TestHelper.VerifyResponseBody(responseStringTask.Result, responseTask.Result.Content.Headers.ContentMD5, false, null); } return SuccessExitCode; - }, useProxy.ToString(), UseManagedHandler.ToString(), new RemoteInvokeOptions { StartInfo = psi }).Dispose(); + }, useProxy.ToString(), UseSocketsHttpHandler.ToString(), new RemoteInvokeOptions { StartInfo = psi }).Dispose(); if (useProxy) { diff --git a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.ServerCertificates.Unix.cs b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.ServerCertificates.Unix.cs index 35f5eada45a9..81b8dcadbf12 100644 --- a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.ServerCertificates.Unix.cs +++ b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.ServerCertificates.Unix.cs @@ -53,7 +53,7 @@ internal bool BackendSupportsCustomCertificateHandling { get { - if (UseManagedHandler) + if (UseSocketsHttpHandler) { return true; } diff --git a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.ServerCertificates.cs b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.ServerCertificates.cs index 423b23e96ee4..54f0b3737a69 100644 --- a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.ServerCertificates.cs +++ b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.ServerCertificates.cs @@ -92,9 +92,9 @@ public async Task UseCallback_HaveNoCredsAndUseAuthenticatedCustomProxyAndPostTo { return; } - if (UseManagedHandler) + if (UseSocketsHttpHandler) { - return; // TODO #23136: SSL proxy tunneling not yet implemented in ManagedHandler + return; // TODO #23136: SSL proxy tunneling not yet implemented in SocketsHttpHandler } int port; @@ -284,7 +284,7 @@ public async Task NoCallback_RevokedCertificate_NoRevocationChecking_Succeeds() } catch (HttpRequestException) { - if (UseManagedHandler || !ShouldSuppressRevocationException) + if (UseSocketsHttpHandler || !ShouldSuppressRevocationException) throw; } } @@ -314,7 +314,7 @@ public async Task NoCallback_RevokedCertificate_RevocationChecking_Fails() new object[] { Configuration.Http.WrongHostNameCertRemoteServer , SslPolicyErrors.RemoteCertificateNameMismatch}, }; - private async Task UseCallback_BadCertificate_ExpectedPolicyErrors_Helper(string url, bool useManagedHandler, SslPolicyErrors expectedErrors) + private async Task UseCallback_BadCertificate_ExpectedPolicyErrors_Helper(string url, bool useSocketsHttpHandler, SslPolicyErrors expectedErrors) { if (!BackendSupportsCustomCertificateHandling) { @@ -322,7 +322,7 @@ private async Task UseCallback_BadCertificate_ExpectedPolicyErrors_Helper(string return; } - HttpClientHandler handler = CreateHttpClientHandler(useManagedHandler); + HttpClientHandler handler = CreateHttpClientHandler(useSocketsHttpHandler); using (var client = new HttpClient(handler)) { bool callbackCalled = false; @@ -333,9 +333,9 @@ private async Task UseCallback_BadCertificate_ExpectedPolicyErrors_Helper(string Assert.NotNull(request); Assert.NotNull(cert); Assert.NotNull(chain); - if (!useManagedHandler) + if (!useSocketsHttpHandler) { - // TODO #23137: This test is failing with the managed handler on the exact value of the managed errors, + // TODO #23137: This test is failing with SocketsHttpHandler on the exact value of the managed errors, // e.g. reporting "RemoteCertificateNameMismatch, RemoteCertificateChainErrors" when we only expect // "RemoteCertificateChainErrors" Assert.Equal(expectedErrors, errors); @@ -371,19 +371,19 @@ public async Task UseCallback_BadCertificate_ExpectedPolicyErrors(string url, Ss // UAP HTTP stack caches connections per-process. This causes interference when these tests run in // the same process as the other tests. Each test needs to be isolated to its own process. // See dicussion: https://github.com/dotnet/corefx/issues/21945 - RemoteInvoke((remoteUrl, remoteExpectedErrors, useManagedHandlerString) => + RemoteInvoke((remoteUrl, remoteExpectedErrors, useSocketsHttpHandlerString) => { UseCallback_BadCertificate_ExpectedPolicyErrors_Helper( remoteUrl, - bool.Parse(useManagedHandlerString), + bool.Parse(useSocketsHttpHandlerString), (SslPolicyErrors)Enum.Parse(typeof(SslPolicyErrors), remoteExpectedErrors)).Wait(); return SuccessExitCode; - }, url, expectedErrors.ToString(), UseManagedHandler.ToString()).Dispose(); + }, url, expectedErrors.ToString(), UseSocketsHttpHandler.ToString()).Dispose(); } else { - await UseCallback_BadCertificate_ExpectedPolicyErrors_Helper(url, UseManagedHandler, expectedErrors); + await UseCallback_BadCertificate_ExpectedPolicyErrors_Helper(url, UseSocketsHttpHandler, expectedErrors); } } catch (HttpRequestException e) when (e.InnerException?.GetType().Name == "WinHttpException" && diff --git a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.SslProtocols.Unix.cs b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.SslProtocols.Unix.cs index 3437df8d0197..db65a14841ae 100644 --- a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.SslProtocols.Unix.cs +++ b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.SslProtocols.Unix.cs @@ -17,7 +17,7 @@ namespace System.Net.Http.Functional.Tests public partial class HttpClientHandler_SslProtocols_Test { private bool BackendSupportsSslConfiguration => - UseManagedHandler || + UseSocketsHttpHandler || (Interop.Http.GetSslVersionDescription()?.StartsWith(Interop.Http.OpenSsl10Description, StringComparison.OrdinalIgnoreCase) ?? false); private bool SSLv3DisabledByDefault => diff --git a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.SslProtocols.cs b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.SslProtocols.cs index 2370fa76a098..cbe360ea7493 100644 --- a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.SslProtocols.cs +++ b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.SslProtocols.cs @@ -100,9 +100,9 @@ public async Task GetAsync_AllowedSSLVersion_Succeeds(SslProtocols acceptedProto return; } - if (UseManagedHandler) + if (UseSocketsHttpHandler) { - // TODO #26186: The managed handler is failing on some OSes. + // TODO #26186: SocketsHttpHandler is failing on some OSes. return; } @@ -141,9 +141,9 @@ await TestHelper.WhenAllCompletedOrAnyFailed( [MemberData(nameof(SupportedSSLVersionServers))] public async Task GetAsync_SupportedSSLVersion_Succeeds(SslProtocols sslProtocols, string url) { - if (UseManagedHandler) + if (UseSocketsHttpHandler) { - // TODO #26186: The managed handler is failing on some OSes. + // TODO #26186: SocketsHttpHandler is failing on some OSes. return; } @@ -197,7 +197,7 @@ public async Task GetAsync_UnsupportedSSLVersion_Throws(string name, string url) return; } - if (UseManagedHandler && !PlatformDetection.IsWindows10Version1607OrGreater) + if (UseSocketsHttpHandler && !PlatformDetection.IsWindows10Version1607OrGreater) { // On Windows, https://github.com/dotnet/corefx/issues/21925#issuecomment-313408314 // On Linux, an older version of OpenSSL may permit negotiating SSLv3. diff --git a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.cs b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.cs index ea3992183e1d..6af897995072 100644 --- a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.cs +++ b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.cs @@ -312,11 +312,11 @@ public async Task SendAsync_GetWithValidHostHeader_Success(bool withPort) [Fact] public async Task SendAsync_GetWithInvalidHostHeader_ThrowsException() { - if (PlatformDetection.IsNetCore && !UseManagedHandler) + if (PlatformDetection.IsNetCore && !UseSocketsHttpHandler) { // [ActiveIssue(24862)] // WinHttpHandler and CurlHandler do not use the Host header to influence the SSL auth. - // .NET Framework and ManagedHandler do. + // .NET Framework and SocketsHttpHandler do. return; } @@ -504,9 +504,9 @@ public void GetAsync_ServerNeedsAuthAndNoCredential_StatusCodeUnauthorized() // UAP HTTP stack caches connections per-process. This causes interference when these tests run in // the same process as the other tests. Each test needs to be isolated to its own process. // See dicussion: https://github.com/dotnet/corefx/issues/21945 - RemoteInvoke(async useManagedHandlerString => + RemoteInvoke(async useSocketsHttpHandlerString => { - using (var client = CreateHttpClient(useManagedHandlerString)) + using (var client = CreateHttpClient(useSocketsHttpHandlerString)) { Uri uri = Configuration.Http.BasicAuthUriForCreds(secure: false, userName: Username, password: Password); using (HttpResponseMessage response = await client.GetAsync(uri)) @@ -516,7 +516,7 @@ public void GetAsync_ServerNeedsAuthAndNoCredential_StatusCodeUnauthorized() return SuccessExitCode; } - }, UseManagedHandler.ToString()).Dispose(); + }, UseSocketsHttpHandler.ToString()).Dispose(); } [OuterLoop] // TODO: Issue #11345 @@ -572,7 +572,7 @@ public async Task GetAsync_AllowAutoRedirectFalse_RedirectFromHttpToHttp_StatusC public async Task AllowAutoRedirect_True_ValidateNewMethodUsedOnRedirection( int statusCode, string oldMethod, string newMethod) { - if (!PlatformDetection.IsWindows && !UseManagedHandler && statusCode == 300 && oldMethod == "POST") + if (!PlatformDetection.IsWindows && !UseSocketsHttpHandler && statusCode == 300 && oldMethod == "POST") { // Known behavior: curl does not change method to "GET" // https://github.com/dotnet/corefx/issues/26434 @@ -692,7 +692,7 @@ public async Task GetAsync_AllowAutoRedirectTrue_RedirectFromHttpsToHttp_StatusC public async Task GetAsync_AllowAutoRedirectTrue_RedirectWithoutLocation_ReturnsOriginalResponse() { // [ActiveIssue(24819, TestPlatforms.Windows)] - if (PlatformDetection.IsWindows && PlatformDetection.IsNetCore && !UseManagedHandler) + if (PlatformDetection.IsWindows && PlatformDetection.IsNetCore && !UseSocketsHttpHandler) { return; } @@ -1086,44 +1086,48 @@ private static KeyValuePair GenerateCookie(string name, char rep return new KeyValuePair(name, value); } - public static object[][] CookieNameValues = - { - // WinHttpHandler calls WinHttpQueryHeaders to iterate through multiple Set-Cookie header values, - // using an initial buffer size of 128 chars. If the buffer is not large enough, WinHttpQueryHeaders - // returns an insufficient buffer error, allowing WinHttpHandler to try again with a larger buffer. - // Sometimes when WinHttpQueryHeaders fails due to insufficient buffer, it still advances the - // iteration index, which would cause header values to be missed if not handled correctly. - // - // In particular, WinHttpQueryHeader behaves as follows for the following header value lengths: - // * 0-127 chars: succeeds, index advances from 0 to 1. - // * 128-255 chars: fails due to insufficient buffer, index advances from 0 to 1. - // * 256+ chars: fails due to insufficient buffer, index stays at 0. - // - // The below overall header value lengths were chosen to exercise reading header values at these - // edges, to ensure WinHttpHandler does not miss multiple Set-Cookie headers. - - new object[] { GenerateCookie(name: "foo", repeat: 'a', overallHeaderValueLength: 126) }, - new object[] { GenerateCookie(name: "foo", repeat: 'a', overallHeaderValueLength: 127) }, - new object[] { GenerateCookie(name: "foo", repeat: 'a', overallHeaderValueLength: 128) }, - new object[] { GenerateCookie(name: "foo", repeat: 'a', overallHeaderValueLength: 129) }, - - new object[] { GenerateCookie(name: "foo", repeat: 'a', overallHeaderValueLength: 254) }, - new object[] { GenerateCookie(name: "foo", repeat: 'a', overallHeaderValueLength: 255) }, - new object[] { GenerateCookie(name: "foo", repeat: 'a', overallHeaderValueLength: 256) }, - new object[] { GenerateCookie(name: "foo", repeat: 'a', overallHeaderValueLength: 257) }, - - new object[] - { - new KeyValuePair( - ".AspNetCore.Antiforgery.Xam7_OeLcN4", - "CfDJ8NGNxAt7CbdClq3UJ8_6w_4661wRQZT1aDtUOIUKshbcV4P0NdS8klCL5qGSN-PNBBV7w23G6MYpQ81t0PMmzIN4O04fqhZ0u1YPv66mixtkX3iTi291DgwT3o5kozfQhe08-RAExEmXpoCbueP_QYM") + public static IEnumerable CookieNameValuesAndUseCookies() + { + foreach (bool useCookies in new[] { true, false }) + { + // WinHttpHandler calls WinHttpQueryHeaders to iterate through multiple Set-Cookie header values, + // using an initial buffer size of 128 chars. If the buffer is not large enough, WinHttpQueryHeaders + // returns an insufficient buffer error, allowing WinHttpHandler to try again with a larger buffer. + // Sometimes when WinHttpQueryHeaders fails due to insufficient buffer, it still advances the + // iteration index, which would cause header values to be missed if not handled correctly. + // + // In particular, WinHttpQueryHeader behaves as follows for the following header value lengths: + // * 0-127 chars: succeeds, index advances from 0 to 1. + // * 128-255 chars: fails due to insufficient buffer, index advances from 0 to 1. + // * 256+ chars: fails due to insufficient buffer, index stays at 0. + // + // The below overall header value lengths were chosen to exercise reading header values at these + // edges, to ensure WinHttpHandler does not miss multiple Set-Cookie headers. + + yield return new object[] { GenerateCookie(name: "foo", repeat: 'a', overallHeaderValueLength: 126), useCookies }; + yield return new object[] { GenerateCookie(name: "foo", repeat: 'a', overallHeaderValueLength: 127), useCookies }; + yield return new object[] { GenerateCookie(name: "foo", repeat: 'a', overallHeaderValueLength: 128), useCookies }; + yield return new object[] { GenerateCookie(name: "foo", repeat: 'a', overallHeaderValueLength: 129), useCookies }; + + yield return new object[] { GenerateCookie(name: "foo", repeat: 'a', overallHeaderValueLength: 254), useCookies }; + yield return new object[] { GenerateCookie(name: "foo", repeat: 'a', overallHeaderValueLength: 255), useCookies }; + yield return new object[] { GenerateCookie(name: "foo", repeat: 'a', overallHeaderValueLength: 256), useCookies }; + yield return new object[] { GenerateCookie(name: "foo", repeat: 'a', overallHeaderValueLength: 257), useCookies }; + + yield return new object[] + { + new KeyValuePair( + ".AspNetCore.Antiforgery.Xam7_OeLcN4", + "CfDJ8NGNxAt7CbdClq3UJ8_6w_4661wRQZT1aDtUOIUKshbcV4P0NdS8klCL5qGSN-PNBBV7w23G6MYpQ81t0PMmzIN4O04fqhZ0u1YPv66mixtkX3iTi291DgwT3o5kozfQhe08-RAExEmXpoCbueP_QYM"), + useCookies + }; } - }; + } [OuterLoop] // TODO: Issue #11345 [Theory] - [MemberData(nameof(CookieNameValues))] - public async Task GetAsync_ResponseWithSetCookieHeaders_AllCookiesRead(KeyValuePair cookie1) + [MemberData(nameof(CookieNameValuesAndUseCookies))] + public async Task GetAsync_ResponseWithSetCookieHeaders_AllCookiesRead(KeyValuePair cookie1, bool useCookies) { var cookie2 = new KeyValuePair(".AspNetCore.Session", "RAExEmXpoCbueP_QYM"); var cookie3 = new KeyValuePair("name", "value"); @@ -1131,28 +1135,39 @@ public async Task GetAsync_ResponseWithSetCookieHeaders_AllCookiesRead(KeyValueP await LoopbackServer.CreateServerAsync(async (server, url) => { using (HttpClientHandler handler = CreateHttpClientHandler()) - using (var client = new HttpClient(handler)) { - Task getResponseTask = client.GetAsync(url); - await TestHelper.WhenAllCompletedOrAnyFailed( - getResponseTask, - LoopbackServer.ReadRequestAndSendResponseAsync(server, - $"HTTP/1.1 200 OK\r\n" + - $"Date: {DateTimeOffset.UtcNow:R}\r\n" + - $"Set-Cookie: {cookie1.Key}={cookie1.Value}; Path=/\r\n" + - $"Set-Cookie : {cookie2.Key}={cookie2.Value}; Path=/\r\n" + // space before colon to verify header is trimmed and recognized - $"Set-Cookie: {cookie3.Key}={cookie3.Value}; Path=/\r\n" + - "\r\n")); + handler.UseCookies = useCookies; - using (HttpResponseMessage response = await getResponseTask) + using (var client = new HttpClient(handler)) { - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - CookieCollection cookies = handler.CookieContainer.GetCookies(url); + Task getResponseTask = client.GetAsync(url); + await TestHelper.WhenAllCompletedOrAnyFailed( + getResponseTask, + LoopbackServer.ReadRequestAndSendResponseAsync(server, + $"HTTP/1.1 200 OK\r\n" + + $"Date: {DateTimeOffset.UtcNow:R}\r\n" + + $"Set-Cookie: {cookie1.Key}={cookie1.Value}; Path=/\r\n" + + $"Set-Cookie : {cookie2.Key}={cookie2.Value}; Path=/\r\n" + // space before colon to verify header is trimmed and recognized + $"Set-Cookie: {cookie3.Key}={cookie3.Value}; Path=/\r\n" + + "\r\n")); - Assert.Equal(3, cookies.Count); - Assert.Equal(cookie1.Value, cookies[cookie1.Key].Value); - Assert.Equal(cookie2.Value, cookies[cookie2.Key].Value); - Assert.Equal(cookie3.Value, cookies[cookie3.Key].Value); + using (HttpResponseMessage response = await getResponseTask) + { + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + CookieCollection cookies = handler.CookieContainer.GetCookies(url); + if (useCookies) + { + Assert.Equal(3, cookies.Count); + Assert.Equal(cookie1.Value, cookies[cookie1.Key].Value); + Assert.Equal(cookie2.Value, cookies[cookie2.Key].Value); + Assert.Equal(cookie3.Value, cookies[cookie3.Key].Value); + } + else + { + Assert.Equal(0, cookies.Count); + } + } } } }); @@ -1411,9 +1426,9 @@ await writer.WriteAsync( [Fact] public async Task Dispose_DisposingHandlerCancelsActiveOperationsWithoutResponses() { - if (UseManagedHandler) + if (UseSocketsHttpHandler) { - // TODO #23131: The ManagedHandler isn't correctly handling disposal of the handler. + // TODO #23131: The SocketsHttpHandler isn't correctly handling disposal of the handler. // It should cause the outstanding requests to be canceled with OperationCanceledExceptions, // whereas currently it's resulting in ObjectDisposedExceptions. return; @@ -1757,7 +1772,7 @@ public async Task PostAsync_ExpectContinue_Success(bool? expectContinue, string using (HttpResponseMessage response = await client.SendAsync(req)) { Assert.Equal(HttpStatusCode.OK, response.StatusCode); - if (UseManagedHandler) + if (UseSocketsHttpHandler) { const string ExpectedReqHeader = "\"Expect\": \"100-continue\""; if (expectContinue == true && version == "1.1") @@ -1798,7 +1813,7 @@ await LoopbackServer.CreateServerAsync(async (server, uri) => canReadFunc: () => true, readAsyncFunc: (buffer, offset, count, cancellationToken) => syncFailure ? throw error : Task.Delay(1).ContinueWith(_ => throw error))); - if (UseManagedHandler || PlatformDetection.IsUap) + if (UseSocketsHttpHandler || PlatformDetection.IsUap) { HttpRequestException requestException = await Assert.ThrowsAsync(() => client.PostAsync(uri, content)); Assert.Same(error, requestException.InnerException); @@ -2048,7 +2063,7 @@ public async Task SendAsync_SendRequestUsingNoBodyMethodToEchoServerWithContent_ using (HttpResponseMessage response = await client.SendAsync(request)) { - if (method == "TRACE" && (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || UseManagedHandler)) + if (method == "TRACE" && (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || UseSocketsHttpHandler)) { // .NET Framework also allows the HttpWebRequest and HttpClient APIs to send a request using 'TRACE' // verb and a request body. The usual response from a server is "400 Bad Request". @@ -2090,8 +2105,8 @@ public async Task SendAsync_RequestVersion11_ServerReceivesVersion11Request() [Fact] public async Task SendAsync_RequestVersionNotSpecified_ServerReceivesVersion11Request() { - // Managed handler treats 0.0 as a bad version, and throws. - if (UseManagedHandler) + // SocketsHttpHandler treats 0.0 as a bad version, and throws. + if (UseSocketsHttpHandler) { return; } @@ -2115,9 +2130,9 @@ public async Task SendAsync_RequestVersion20_ResponseVersion20IfHttp2Supported(U _output.WriteLine("Skipping test due to Windows 10 version prior to Version 1703."); return; } - if (UseManagedHandler) + if (UseSocketsHttpHandler) { - // TODO #23134: The managed handler doesn't yet support HTTP/2. + // TODO #23134: SocketsHttpHandler doesn't yet support HTTP/2. return; } @@ -2167,9 +2182,9 @@ public async Task SendAsync_RequestVersion20_ResponseVersion20IfHttp2Supported(U [ConditionalTheory(nameof(IsWindows10Version1607OrGreater)), MemberData(nameof(Http2NoPushServers))] public async Task SendAsync_RequestVersion20_ResponseVersion20(Uri server) { - if (UseManagedHandler) + if (UseSocketsHttpHandler) { - // TODO #23134: The managed handler doesn't yet support HTTP/2. + // TODO #23134: SocketsHttpHandler doesn't yet support HTTP/2. return; } @@ -2246,9 +2261,9 @@ public async Task Proxy_BypassFalse_GetRequestGoesThroughCustomProxy_RfcComplian [MemberData(nameof(CredentialsForProxyNonRfcCompliant))] public async Task Proxy_BypassFalse_GetRequestGoesThroughCustomProxy_NonRfcCompliant(ICredentials creds, bool wrapCredsInCache) { - if (UseManagedHandler) + if (UseSocketsHttpHandler) { - // TODO #23135: ManagedHandler currently gets error "System.NotImplementedException : Basic auth: can't handle ':' in domain "dom:\ain"" + // TODO #23135: SocketsHttpHandler currently gets error "System.NotImplementedException : Basic auth: can't handle ':' in domain "dom:\ain"" return; } diff --git a/src/System.Net.Http/tests/FunctionalTests/HttpClientTestBase.cs b/src/System.Net.Http/tests/FunctionalTests/HttpClientTestBase.cs index bd8ecc9adf1a..c896cfedb55e 100644 --- a/src/System.Net.Http/tests/FunctionalTests/HttpClientTestBase.cs +++ b/src/System.Net.Http/tests/FunctionalTests/HttpClientTestBase.cs @@ -3,63 +3,47 @@ // See the LICENSE file in the project root for more information. using System.Diagnostics; -using System.Threading; +using System.Reflection; namespace System.Net.Http.Functional.Tests { public abstract class HttpClientTestBase : RemoteExecutorTestBase { - private const string ManagedHandlerEnvVar = "DOTNET_SYSTEM_NET_HTTP_USEMANAGEDHTTPCLIENTHANDLER"; - private static readonly LocalDataStoreSlot s_managedHandlerSlot; + protected virtual bool UseSocketsHttpHandler => false; - static HttpClientTestBase() - { - s_managedHandlerSlot = Thread.GetNamedDataSlot(ManagedHandlerEnvVar); - if (s_managedHandlerSlot == null) - { - try - { - s_managedHandlerSlot = Thread.AllocateNamedDataSlot(ManagedHandlerEnvVar); - } - catch (ArgumentException) - { - s_managedHandlerSlot = Thread.GetNamedDataSlot(ManagedHandlerEnvVar); - } - } - Debug.Assert(s_managedHandlerSlot != null); - } - - protected virtual bool UseManagedHandler => false; - - protected bool IsWinHttpHandler => !UseManagedHandler && PlatformDetection.IsWindows && !PlatformDetection.IsUap && !PlatformDetection.IsFullFramework; - protected bool IsCurlHandler => !UseManagedHandler && !PlatformDetection.IsWindows; - protected bool IsNetfxHandler => !UseManagedHandler && PlatformDetection.IsWindows && PlatformDetection.IsFullFramework; - protected bool IsUapHandler => !UseManagedHandler && PlatformDetection.IsWindows && PlatformDetection.IsUap; + protected bool IsWinHttpHandler => !UseSocketsHttpHandler && PlatformDetection.IsWindows && !PlatformDetection.IsUap && !PlatformDetection.IsFullFramework; + protected bool IsCurlHandler => !UseSocketsHttpHandler && !PlatformDetection.IsWindows; + protected bool IsNetfxHandler => !UseSocketsHttpHandler && PlatformDetection.IsWindows && PlatformDetection.IsFullFramework; + protected bool IsUapHandler => !UseSocketsHttpHandler && PlatformDetection.IsWindows && PlatformDetection.IsUap; protected HttpClient CreateHttpClient() => new HttpClient(CreateHttpClientHandler()); - protected HttpClientHandler CreateHttpClientHandler() => CreateHttpClientHandler(UseManagedHandler); - - protected static HttpClient CreateHttpClient(string useManagedHandlerBoolString) => - new HttpClient(CreateHttpClientHandler(useManagedHandlerBoolString)); + protected HttpClientHandler CreateHttpClientHandler() => CreateHttpClientHandler(UseSocketsHttpHandler); - protected static HttpClientHandler CreateHttpClientHandler(string useManagedHandlerBoolString) => - CreateHttpClientHandler(bool.Parse(useManagedHandlerBoolString)); + protected static HttpClient CreateHttpClient(string useSocketsHttpHandlerBoolString) => + new HttpClient(CreateHttpClientHandler(useSocketsHttpHandlerBoolString)); - protected static HttpClientHandler CreateHttpClientHandler(bool useManagedHandler) => - useManagedHandler ? CreateManagedHttpClientHandler() : new HttpClientHandler(); + protected static HttpClientHandler CreateHttpClientHandler(string useSocketsHttpHandlerBoolString) => + CreateHttpClientHandler(bool.Parse(useSocketsHttpHandlerBoolString)); - private static HttpClientHandler CreateManagedHttpClientHandler() + protected static HttpClientHandler CreateHttpClientHandler(bool useSocketsHttpHandler) { - try + if (!PlatformDetection.IsNetCore) // SocketsHttpHandler only exists on .NET Core { - Thread.SetData(s_managedHandlerSlot, true); return new HttpClientHandler(); } - finally - { - Thread.SetData(s_managedHandlerSlot, null); - } + + ConstructorInfo ctor = typeof(HttpClientHandler).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { typeof(bool) }, null); + Debug.Assert(ctor != null, "Couldn't find test constructor on HttpClientHandler"); + + HttpClientHandler handler = (HttpClientHandler)ctor.Invoke(new object[] { useSocketsHttpHandler }); + + FieldInfo field = typeof(HttpClientHandler).GetField("_socketsHttpHandler", BindingFlags.Instance | BindingFlags.NonPublic); + Debug.Assert(field != null, "Couldn't find _socketsHttpHandler field"); + object socketsHttpHandler = field.GetValue(handler); + Debug.Assert((socketsHttpHandler != null) == useSocketsHttpHandler, $"{nameof(useSocketsHttpHandler)} was {useSocketsHttpHandler}, but _socketsHttpHandler field was {socketsHttpHandler}"); + + return handler; } } } diff --git a/src/System.Net.Http/tests/FunctionalTests/HttpProtocolTests.cs b/src/System.Net.Http/tests/FunctionalTests/HttpProtocolTests.cs index 49649e720ab5..c792a81237ee 100644 --- a/src/System.Net.Http/tests/FunctionalTests/HttpProtocolTests.cs +++ b/src/System.Net.Http/tests/FunctionalTests/HttpProtocolTests.cs @@ -72,7 +72,7 @@ await LoopbackServer.CreateServerAsync(async (server, url) => public async Task GetAsync_RequestVersion0X_ThrowsOr11(int minorVersion) { Type exceptionType = null; - if (UseManagedHandler) + if (UseSocketsHttpHandler) { exceptionType = typeof(NotSupportedException); } @@ -186,7 +186,7 @@ await LoopbackServer.CreateServerAsync(async (server, url) => public async Task GetAsync_ResponseUnknownVersion1X_Success(int responseMinorVersion) { bool reportAs11 = PlatformDetection.IsFullFramework; - bool reportAs00 = !UseManagedHandler; + bool reportAs00 = !UseSocketsHttpHandler; await LoopbackServer.CreateServerAsync(async (server, url) => { @@ -279,7 +279,7 @@ public async Task GetAsyncVersion11_BadResponseVersion_ThrowsOr00(int responseMa // CurlHandler reports these as 0.0, except for 2.0 which is reported as 2.0, instead of throwing. bool reportAs00 = false; bool reportAs20 = false; - if (!PlatformDetection.IsWindows && !UseManagedHandler) + if (!PlatformDetection.IsWindows && !UseSocketsHttpHandler) { if (responseMajorVersion == 2 && responseMinorVersion == 0) { @@ -366,9 +366,9 @@ public async Task GetAsync_ExpectedStatusCodeAndReason_Success(string statusLine public async Task GetAsync_ExpectedStatusCodeAndReason_PlatformBehaviorTest(string statusLine, int expectedStatusCode, string reasonWithSpace, string reasonNoSpace) { - if (UseManagedHandler || PlatformDetection.IsFullFramework) + if (UseSocketsHttpHandler || PlatformDetection.IsFullFramework) { - // ManagedHandler and .NET Framework will keep the space characters. + // SocketsHttpHandler and .NET Framework will keep the space characters. await GetAsyncSuccessHelper(statusLine, expectedStatusCode, reasonWithSpace); } else @@ -491,11 +491,11 @@ public async Task GetAsync_ReasonPhraseHasLF_BehaviorDifference() [InlineData("HTTP/1.1\t200 OK")] [InlineData("HTTP/1.1 200\tOK")] [InlineData("HTTP/1.1 200\t")] - public async Task GetAsync_InvalidStatusLine_ThrowsExceptionOnManagedHandler(string responseString) + public async Task GetAsync_InvalidStatusLine_ThrowsExceptionOnSocketsHttpHandler(string responseString) { - if (UseManagedHandler || PlatformDetection.IsFullFramework) + if (UseSocketsHttpHandler || PlatformDetection.IsFullFramework) { - // ManagedHandler and .NET Framework will throw HttpRequestException. + // SocketsHttpHandler and .NET Framework will throw HttpRequestException. await GetAsyncThrowsExceptionHelper(responseString); } // WinRT, WinHttpHandler, and CurlHandler will succeed. diff --git a/src/System.Net.Http/tests/FunctionalTests/HttpRetryProtocolTests.cs b/src/System.Net.Http/tests/FunctionalTests/HttpRetryProtocolTests.cs index c5ae3a286ba5..d887257b402a 100644 --- a/src/System.Net.Http/tests/FunctionalTests/HttpRetryProtocolTests.cs +++ b/src/System.Net.Http/tests/FunctionalTests/HttpRetryProtocolTests.cs @@ -22,9 +22,9 @@ public class HttpRetryProtocolTests : HttpClientTestBase "\r\n" + s_simpleContent; - // Retry logic is supported by managed handler, CurlHandler, uap, and netfx. Only WinHttp does not support. + // Retry logic is supported by SocketsHttpHandler, CurlHandler, uap, and netfx. Only WinHttp does not support. private bool IsRetrySupported => - UseManagedHandler || !PlatformDetection.IsWindows || PlatformDetection.IsUap || PlatformDetection.IsFullFramework; + UseSocketsHttpHandler || !PlatformDetection.IsWindows || PlatformDetection.IsUap || PlatformDetection.IsFullFramework; public static Task CreateServerAndClientAsync(Func clientFunc, Func serverFunc) { diff --git a/src/System.Net.Http/tests/FunctionalTests/ManagedHandlerTest.cs b/src/System.Net.Http/tests/FunctionalTests/ManagedHandlerTest.cs deleted file mode 100644 index 691f2c728020..000000000000 --- a/src/System.Net.Http/tests/FunctionalTests/ManagedHandlerTest.cs +++ /dev/null @@ -1,483 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.IO; -using System.Net.Sockets; -using System.Net.Test.Common; -using System.Reflection; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Xunit; -using Xunit.Abstractions; - -namespace System.Net.Http.Functional.Tests -{ - public sealed class ManagedHandler_HttpProtocolTests : HttpProtocolTests - { - protected override bool UseManagedHandler => true; - } - - public sealed class ManagedHandler_HttpProtocolTests_Dribble : HttpProtocolTests_Dribble - { - protected override bool UseManagedHandler => true; - } - - public sealed class ManagedHandler_HttpClientTest : HttpClientTest - { - protected override bool UseManagedHandler => true; - } - - public sealed class ManagedHandler_DiagnosticsTest : DiagnosticsTest - { - protected override bool UseManagedHandler => true; - } - - public sealed class ManagedHandler_HttpClientEKUTest : HttpClientEKUTest - { - protected override bool UseManagedHandler => true; - } - - public sealed class ManagedHandler_HttpClientHandler_DangerousAcceptAllCertificatesValidator_Test : HttpClientHandler_DangerousAcceptAllCertificatesValidator_Test - { - protected override bool UseManagedHandler => true; - } - - public sealed class ManagedHandler_HttpClientHandler_ClientCertificates_Test : HttpClientHandler_ClientCertificates_Test - { - public ManagedHandler_HttpClientHandler_ClientCertificates_Test(ITestOutputHelper output) : base(output) { } - protected override bool UseManagedHandler => true; - } - - public sealed class ManagedHandler_HttpClientHandler_DefaultProxyCredentials_Test : HttpClientHandler_DefaultProxyCredentials_Test - { - protected override bool UseManagedHandler => true; - } - - public sealed class ManagedHandler_HttpClientHandler_MaxConnectionsPerServer_Test : HttpClientHandler_MaxConnectionsPerServer_Test - { - protected override bool UseManagedHandler => true; - } - - public sealed class ManagedHandler_HttpClientHandler_ServerCertificates_Test : HttpClientHandler_ServerCertificates_Test - { - protected override bool UseManagedHandler => true; - } - - public sealed class ManagedHandler_PostScenarioTest : PostScenarioTest - { - public ManagedHandler_PostScenarioTest(ITestOutputHelper output) : base(output) { } - protected override bool UseManagedHandler => true; - } - - public sealed class ManagedHandler_ResponseStreamTest : ResponseStreamTest - { - public ManagedHandler_ResponseStreamTest(ITestOutputHelper output) : base(output) { } - protected override bool UseManagedHandler => true; - } - - public sealed class ManagedHandler_HttpClientHandler_SslProtocols_Test : HttpClientHandler_SslProtocols_Test - { - protected override bool UseManagedHandler => true; - } - - public sealed class ManagedHandler_SchSendAuxRecordHttpTest : SchSendAuxRecordHttpTest - { - public ManagedHandler_SchSendAuxRecordHttpTest(ITestOutputHelper output) : base(output) { } - protected override bool UseManagedHandler => true; - } - - public sealed class ManagedHandler_HttpClientMiniStress : HttpClientMiniStress - { - protected override bool UseManagedHandler => true; - } - - public sealed class ManagedHandler_HttpClientHandlerTest : HttpClientHandlerTest - { - public ManagedHandler_HttpClientHandlerTest(ITestOutputHelper output) : base(output) { } - protected override bool UseManagedHandler => true; - } - - public sealed class ManagedHandler_DefaultCredentialsTest : DefaultCredentialsTest - { - public ManagedHandler_DefaultCredentialsTest(ITestOutputHelper output) : base(output) { } - protected override bool UseManagedHandler => true; - } - - public sealed class ManagedHandler_IdnaProtocolTests : IdnaProtocolTests - { - protected override bool UseManagedHandler => true; - protected override bool SupportsIdna => true; - } - - public sealed class ManagedHandler_HttpRetryProtocolTests : HttpRetryProtocolTests - { - protected override bool UseManagedHandler => true; - } - - // TODO #23141: Socket's don't support canceling individual operations, so ReadStream on NetworkStream - // isn't cancelable once the operation has started. We either need to wrap the operation with one that's - // "cancelable", meaning that the underlying operation will still be running even though we've returned "canceled", - // or we need to just recognize that cancellation in such situations can be left up to the caller to do the - // same thing if it's really important. - //public sealed class ManagedHandler_CancellationTest : CancellationTest - //{ - // public ManagedHandler_CancellationTest(ITestOutputHelper output) : base(output) { } - // protected override bool UseManagedHandler => true; - //} - - public sealed class ManagedHandler_HttpClientHandler_MaxResponseHeadersLength_Test : HttpClientHandler_MaxResponseHeadersLength_Test - { - protected override bool UseManagedHandler => true; - } - - public sealed class ManagedHandler_HttpClientHandler_DuplexCommunication_Test : HttpClientTestBase - { - protected override bool UseManagedHandler => true; - - [Fact] - public async Task SendBytesBackAndForthBetweenClientAndServer_Success() - { - using (HttpClient client = CreateHttpClient()) - using (var listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) - { - listener.Bind(new IPEndPoint(IPAddress.Loopback, 0)); - listener.Listen(1); - var ep = (IPEndPoint)listener.LocalEndPoint; - - var clientToServerStream = new ProducerConsumerStream(); - clientToServerStream.WriteByte(0); - - var reqMsg = new HttpRequestMessage - { - RequestUri = new Uri($"http://{ep.Address}:{ep.Port}/"), - Content = new StreamContent(clientToServerStream), - }; - Task req = client.SendAsync(reqMsg, HttpCompletionOption.ResponseHeadersRead); - - using (Socket server = await listener.AcceptAsync()) - using (var serverStream = new NetworkStream(server, ownsSocket: false)) - { - // Skip request headers. - while (true) - { - if (serverStream.ReadByte() == '\r') - { - serverStream.ReadByte(); - break; - } - while (serverStream.ReadByte() != '\r') { } - serverStream.ReadByte(); - } - - // Send response headers. - await server.SendAsync( - new ArraySegment(Encoding.ASCII.GetBytes($"HTTP/1.1 200 OK\r\nConnection: close\r\nDate: {DateTimeOffset.UtcNow:R}\r\n\r\n")), - SocketFlags.None); - - HttpResponseMessage resp = await req; - Stream serverToClientStream = await resp.Content.ReadAsStreamAsync(); - - // Communication should now be open between the client and server. - // Ping pong bytes back and forth. - for (byte i = 0; i < 100; i++) - { - // Send a byte from the client to the server. The server will receive - // the byte as a chunk. - if (i > 0) clientToServerStream.WriteByte(i); // 0 was already seeded when the stream was created above - Assert.Equal('1', serverStream.ReadByte()); - Assert.Equal('\r', serverStream.ReadByte()); - Assert.Equal('\n', serverStream.ReadByte()); - Assert.Equal(i, serverStream.ReadByte()); - Assert.Equal('\r', serverStream.ReadByte()); - Assert.Equal('\n', serverStream.ReadByte()); - - // Send a byte from the server to the client. The client will receive - // the byte on its own, with HttpClient stripping away the chunk encoding. - serverStream.WriteByte(i); - Assert.Equal(i, serverToClientStream.ReadByte()); - } - - clientToServerStream.DoneWriting(); - server.Shutdown(SocketShutdown.Send); - Assert.Equal(-1, clientToServerStream.ReadByte()); - } - } - } - - private sealed class ProducerConsumerStream : Stream - { - private readonly BlockingCollection _buffers = new BlockingCollection(); - private ArraySegment _remaining; - - public override void Write(byte[] buffer, int offset, int count) - { - if (count > 0) - { - byte[] tmp = new byte[count]; - Buffer.BlockCopy(buffer, offset, tmp, 0, count); - _buffers.Add(tmp); - } - } - - public override int Read(byte[] buffer, int offset, int count) - { - if (count > 0) - { - if (_remaining.Count == 0) - { - if (!_buffers.TryTake(out byte[] tmp, Timeout.Infinite)) - { - return 0; - } - _remaining = new ArraySegment(tmp, 0, tmp.Length); - } - - if (_remaining.Count <= count) - { - count = _remaining.Count; - Buffer.BlockCopy(_remaining.Array, _remaining.Offset, buffer, offset, count); - _remaining = default(ArraySegment); - } - else - { - Buffer.BlockCopy(_remaining.Array, _remaining.Offset, buffer, offset, count); - _remaining = new ArraySegment(_remaining.Array, _remaining.Offset + count, _remaining.Count - count); - } - } - - return count; - } - - public void DoneWriting() => _buffers.CompleteAdding(); - - public override bool CanRead => true; - public override bool CanSeek => false; - public override bool CanWrite => true; - public override long Length => throw new NotImplementedException(); - public override long Position { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } - public override void Flush() { } - public override long Seek(long offset, SeekOrigin origin) => throw new NotImplementedException(); - public override void SetLength(long value) => throw new NotImplementedException(); - } - } - - public sealed class ManagedHandler_ConnectionUpgrade_Test : HttpClientTestBase - { - protected override bool UseManagedHandler => true; - - [Fact] - public async Task UpgradeConnection_Success() - { - await LoopbackServer.CreateServerAsync(async (server, url) => - { - using (HttpClient client = CreateHttpClient()) - { - // We need to use ResponseHeadersRead here, otherwise we will hang trying to buffer the response body. - Task getResponseTask = client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead); - await LoopbackServer.AcceptSocketAsync(server, async (s, serverStream, serverReader, serverWriter) => - { - Task> serverTask = LoopbackServer.ReadWriteAcceptedAsync(s, serverReader, serverWriter, - $"HTTP/1.1 101 Switching Protocols\r\nDate: {DateTimeOffset.UtcNow:R}\r\n\r\n"); - - await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask); - - using (Stream clientStream = await (await getResponseTask).Content.ReadAsStreamAsync()) - { - Assert.True(clientStream.CanWrite); - Assert.True(clientStream.CanRead); - Assert.False(clientStream.CanSeek); - - TextReader clientReader = new StreamReader(clientStream); - TextWriter clientWriter = new StreamWriter(clientStream) { AutoFlush = true }; - - const string helloServer = "hello server"; - const string helloClient = "hello client"; - const string goodbyeServer = "goodbye server"; - const string goodbyeClient = "goodbye client"; - - clientWriter.WriteLine(helloServer); - Assert.Equal(helloServer, serverReader.ReadLine()); - serverWriter.WriteLine(helloClient); - Assert.Equal(helloClient, clientReader.ReadLine()); - clientWriter.WriteLine(goodbyeServer); - Assert.Equal(goodbyeServer, serverReader.ReadLine()); - serverWriter.WriteLine(goodbyeClient); - Assert.Equal(goodbyeClient, clientReader.ReadLine()); - } - - return null; - }); - } - }); - } - } - - public sealed class ManagedHandler_HttpClientHandler_ConnectionPooling_Test : HttpClientTestBase - { - protected override bool UseManagedHandler => true; - - // TODO: Currently the subsequent tests sometimes fail/hang with WinHttpHandler / CurlHandler. - // In theory they should pass with any handler that does appropriate connection pooling. - // We should understand why they sometimes fail there and ideally move them to be - // used by all handlers this test project tests. - - [Fact] - public async Task MultipleIterativeRequests_SameConnectionReused() - { - using (HttpClient client = CreateHttpClient()) - using (var listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) - { - listener.Bind(new IPEndPoint(IPAddress.Loopback, 0)); - listener.Listen(1); - var ep = (IPEndPoint)listener.LocalEndPoint; - var uri = new Uri($"http://{ep.Address}:{ep.Port}/"); - - string responseBody = - "HTTP/1.1 200 OK\r\n" + - $"Date: {DateTimeOffset.UtcNow:R}\r\n" + - "Content-Length: 0\r\n" + - "\r\n"; - - Task firstRequest = client.GetStringAsync(uri); - using (Socket server = await listener.AcceptAsync()) - using (var serverStream = new NetworkStream(server, ownsSocket: false)) - using (var serverReader = new StreamReader(serverStream)) - { - while (!string.IsNullOrWhiteSpace(await serverReader.ReadLineAsync())); - await server.SendAsync(new ArraySegment(Encoding.ASCII.GetBytes(responseBody)), SocketFlags.None); - await firstRequest; - - Task secondAccept = listener.AcceptAsync(); // shouldn't complete - - Task additionalRequest = client.GetStringAsync(uri); - while (!string.IsNullOrWhiteSpace(await serverReader.ReadLineAsync())); - await server.SendAsync(new ArraySegment(Encoding.ASCII.GetBytes(responseBody)), SocketFlags.None); - await additionalRequest; - - Assert.False(secondAccept.IsCompleted, $"Second accept should never complete"); - } - } - } - - [OuterLoop("Incurs a delay")] - [Fact] - public async Task ServerDisconnectsAfterInitialRequest_SubsequentRequestUsesDifferentConnection() - { - using (HttpClient client = CreateHttpClient()) - { - await LoopbackServer.CreateServerAsync(async (listener, uri) => - { - // Make multiple requests iteratively. - for (int i = 0; i < 2; i++) - { - Task request = client.GetStringAsync(uri); - await LoopbackServer.AcceptSocketAsync(listener, async (server, serverStream, serverReader, serverWriter) => - { - while (!string.IsNullOrWhiteSpace(await serverReader.ReadLineAsync())); - await serverWriter.WriteAsync(LoopbackServer.DefaultHttpResponse); - await request; - - server.Shutdown(SocketShutdown.Both); - if (i == 0) - { - await Task.Delay(2000); // give client time to see the closing before next connect - } - - return null; - }); - } - }); - } - } - - [Fact] - public async Task ServerSendsConnectionClose_SubsequentRequestUsesDifferentConnection() - { - using (HttpClient client = CreateHttpClient()) - { - await LoopbackServer.CreateServerAsync(async (listener, uri) => - { - string responseBody = - "HTTP/1.1 200 OK\r\n" + - $"Date: {DateTimeOffset.UtcNow:R}\r\n" + - "Content-Length: 0\r\n" + - "Connection: close\r\n" + - "\r\n"; - - // Make first request. - Task request1 = client.GetStringAsync(uri); - await LoopbackServer.AcceptSocketAsync(listener, async (server1, serverStream1, serverReader1, serverWriter1) => - { - while (!string.IsNullOrWhiteSpace(await serverReader1.ReadLineAsync())); - await serverWriter1.WriteAsync(responseBody); - await request1; - - // Make second request and expect it to be served from a different connection. - Task request2 = client.GetStringAsync(uri); - await LoopbackServer.AcceptSocketAsync(listener, async (server2, serverStream2, serverReader2, serverWriter2) => - { - while (!string.IsNullOrWhiteSpace(await serverReader2.ReadLineAsync())); - await serverWriter2.WriteAsync(responseBody); - await request2; - return null; - }); - - return null; - }); - }); - } - } - - [Theory] - [InlineData("PooledConnectionLifetime")] - [InlineData("PooledConnectionIdleTimeout")] - public async Task SmallConnectionTimeout_SubsequentRequestUsesDifferentConnection(string timeoutPropertyName) - { - using (HttpClientHandler handler = CreateHttpClientHandler()) - { - SetManagedHandlerProperty(handler, timeoutPropertyName, TimeSpan.FromMilliseconds(1)); - - using (HttpClient client = new HttpClient(handler)) - { - await LoopbackServer.CreateServerAsync(async (listener, uri) => - { - // Make first request. - Task request1 = client.GetStringAsync(uri); - await LoopbackServer.AcceptSocketAsync(listener, async (server1, serverStream1, serverReader1, serverWriter1) => - { - while (!string.IsNullOrWhiteSpace(await serverReader1.ReadLineAsync())); - await serverWriter1.WriteAsync(LoopbackServer.DefaultHttpResponse); - await request1; - - // Wait a small amount of time before making the second request, to give the first request time to timeout. - await Task.Delay(100); - - // Make second request and expect it to be served from a different connection. - Task request2 = client.GetStringAsync(uri); - await LoopbackServer.AcceptSocketAsync(listener, async (server2, serverStream2, serverReader2, serverWriter2) => - { - while (!string.IsNullOrWhiteSpace(await serverReader2.ReadLineAsync())); - await serverWriter2.WriteAsync(LoopbackServer.DefaultHttpResponse); - await request2; - return null; - }); - - return null; - }); - }); - } - } - } - - private static void SetManagedHandlerProperty(HttpClientHandler handler, string name, object value) - { - // TODO #23166: Use the managed API for ConnectionIdleTimeout when it's exposed - object managedHandler = handler.GetType().GetField("_managedHandler", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(handler); - managedHandler.GetType().GetProperty(name).SetValue(managedHandler, value); - } - } -} diff --git a/src/System.Net.Http/tests/FunctionalTests/PostScenarioUWPTest.cs b/src/System.Net.Http/tests/FunctionalTests/PostScenarioUWPTest.cs index 1c58ff83ebe0..57843f97e620 100644 --- a/src/System.Net.Http/tests/FunctionalTests/PostScenarioUWPTest.cs +++ b/src/System.Net.Http/tests/FunctionalTests/PostScenarioUWPTest.cs @@ -27,7 +27,7 @@ public PostScenarioUWPTest(ITestOutputHelper output) [Fact] public void Authentication_UseStreamContent_Throws() { - RemoteInvoke(async useManagedHandlerString => + RemoteInvoke(async useSocketsHttpHandlerString => { // This test validates the current limitation of CoreFx's NetFxToWinRtStreamAdapter // which throws exceptions when trying to rewind a .NET Stream when it needs to be @@ -35,7 +35,7 @@ public void Authentication_UseStreamContent_Throws() string username = "testuser"; string password = "password"; Uri uri = Configuration.Http.BasicAuthUriForCreds(secure: false, userName: username, password: password); - HttpClientHandler handler = CreateHttpClientHandler(useManagedHandlerString); + HttpClientHandler handler = CreateHttpClientHandler(useSocketsHttpHandlerString); handler.Credentials = new NetworkCredential(username, password); using (var client = new HttpClient(handler)) @@ -48,18 +48,18 @@ public void Authentication_UseStreamContent_Throws() } return SuccessExitCode; - }, UseManagedHandler.ToString()).Dispose(); + }, UseSocketsHttpHandler.ToString()).Dispose(); } [Fact] public void Authentication_UseMultiInterfaceNonRewindableStreamContent_Throws() { - RemoteInvoke(async useManagedHandlerString => + RemoteInvoke(async useSocketsHttpHandlerString => { string username = "testuser"; string password = "password"; Uri uri = Configuration.Http.BasicAuthUriForCreds(secure: false, userName: username, password: password); - HttpClientHandler handler = CreateHttpClientHandler(useManagedHandlerString); + HttpClientHandler handler = CreateHttpClientHandler(useSocketsHttpHandlerString); handler.Credentials = new NetworkCredential(username, password); using (var client = new HttpClient(handler)) @@ -72,18 +72,18 @@ public void Authentication_UseMultiInterfaceNonRewindableStreamContent_Throws() } return SuccessExitCode; - }, UseManagedHandler.ToString()).Dispose(); + }, UseSocketsHttpHandler.ToString()).Dispose(); } [Fact] public void Authentication_UseMultiInterfaceStreamContent_Success() { - RemoteInvoke(async useManagedHandlerString => + RemoteInvoke(async useSocketsHttpHandlerString => { string username = "testuser"; string password = "password"; Uri uri = Configuration.Http.BasicAuthUriForCreds(secure: false, userName: username, password: password); - HttpClientHandler handler = CreateHttpClientHandler(useManagedHandlerString); + HttpClientHandler handler = CreateHttpClientHandler(useSocketsHttpHandlerString); handler.Credentials = new NetworkCredential(username, password); using (var client = new HttpClient(handler)) @@ -100,18 +100,18 @@ public void Authentication_UseMultiInterfaceStreamContent_Success() } return SuccessExitCode; - }, UseManagedHandler.ToString()).Dispose(); + }, UseSocketsHttpHandler.ToString()).Dispose(); } [Fact] public void Authentication_UseMemoryStreamVisibleBufferContent_Success() { - RemoteInvoke(async useManagedHandlerString => + RemoteInvoke(async useSocketsHttpHandlerString => { string username = "testuser"; string password = "password"; Uri uri = Configuration.Http.BasicAuthUriForCreds(secure: false, userName: username, password: password); - HttpClientHandler handler = CreateHttpClientHandler(useManagedHandlerString); + HttpClientHandler handler = CreateHttpClientHandler(useSocketsHttpHandlerString); handler.Credentials = new NetworkCredential(username, password); using (var client = new HttpClient(handler)) @@ -128,18 +128,18 @@ public void Authentication_UseMemoryStreamVisibleBufferContent_Success() } return SuccessExitCode; - }, UseManagedHandler.ToString()).Dispose(); + }, UseSocketsHttpHandler.ToString()).Dispose(); } [Fact] public void Authentication_UseMemoryStreamNotVisibleBufferContent_Success() { - RemoteInvoke(async useManagedHandlerString => + RemoteInvoke(async useSocketsHttpHandlerString => { string username = "testuser"; string password = "password"; Uri uri = Configuration.Http.BasicAuthUriForCreds(secure: false, userName: username, password: password); - HttpClientHandler handler = CreateHttpClientHandler(useManagedHandlerString); + HttpClientHandler handler = CreateHttpClientHandler(useSocketsHttpHandlerString); handler.Credentials = new NetworkCredential(username, password); using (var client = new HttpClient(handler)) @@ -156,7 +156,7 @@ public void Authentication_UseMemoryStreamNotVisibleBufferContent_Success() } return SuccessExitCode; - }, UseManagedHandler.ToString()).Dispose(); + }, UseSocketsHttpHandler.ToString()).Dispose(); } } } diff --git a/src/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs b/src/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs new file mode 100644 index 000000000000..912f10f3c73f --- /dev/null +++ b/src/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs @@ -0,0 +1,813 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Net.Security; +using System.Net.Sockets; +using System.Net.Test.Common; +using System.Security.Authentication; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +namespace System.Net.Http.Functional.Tests +{ + public sealed class SocketsHttpHandler_HttpProtocolTests : HttpProtocolTests + { + protected override bool UseSocketsHttpHandler => true; + } + + public sealed class SocketsHttpHandler_HttpProtocolTests_Dribble : HttpProtocolTests_Dribble + { + protected override bool UseSocketsHttpHandler => true; + } + + public sealed class SocketsHttpHandler_HttpClientTest : HttpClientTest + { + protected override bool UseSocketsHttpHandler => true; + } + + public sealed class SocketsHttpHandler_DiagnosticsTest : DiagnosticsTest + { + protected override bool UseSocketsHttpHandler => true; + } + + public sealed class SocketsHttpHandler_HttpClientEKUTest : HttpClientEKUTest + { + protected override bool UseSocketsHttpHandler => true; + } + + public sealed class SocketsHttpHandler_HttpClientHandler_DangerousAcceptAllCertificatesValidator_Test : HttpClientHandler_DangerousAcceptAllCertificatesValidator_Test + { + protected override bool UseSocketsHttpHandler => true; + } + + public sealed class SocketsHttpHandler_HttpClientHandler_ClientCertificates_Test : HttpClientHandler_ClientCertificates_Test + { + public SocketsHttpHandler_HttpClientHandler_ClientCertificates_Test(ITestOutputHelper output) : base(output) { } + protected override bool UseSocketsHttpHandler => true; + } + + public sealed class SocketsHttpHandler_HttpClientHandler_DefaultProxyCredentials_Test : HttpClientHandler_DefaultProxyCredentials_Test + { + protected override bool UseSocketsHttpHandler => true; + } + + public sealed class SocketsHttpHandler_HttpClientHandler_MaxConnectionsPerServer_Test : HttpClientHandler_MaxConnectionsPerServer_Test + { + protected override bool UseSocketsHttpHandler => true; + } + + public sealed class SocketsHttpHandler_HttpClientHandler_ServerCertificates_Test : HttpClientHandler_ServerCertificates_Test + { + protected override bool UseSocketsHttpHandler => true; + } + + public sealed class SocketsHttpHandler_PostScenarioTest : PostScenarioTest + { + public SocketsHttpHandler_PostScenarioTest(ITestOutputHelper output) : base(output) { } + protected override bool UseSocketsHttpHandler => true; + } + + public sealed class SocketsHttpHandler_ResponseStreamTest : ResponseStreamTest + { + public SocketsHttpHandler_ResponseStreamTest(ITestOutputHelper output) : base(output) { } + protected override bool UseSocketsHttpHandler => true; + } + + public sealed class SocketsHttpHandler_HttpClientHandler_SslProtocols_Test : HttpClientHandler_SslProtocols_Test + { + protected override bool UseSocketsHttpHandler => true; + } + + public sealed class SocketsHttpHandler_SchSendAuxRecordHttpTest : SchSendAuxRecordHttpTest + { + public SocketsHttpHandler_SchSendAuxRecordHttpTest(ITestOutputHelper output) : base(output) { } + protected override bool UseSocketsHttpHandler => true; + } + + public sealed class SocketsHttpHandler_HttpClientMiniStress : HttpClientMiniStress + { + protected override bool UseSocketsHttpHandler => true; + } + + public sealed class SocketsHttpHandler_HttpClientHandlerTest : HttpClientHandlerTest + { + public SocketsHttpHandler_HttpClientHandlerTest(ITestOutputHelper output) : base(output) { } + protected override bool UseSocketsHttpHandler => true; + } + + public sealed class SocketsHttpHandler_DefaultCredentialsTest : DefaultCredentialsTest + { + public SocketsHttpHandler_DefaultCredentialsTest(ITestOutputHelper output) : base(output) { } + protected override bool UseSocketsHttpHandler => true; + } + + public sealed class SocketsHttpHandler_IdnaProtocolTests : IdnaProtocolTests + { + protected override bool UseSocketsHttpHandler => true; + protected override bool SupportsIdna => true; + } + + public sealed class SocketsHttpHandler_HttpRetryProtocolTests : HttpRetryProtocolTests + { + protected override bool UseSocketsHttpHandler => true; + } + + // TODO #23141: Socket's don't support canceling individual operations, so ReadStream on NetworkStream + // isn't cancelable once the operation has started. We either need to wrap the operation with one that's + // "cancelable", meaning that the underlying operation will still be running even though we've returned "canceled", + // or we need to just recognize that cancellation in such situations can be left up to the caller to do the + // same thing if it's really important. + //public sealed class SocketsHttpHandler_CancellationTest : CancellationTest + //{ + // public SocketsHttpHandler_CancellationTest(ITestOutputHelper output) : base(output) { } + // protected override bool UseSocketsHttpHandler => true; + //} + + public sealed class SocketsHttpHandler_HttpClientHandler_MaxResponseHeadersLength_Test : HttpClientHandler_MaxResponseHeadersLength_Test + { + protected override bool UseSocketsHttpHandler => true; + } + + public sealed class SocketsHttpHandler_HttpClientHandler_DuplexCommunication_Test : HttpClientTestBase + { + protected override bool UseSocketsHttpHandler => true; + + [Fact] + public async Task SendBytesBackAndForthBetweenClientAndServer_Success() + { + using (HttpClient client = CreateHttpClient()) + using (var listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + { + listener.Bind(new IPEndPoint(IPAddress.Loopback, 0)); + listener.Listen(1); + var ep = (IPEndPoint)listener.LocalEndPoint; + + var clientToServerStream = new ProducerConsumerStream(); + clientToServerStream.WriteByte(0); + + var reqMsg = new HttpRequestMessage + { + RequestUri = new Uri($"http://{ep.Address}:{ep.Port}/"), + Content = new StreamContent(clientToServerStream), + }; + Task req = client.SendAsync(reqMsg, HttpCompletionOption.ResponseHeadersRead); + + using (Socket server = await listener.AcceptAsync()) + using (var serverStream = new NetworkStream(server, ownsSocket: false)) + { + // Skip request headers. + while (true) + { + if (serverStream.ReadByte() == '\r') + { + serverStream.ReadByte(); + break; + } + while (serverStream.ReadByte() != '\r') { } + serverStream.ReadByte(); + } + + // Send response headers. + await server.SendAsync( + new ArraySegment(Encoding.ASCII.GetBytes($"HTTP/1.1 200 OK\r\nConnection: close\r\nDate: {DateTimeOffset.UtcNow:R}\r\n\r\n")), + SocketFlags.None); + + HttpResponseMessage resp = await req; + Stream serverToClientStream = await resp.Content.ReadAsStreamAsync(); + + // Communication should now be open between the client and server. + // Ping pong bytes back and forth. + for (byte i = 0; i < 100; i++) + { + // Send a byte from the client to the server. The server will receive + // the byte as a chunk. + if (i > 0) clientToServerStream.WriteByte(i); // 0 was already seeded when the stream was created above + Assert.Equal('1', serverStream.ReadByte()); + Assert.Equal('\r', serverStream.ReadByte()); + Assert.Equal('\n', serverStream.ReadByte()); + Assert.Equal(i, serverStream.ReadByte()); + Assert.Equal('\r', serverStream.ReadByte()); + Assert.Equal('\n', serverStream.ReadByte()); + + // Send a byte from the server to the client. The client will receive + // the byte on its own, with HttpClient stripping away the chunk encoding. + serverStream.WriteByte(i); + Assert.Equal(i, serverToClientStream.ReadByte()); + } + + clientToServerStream.DoneWriting(); + server.Shutdown(SocketShutdown.Send); + Assert.Equal(-1, clientToServerStream.ReadByte()); + } + } + } + + private sealed class ProducerConsumerStream : Stream + { + private readonly BlockingCollection _buffers = new BlockingCollection(); + private ArraySegment _remaining; + + public override void Write(byte[] buffer, int offset, int count) + { + if (count > 0) + { + byte[] tmp = new byte[count]; + Buffer.BlockCopy(buffer, offset, tmp, 0, count); + _buffers.Add(tmp); + } + } + + public override int Read(byte[] buffer, int offset, int count) + { + if (count > 0) + { + if (_remaining.Count == 0) + { + if (!_buffers.TryTake(out byte[] tmp, Timeout.Infinite)) + { + return 0; + } + _remaining = new ArraySegment(tmp, 0, tmp.Length); + } + + if (_remaining.Count <= count) + { + count = _remaining.Count; + Buffer.BlockCopy(_remaining.Array, _remaining.Offset, buffer, offset, count); + _remaining = default(ArraySegment); + } + else + { + Buffer.BlockCopy(_remaining.Array, _remaining.Offset, buffer, offset, count); + _remaining = new ArraySegment(_remaining.Array, _remaining.Offset + count, _remaining.Count - count); + } + } + + return count; + } + + public void DoneWriting() => _buffers.CompleteAdding(); + + public override bool CanRead => true; + public override bool CanSeek => false; + public override bool CanWrite => true; + public override long Length => throw new NotImplementedException(); + public override long Position { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + public override void Flush() { } + public override long Seek(long offset, SeekOrigin origin) => throw new NotImplementedException(); + public override void SetLength(long value) => throw new NotImplementedException(); + } + } + + public sealed class SocketsHttpHandler_ConnectionUpgrade_Test : HttpClientTestBase + { + protected override bool UseSocketsHttpHandler => true; + + [Fact] + public async Task UpgradeConnection_Success() + { + await LoopbackServer.CreateServerAsync(async (server, url) => + { + using (HttpClient client = CreateHttpClient()) + { + // We need to use ResponseHeadersRead here, otherwise we will hang trying to buffer the response body. + Task getResponseTask = client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead); + await LoopbackServer.AcceptSocketAsync(server, async (s, serverStream, serverReader, serverWriter) => + { + Task> serverTask = LoopbackServer.ReadWriteAcceptedAsync(s, serverReader, serverWriter, + $"HTTP/1.1 101 Switching Protocols\r\nDate: {DateTimeOffset.UtcNow:R}\r\n\r\n"); + + await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask); + + using (Stream clientStream = await (await getResponseTask).Content.ReadAsStreamAsync()) + { + Assert.True(clientStream.CanWrite); + Assert.True(clientStream.CanRead); + Assert.False(clientStream.CanSeek); + + TextReader clientReader = new StreamReader(clientStream); + TextWriter clientWriter = new StreamWriter(clientStream) { AutoFlush = true }; + + const string helloServer = "hello server"; + const string helloClient = "hello client"; + const string goodbyeServer = "goodbye server"; + const string goodbyeClient = "goodbye client"; + + clientWriter.WriteLine(helloServer); + Assert.Equal(helloServer, serverReader.ReadLine()); + serverWriter.WriteLine(helloClient); + Assert.Equal(helloClient, clientReader.ReadLine()); + clientWriter.WriteLine(goodbyeServer); + Assert.Equal(goodbyeServer, serverReader.ReadLine()); + serverWriter.WriteLine(goodbyeClient); + Assert.Equal(goodbyeClient, clientReader.ReadLine()); + } + + return null; + }); + } + }); + } + } + + public sealed class SocketsHttpHandler_HttpClientHandler_ConnectionPooling_Test : HttpClientTestBase + { + protected override bool UseSocketsHttpHandler => true; + + // TODO: Currently the subsequent tests sometimes fail/hang with WinHttpHandler / CurlHandler. + // In theory they should pass with any handler that does appropriate connection pooling. + // We should understand why they sometimes fail there and ideally move them to be + // used by all handlers this test project tests. + + [Fact] + public async Task MultipleIterativeRequests_SameConnectionReused() + { + using (HttpClient client = CreateHttpClient()) + using (var listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + { + listener.Bind(new IPEndPoint(IPAddress.Loopback, 0)); + listener.Listen(1); + var ep = (IPEndPoint)listener.LocalEndPoint; + var uri = new Uri($"http://{ep.Address}:{ep.Port}/"); + + string responseBody = + "HTTP/1.1 200 OK\r\n" + + $"Date: {DateTimeOffset.UtcNow:R}\r\n" + + "Content-Length: 0\r\n" + + "\r\n"; + + Task firstRequest = client.GetStringAsync(uri); + using (Socket server = await listener.AcceptAsync()) + using (var serverStream = new NetworkStream(server, ownsSocket: false)) + using (var serverReader = new StreamReader(serverStream)) + { + while (!string.IsNullOrWhiteSpace(await serverReader.ReadLineAsync())); + await server.SendAsync(new ArraySegment(Encoding.ASCII.GetBytes(responseBody)), SocketFlags.None); + await firstRequest; + + Task secondAccept = listener.AcceptAsync(); // shouldn't complete + + Task additionalRequest = client.GetStringAsync(uri); + while (!string.IsNullOrWhiteSpace(await serverReader.ReadLineAsync())); + await server.SendAsync(new ArraySegment(Encoding.ASCII.GetBytes(responseBody)), SocketFlags.None); + await additionalRequest; + + Assert.False(secondAccept.IsCompleted, $"Second accept should never complete"); + } + } + } + + [OuterLoop("Incurs a delay")] + [Fact] + public async Task ServerDisconnectsAfterInitialRequest_SubsequentRequestUsesDifferentConnection() + { + using (HttpClient client = CreateHttpClient()) + { + await LoopbackServer.CreateServerAsync(async (listener, uri) => + { + // Make multiple requests iteratively. + for (int i = 0; i < 2; i++) + { + Task request = client.GetStringAsync(uri); + await LoopbackServer.AcceptSocketAsync(listener, async (server, serverStream, serverReader, serverWriter) => + { + while (!string.IsNullOrWhiteSpace(await serverReader.ReadLineAsync())); + await serverWriter.WriteAsync(LoopbackServer.DefaultHttpResponse); + await request; + + server.Shutdown(SocketShutdown.Both); + if (i == 0) + { + await Task.Delay(2000); // give client time to see the closing before next connect + } + + return null; + }); + } + }); + } + } + + [Fact] + public async Task ServerSendsConnectionClose_SubsequentRequestUsesDifferentConnection() + { + using (HttpClient client = CreateHttpClient()) + { + await LoopbackServer.CreateServerAsync(async (listener, uri) => + { + string responseBody = + "HTTP/1.1 200 OK\r\n" + + $"Date: {DateTimeOffset.UtcNow:R}\r\n" + + "Content-Length: 0\r\n" + + "Connection: close\r\n" + + "\r\n"; + + // Make first request. + Task request1 = client.GetStringAsync(uri); + await LoopbackServer.AcceptSocketAsync(listener, async (server1, serverStream1, serverReader1, serverWriter1) => + { + while (!string.IsNullOrWhiteSpace(await serverReader1.ReadLineAsync())); + await serverWriter1.WriteAsync(responseBody); + await request1; + + // Make second request and expect it to be served from a different connection. + Task request2 = client.GetStringAsync(uri); + await LoopbackServer.AcceptSocketAsync(listener, async (server2, serverStream2, serverReader2, serverWriter2) => + { + while (!string.IsNullOrWhiteSpace(await serverReader2.ReadLineAsync())); + await serverWriter2.WriteAsync(responseBody); + await request2; + return null; + }); + + return null; + }); + }); + } + } + + [Theory] + [InlineData("PooledConnectionLifetime")] + [InlineData("PooledConnectionIdleTimeout")] + public async Task SmallConnectionTimeout_SubsequentRequestUsesDifferentConnection(string timeoutPropertyName) + { + using (var handler = new SocketsHttpHandler()) + { + switch (timeoutPropertyName) + { + case "PooledConnectionLifetime": handler.PooledConnectionLifetime = TimeSpan.FromMilliseconds(1); break; + case "PooledConnectionIdleTimeout": handler.PooledConnectionLifetime = TimeSpan.FromMilliseconds(1); break; + default: throw new ArgumentOutOfRangeException(nameof(timeoutPropertyName)); + } + + using (HttpClient client = new HttpClient(handler)) + { + await LoopbackServer.CreateServerAsync(async (listener, uri) => + { + // Make first request. + Task request1 = client.GetStringAsync(uri); + await LoopbackServer.AcceptSocketAsync(listener, async (server1, serverStream1, serverReader1, serverWriter1) => + { + while (!string.IsNullOrWhiteSpace(await serverReader1.ReadLineAsync())); + await serverWriter1.WriteAsync(LoopbackServer.DefaultHttpResponse); + await request1; + + // Wait a small amount of time before making the second request, to give the first request time to timeout. + await Task.Delay(100); + + // Make second request and expect it to be served from a different connection. + Task request2 = client.GetStringAsync(uri); + await LoopbackServer.AcceptSocketAsync(listener, async (server2, serverStream2, serverReader2, serverWriter2) => + { + while (!string.IsNullOrWhiteSpace(await serverReader2.ReadLineAsync())); + await serverWriter2.WriteAsync(LoopbackServer.DefaultHttpResponse); + await request2; + return null; + }); + + return null; + }); + }); + } + } + } + } + + public sealed class SocketsHttpHandler_PublicAPIBehavior_Test + { + private static async Task IssueRequestAsync(HttpMessageHandler handler) + { + using (var c = new HttpMessageInvoker(handler, disposeHandler: false)) + await Assert.ThrowsAnyAsync(() => + c.SendAsync(new HttpRequestMessage(HttpMethod.Get, new Uri("/shouldquicklyfail", UriKind.Relative)), default)); + } + + [Fact] + public void AllowAutoRedirect_GetSet_Roundtrips() + { + using (var handler = new SocketsHttpHandler()) + { + Assert.True(handler.AllowAutoRedirect); + + handler.AllowAutoRedirect = true; + Assert.True(handler.AllowAutoRedirect); + + handler.AllowAutoRedirect = false; + Assert.False(handler.AllowAutoRedirect); + } + } + + [Fact] + public void AutomaticDecompression_GetSet_Roundtrips() + { + using (var handler = new SocketsHttpHandler()) + { + Assert.Equal(DecompressionMethods.None, handler.AutomaticDecompression); + + handler.AutomaticDecompression = DecompressionMethods.GZip; + Assert.Equal(DecompressionMethods.GZip, handler.AutomaticDecompression); + + handler.AutomaticDecompression = DecompressionMethods.Deflate; + Assert.Equal(DecompressionMethods.Deflate, handler.AutomaticDecompression); + + handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; + Assert.Equal(DecompressionMethods.GZip | DecompressionMethods.Deflate, handler.AutomaticDecompression); + } + } + + [Fact] + public void CookieContainer_GetSet_Roundtrips() + { + using (var handler = new SocketsHttpHandler()) + { + CookieContainer container = handler.CookieContainer; + Assert.Same(container, handler.CookieContainer); + + var newContainer = new CookieContainer(); + handler.CookieContainer = newContainer; + Assert.Same(newContainer, handler.CookieContainer); + } + } + + [Fact] + public void Credentials_GetSet_Roundtrips() + { + using (var handler = new SocketsHttpHandler()) + { + Assert.Null(handler.Credentials); + + var newCredentials = new NetworkCredential("username", "password"); + handler.Credentials = newCredentials; + Assert.Same(newCredentials, handler.Credentials); + } + } + + [Fact] + public void DefaultProxyCredentials_GetSet_Roundtrips() + { + using (var handler = new SocketsHttpHandler()) + { + Assert.Null(handler.DefaultProxyCredentials); + + var newCredentials = new NetworkCredential("username", "password"); + handler.DefaultProxyCredentials = newCredentials; + Assert.Same(newCredentials, handler.DefaultProxyCredentials); + } + } + + [Fact] + public void MaxAutomaticRedirections_GetSet_Roundtrips() + { + using (var handler = new SocketsHttpHandler()) + { + Assert.Equal(50, handler.MaxAutomaticRedirections); + + handler.MaxAutomaticRedirections = int.MaxValue; + Assert.Equal(int.MaxValue, handler.MaxAutomaticRedirections); + + handler.MaxAutomaticRedirections = 1; + Assert.Equal(1, handler.MaxAutomaticRedirections); + + AssertExtensions.Throws("value", () => handler.MaxAutomaticRedirections = 0); + AssertExtensions.Throws("value", () => handler.MaxAutomaticRedirections = -1); + } + } + + [Fact] + public void MaxConnectionsPerServer_GetSet_Roundtrips() + { + using (var handler = new SocketsHttpHandler()) + { + Assert.Equal(int.MaxValue, handler.MaxConnectionsPerServer); + + handler.MaxConnectionsPerServer = int.MaxValue; + Assert.Equal(int.MaxValue, handler.MaxConnectionsPerServer); + + handler.MaxConnectionsPerServer = 1; + Assert.Equal(1, handler.MaxConnectionsPerServer); + + AssertExtensions.Throws("value", () => handler.MaxConnectionsPerServer = 0); + AssertExtensions.Throws("value", () => handler.MaxConnectionsPerServer = -1); + } + } + + [Fact] + public void MaxResponseHeadersLength_GetSet_Roundtrips() + { + using (var handler = new SocketsHttpHandler()) + { + Assert.Equal(64, handler.MaxResponseHeadersLength); + + handler.MaxResponseHeadersLength = int.MaxValue; + Assert.Equal(int.MaxValue, handler.MaxResponseHeadersLength); + + handler.MaxResponseHeadersLength = 1; + Assert.Equal(1, handler.MaxResponseHeadersLength); + + AssertExtensions.Throws("value", () => handler.MaxResponseHeadersLength = 0); + AssertExtensions.Throws("value", () => handler.MaxResponseHeadersLength = -1); + } + } + + [Fact] + public void PreAuthenticate_GetSet_Roundtrips() + { + using (var handler = new SocketsHttpHandler()) + { + Assert.False(handler.PreAuthenticate); + + handler.PreAuthenticate = false; + Assert.False(handler.PreAuthenticate); + + handler.PreAuthenticate = true; + Assert.True(handler.PreAuthenticate); + } + } + + [Fact] + public void PooledConnectionIdleTimeout_GetSet_Roundtrips() + { + using (var handler = new SocketsHttpHandler()) + { + Assert.Equal(TimeSpan.FromMinutes(2), handler.PooledConnectionIdleTimeout); + + handler.PooledConnectionIdleTimeout = Timeout.InfiniteTimeSpan; + Assert.Equal(Timeout.InfiniteTimeSpan, handler.PooledConnectionIdleTimeout); + + handler.PooledConnectionIdleTimeout = TimeSpan.FromSeconds(0); + Assert.Equal(TimeSpan.FromSeconds(0), handler.PooledConnectionIdleTimeout); + + handler.PooledConnectionIdleTimeout = TimeSpan.FromSeconds(1); + Assert.Equal(TimeSpan.FromSeconds(1), handler.PooledConnectionIdleTimeout); + + AssertExtensions.Throws("value", () => handler.PooledConnectionIdleTimeout = TimeSpan.FromSeconds(-2)); + } + } + + [Fact] + public void PooledConnectionLifetime_GetSet_Roundtrips() + { + using (var handler = new SocketsHttpHandler()) + { + Assert.Equal(Timeout.InfiniteTimeSpan, handler.PooledConnectionLifetime); + + handler.PooledConnectionLifetime = Timeout.InfiniteTimeSpan; + Assert.Equal(Timeout.InfiniteTimeSpan, handler.PooledConnectionLifetime); + + handler.PooledConnectionLifetime = TimeSpan.FromSeconds(0); + Assert.Equal(TimeSpan.FromSeconds(0), handler.PooledConnectionLifetime); + + handler.PooledConnectionLifetime = TimeSpan.FromSeconds(1); + Assert.Equal(TimeSpan.FromSeconds(1), handler.PooledConnectionLifetime); + + AssertExtensions.Throws("value", () => handler.PooledConnectionLifetime = TimeSpan.FromSeconds(-2)); + } + } + + [Fact] + public void Properties_Roundtrips() + { + using (var handler = new SocketsHttpHandler()) + { + IDictionary props = handler.Properties; + Assert.NotNull(props); + Assert.Empty(props); + + props.Add("hello", "world"); + Assert.Equal(1, props.Count); + Assert.Equal("world", props["hello"]); + } + } + + [Fact] + public void Proxy_GetSet_Roundtrips() + { + using (var handler = new SocketsHttpHandler()) + { + Assert.Null(handler.Proxy); + + var proxy = new WebProxy(); + handler.Proxy = proxy; + Assert.Same(proxy, handler.Proxy); + } + } + + [Fact] + public void SslOptions_GetSet_Roundtrips() + { + using (var handler = new SocketsHttpHandler()) + { + SslClientAuthenticationOptions options = handler.SslOptions; + Assert.NotNull(options); + + Assert.True(options.AllowRenegotiation); + Assert.Null(options.ApplicationProtocols); + Assert.Equal(X509RevocationMode.NoCheck, options.CertificateRevocationCheckMode); + Assert.Null(options.ClientCertificates); + Assert.Equal(SslProtocols.None, options.EnabledSslProtocols); + Assert.Equal(EncryptionPolicy.RequireEncryption, options.EncryptionPolicy); + Assert.Null(options.LocalCertificateSelectionCallback); + Assert.Null(options.RemoteCertificateValidationCallback); + Assert.Null(options.TargetHost); + + Assert.Same(options, handler.SslOptions); + + var newOptions = new SslClientAuthenticationOptions(); + handler.SslOptions = newOptions; + Assert.Same(newOptions, handler.SslOptions); + } + } + + [Fact] + public void UseCookies_GetSet_Roundtrips() + { + using (var handler = new SocketsHttpHandler()) + { + Assert.True(handler.UseCookies); + + handler.UseCookies = true; + Assert.True(handler.UseCookies); + + handler.UseCookies = false; + Assert.False(handler.UseCookies); + } + } + + [Fact] + public void UseProxy_GetSet_Roundtrips() + { + using (var handler = new SocketsHttpHandler()) + { + Assert.True(handler.UseProxy); + + handler.UseProxy = false; + Assert.False(handler.UseProxy); + + handler.UseProxy = true; + Assert.True(handler.UseProxy); + } + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task AfterDisposeSendAsync_GettersUsable_SettersThrow(bool dispose) + { + using (var handler = new SocketsHttpHandler()) + { + Type expectedExceptionType; + if (dispose) + { + handler.Dispose(); + expectedExceptionType = typeof(ObjectDisposedException); + } + else + { + await IssueRequestAsync(handler); + expectedExceptionType = typeof(InvalidOperationException); + } + + Assert.True(handler.AllowAutoRedirect); + Assert.Equal(DecompressionMethods.None, handler.AutomaticDecompression); + Assert.NotNull(handler.CookieContainer); + Assert.Null(handler.Credentials); + Assert.Null(handler.DefaultProxyCredentials); + Assert.Equal(50, handler.MaxAutomaticRedirections); + Assert.Equal(int.MaxValue, handler.MaxConnectionsPerServer); + Assert.Equal(64, handler.MaxResponseHeadersLength); + Assert.False(handler.PreAuthenticate); + Assert.Equal(TimeSpan.FromMinutes(2), handler.PooledConnectionIdleTimeout); + Assert.Equal(Timeout.InfiniteTimeSpan, handler.PooledConnectionLifetime); + Assert.NotNull(handler.Properties); + Assert.Null(handler.Proxy); + Assert.NotNull(handler.SslOptions); + Assert.True(handler.UseCookies); + Assert.True(handler.UseProxy); + + Assert.Throws(expectedExceptionType, () => handler.AllowAutoRedirect = false); + Assert.Throws(expectedExceptionType, () => handler.AutomaticDecompression = DecompressionMethods.GZip); + Assert.Throws(expectedExceptionType, () => handler.CookieContainer = new CookieContainer()); + Assert.Throws(expectedExceptionType, () => handler.Credentials = new NetworkCredential("anotheruser", "anotherpassword")); + Assert.Throws(expectedExceptionType, () => handler.DefaultProxyCredentials = new NetworkCredential("anotheruser", "anotherpassword")); + Assert.Throws(expectedExceptionType, () => handler.MaxAutomaticRedirections = 2); + Assert.Throws(expectedExceptionType, () => handler.MaxConnectionsPerServer = 2); + Assert.Throws(expectedExceptionType, () => handler.MaxResponseHeadersLength = 2); + Assert.Throws(expectedExceptionType, () => handler.PreAuthenticate = false); + Assert.Throws(expectedExceptionType, () => handler.PooledConnectionIdleTimeout = TimeSpan.FromSeconds(2)); + Assert.Throws(expectedExceptionType, () => handler.PooledConnectionLifetime = TimeSpan.FromSeconds(2)); + Assert.Throws(expectedExceptionType, () => handler.Proxy = new WebProxy()); + Assert.Throws(expectedExceptionType, () => handler.SslOptions = new SslClientAuthenticationOptions()); + Assert.Throws(expectedExceptionType, () => handler.UseCookies = false); + Assert.Throws(expectedExceptionType, () => handler.UseProxy = false); + } + } + } +} diff --git a/src/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj b/src/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj index 7a7c7fa8d6fd..04487b8ba1f7 100644 --- a/src/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj +++ b/src/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj @@ -118,11 +118,11 @@ - - + + diff --git a/src/System.Net.Http/tests/UnitTests/System.Net.Http.Unit.Tests.csproj b/src/System.Net.Http/tests/UnitTests/System.Net.Http.Unit.Tests.csproj index 06842f6aa8fd..b03dc63c489d 100644 --- a/src/System.Net.Http/tests/UnitTests/System.Net.Http.Unit.Tests.csproj +++ b/src/System.Net.Http/tests/UnitTests/System.Net.Http.Unit.Tests.csproj @@ -84,8 +84,8 @@ ProductionCode\Common\System\Threading\Tasks\TaskToApm.cs - - ProductionCode\System\Net\Http\Managed\AuthenticationHelper.Digest.cs + + ProductionCode\System\Net\Http\SocketsHttpHandler\AuthenticationHelper.Digest.cs ProductionCode\System\Net\Http\ByteArrayContent.cs @@ -297,11 +297,11 @@ ProductionCode\System\Net\Http\StringContent.cs - + ProductionCode\System\Net\Http\CurlResponseHeaderReader.cs - - ProductionCode\System\Net\Http\HttpEnvironmentProxy.cs + + ProductionCode\System\Net\Http\SocketsHttpHandler\HttpEnvironmentProxy.cs ProductionCode\System\Net\Http\HttpHandlerDefaults.cs diff --git a/src/System.Net.WebSockets.Client/src/System/Net/WebSockets/WebSocketHandle.Managed.cs b/src/System.Net.WebSockets.Client/src/System/Net/WebSockets/WebSocketHandle.Managed.cs index dfbfb28d24f5..d57381419a12 100644 --- a/src/System.Net.WebSockets.Client/src/System/Net/WebSockets/WebSocketHandle.Managed.cs +++ b/src/System.Net.WebSockets.Client/src/System/Net/WebSockets/WebSocketHandle.Managed.cs @@ -66,46 +66,7 @@ public Task CloseAsync(WebSocketCloseStatus closeStatus, string statusDescriptio public Task CloseOutputAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken) => _webSocket.CloseOutputAsync(closeStatus, statusDescription, cancellationToken); - - private sealed class DirectManagedHttpClientHandler : HttpClientHandler - { - private const string ManagedHandlerEnvVar = "DOTNET_SYSTEM_NET_HTTP_USEMANAGEDHTTPCLIENTHANDLER"; - private static readonly LocalDataStoreSlot s_managedHandlerSlot = GetSlot(); - private static readonly object s_true = true; - - private static LocalDataStoreSlot GetSlot() - { - LocalDataStoreSlot slot = Thread.GetNamedDataSlot(ManagedHandlerEnvVar); - if (slot != null) - { - return slot; - } - - try - { - return Thread.AllocateNamedDataSlot(ManagedHandlerEnvVar); - } - catch (ArgumentException) // in case of a race condition where multiple threads all try to allocate the slot concurrently - { - return Thread.GetNamedDataSlot(ManagedHandlerEnvVar); - } - } - - public static DirectManagedHttpClientHandler CreateHandler() - { - Thread.SetData(s_managedHandlerSlot, s_true); - try - { - return new DirectManagedHttpClientHandler(); - } - finally { Thread.SetData(s_managedHandlerSlot, null); } - } - - public new Task SendAsync( - HttpRequestMessage request, CancellationToken cancellationToken) => - base.SendAsync(request, cancellationToken); - } - + public async Task ConnectAsyncCore(Uri uri, CancellationToken cancellationToken, ClientWebSocketOptions options) { HttpResponseMessage response = null; @@ -127,15 +88,13 @@ public async Task ConnectAsyncCore(Uri uri, CancellationToken cancellationToken, AddWebSocketHeaders(request, secKeyAndSecWebSocketAccept.Key, options); // Create the handler for this request and populate it with all of the options. - DirectManagedHttpClientHandler handler = DirectManagedHttpClientHandler.CreateHandler(); - handler.UseDefaultCredentials = options.UseDefaultCredentials; + var handler = new SocketsHttpHandler(); handler.Credentials = options.Credentials; handler.Proxy = options.Proxy; handler.CookieContainer = options.Cookies; if (options._clientCertificates?.Count > 0) // use field to avoid lazily initializing the collection { - handler.ClientCertificateOptions = ClientCertificateOption.Manual; - handler.ClientCertificates.AddRange(options.ClientCertificates); + handler.SslOptions.ClientCertificates.AddRange(options.ClientCertificates); } // Issue the request. The response must be status code 101. @@ -154,7 +113,7 @@ public async Task ConnectAsyncCore(Uri uri, CancellationToken cancellationToken, using (linkedCancellation) { - response = await handler.SendAsync(request, externalAndAbortCancellation.Token).ConfigureAwait(false); + response = await new HttpMessageInvoker(handler).SendAsync(request, externalAndAbortCancellation.Token).ConfigureAwait(false); externalAndAbortCancellation.Token.ThrowIfCancellationRequested(); // poll in case sends/receives in request/response didn't observe cancellation } diff --git a/src/System.Net.WebSockets.WebSocketProtocol/tests/WebSocketProtocolTests.cs b/src/System.Net.WebSockets.WebSocketProtocol/tests/WebSocketProtocolTests.cs index 597df59646c0..9db60cd882c5 100644 --- a/src/System.Net.WebSockets.WebSocketProtocol/tests/WebSocketProtocolTests.cs +++ b/src/System.Net.WebSockets.WebSocketProtocol/tests/WebSocketProtocolTests.cs @@ -7,6 +7,7 @@ using System.Net; using System.Net.Http; using System.Net.Http.Headers; +using System.Net.Security; using System.Net.Sockets; using System.Security.Cryptography; using System.Text; @@ -55,19 +56,41 @@ public void CreateFromStream_ValidBufferSizes_Succeed(int bufferSize) [MemberData(nameof(EchoServers))] public async Task WebSocketProtocol_CreateFromConnectedStream_Succeeds(Uri echoUri) { - Uri uri = new UriBuilder(echoUri) { Scheme = (echoUri.Scheme == "ws") ? "http" : "https" }.Uri; - HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, uri); - KeyValuePair secKeyAndSecWebSocketAccept = CreateSecKeyAndSecWebSocketAccept(); - AddWebSocketHeaders(request, secKeyAndSecWebSocketAccept.Key); - DirectManagedHttpClientHandler handler = DirectManagedHttpClientHandler.CreateHandler(); - using (HttpResponseMessage response = await handler.SendAsync(request, CancellationToken.None).ConfigureAwait(false)) + using (var client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) { - Assert.Equal(HttpStatusCode.SwitchingProtocols, response.StatusCode); - using (Stream connectedStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) + bool secure = echoUri.Scheme == "wss"; + client.Connect(echoUri.Host, secure ? 443 : 80); + + Stream stream = new NetworkStream(client, ownsSocket: false); + if (secure) { - Assert.True(connectedStream.CanRead); - Assert.True(connectedStream.CanWrite); - using (WebSocket socket = WebSocketProtocol.CreateFromStream(connectedStream, false, null, TimeSpan.FromSeconds(10))) + SslStream ssl = new SslStream(stream, leaveInnerStreamOpen: true, delegate { return true; }); + await ssl.AuthenticateAsClientAsync(echoUri.Host); + stream = ssl; + } + + using (stream) + { + using (var writer = new StreamWriter(stream, Encoding.ASCII, bufferSize: 1, leaveOpen: true)) + { + await writer.WriteAsync($"GET {echoUri.PathAndQuery} HTTP/1.1\r\n"); + await writer.WriteAsync($"Host: {echoUri.Host}\r\n"); + await writer.WriteAsync($"Upgrade: websocket\r\n"); + await writer.WriteAsync($"Connection: Upgrade\r\n"); + await writer.WriteAsync($"Sec-WebSocket-Version: 13\r\n"); + await writer.WriteAsync($"Sec-WebSocket-Key: {Convert.ToBase64String(Guid.NewGuid().ToByteArray())}\r\n"); + await writer.WriteAsync($"\r\n"); + } + + using (var reader = new StreamReader(stream, Encoding.ASCII, detectEncodingFromByteOrderMarks: false, bufferSize: 1, leaveOpen: true)) + { + string statusLine = await reader.ReadLineAsync(); + Assert.NotEmpty(statusLine); + Assert.Equal("HTTP/1.1 101 Switching Protocols", statusLine); + while (!string.IsNullOrEmpty(await reader.ReadLineAsync())); + } + + using (WebSocket socket = WebSocketProtocol.CreateFromStream(stream, false, null, TimeSpan.FromSeconds(10))) { Assert.NotNull(socket); Assert.Equal(WebSocketState.Open, socket.State); @@ -87,28 +110,6 @@ public async Task WebSocketProtocol_CreateFromConnectedStream_Succeeds(Uri echoU public static readonly object[][] EchoServers = System.Net.Test.Common.Configuration.WebSockets.EchoServers; - /// GUID appended by the server as part of the security key response. Defined in the RFC. - private const string WSServerGuid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - - private static KeyValuePair CreateSecKeyAndSecWebSocketAccept() - { - string secKey = Convert.ToBase64String(Guid.NewGuid().ToByteArray()); - using (SHA1 sha = SHA1.Create()) - { - return new KeyValuePair( - secKey, - Convert.ToBase64String(sha.ComputeHash(Encoding.ASCII.GetBytes(secKey + WSServerGuid)))); - } - } - - private static void AddWebSocketHeaders(HttpRequestMessage request, string secKey) - { - request.Headers.TryAddWithoutValidation(HttpKnownHeaderNames.Connection, HttpKnownHeaderNames.Upgrade); - request.Headers.TryAddWithoutValidation(HttpKnownHeaderNames.Upgrade, "websocket"); - request.Headers.TryAddWithoutValidation(HttpKnownHeaderNames.SecWebSocketVersion, "13"); - request.Headers.TryAddWithoutValidation(HttpKnownHeaderNames.SecWebSocketKey, secKey); - } - private sealed class UnreadableStream : Stream { public override bool CanRead => false; @@ -122,44 +123,5 @@ private sealed class UnreadableStream : Stream public override void SetLength(long value) => throw new NotImplementedException(); public override void Write(byte[] buffer, int offset, int count) => throw new NotImplementedException(); } - - private sealed class DirectManagedHttpClientHandler : HttpClientHandler - { - private const string ManagedHandlerEnvVar = "DOTNET_SYSTEM_NET_HTTP_USEMANAGEDHTTPCLIENTHANDLER"; - private static readonly LocalDataStoreSlot s_managedHandlerSlot = GetSlot(); - private static readonly object s_true = true; - - private static LocalDataStoreSlot GetSlot() - { - LocalDataStoreSlot slot = Thread.GetNamedDataSlot(ManagedHandlerEnvVar); - if (slot != null) - { - return slot; - } - - try - { - return Thread.AllocateNamedDataSlot(ManagedHandlerEnvVar); - } - catch (ArgumentException) // in case of a race condition where multiple threads all try to allocate the slot concurrently - { - return Thread.GetNamedDataSlot(ManagedHandlerEnvVar); - } - } - - public static DirectManagedHttpClientHandler CreateHandler() - { - Thread.SetData(s_managedHandlerSlot, s_true); - try - { - return new DirectManagedHttpClientHandler(); - } - finally { Thread.SetData(s_managedHandlerSlot, null); } - } - - public new Task SendAsync( - HttpRequestMessage request, CancellationToken cancellationToken) => - base.SendAsync(request, cancellationToken); - } } }