From 97d4730c59b5a5439899da790eadd3ca333b8ab3 Mon Sep 17 00:00:00 2001 From: "Ahmet Ibrahim Aksoy (from Dev Box)" Date: Fri, 26 Jan 2024 07:16:11 +0100 Subject: [PATCH 01/37] Implement more ServicePoint properties --- .../System.Net.Requests/src/System/Net/HttpWebRequest.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs index 2a54bbb4d8d50d..94e6ae7ea99fc6 100644 --- a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs +++ b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs @@ -806,10 +806,12 @@ public Version ProtocolVersion if (value.Equals(HttpVersion.Version11)) { IsVersionHttp10 = false; + ServicePoint.ProtocolVersion = HttpVersion.Version11; } else if (value.Equals(HttpVersion.Version10)) { IsVersionHttp10 = true; + ServicePoint.ProtocolVersion = HttpVersion.Version10; } else { @@ -1621,6 +1623,13 @@ private static HttpClient CreateHttpClient(HttpClientParameters parameters, Http handler.UseCookies = false; } + if (parameters.ServicePoint is { } servicePoint) + { + handler.MaxConnectionsPerServer = servicePoint.ConnectionLimit; + handler.PooledConnectionIdleTimeout = servicePoint.MaxIdleTime == -1 ? Threading.Timeout.InfiniteTimeSpan : TimeSpan.FromMilliseconds(servicePoint.MaxIdleTime); + handler.PooledConnectionLifetime = servicePoint.ConnectionLeaseTimeout == -1 ? Threading.Timeout.InfiniteTimeSpan : TimeSpan.FromMilliseconds(servicePoint.ConnectionLeaseTimeout); + } + Debug.Assert(handler.UseProxy); // Default of handler.UseProxy is true. Debug.Assert(handler.Proxy == null); // Default of handler.Proxy is null. From b9fdca49107db077399face0e25f1e3c677e12da Mon Sep 17 00:00:00 2001 From: "Ahmet Ibrahim Aksoy (from Dev Box)" Date: Wed, 31 Jan 2024 17:45:49 +0100 Subject: [PATCH 02/37] Implement more properties --- .../src/System.Net.Requests.csproj | 1 + .../src/System/Net/HttpWebRequest.cs | 45 +++++++++++++++++-- .../src/System/Net/HttpWebResponse.cs | 27 ++++++++++- .../Net/ServicePoint/ServicePointManager.cs | 2 +- 4 files changed, 69 insertions(+), 6 deletions(-) diff --git a/src/libraries/System.Net.Requests/src/System.Net.Requests.csproj b/src/libraries/System.Net.Requests/src/System.Net.Requests.csproj index 397622b4806a03..b53b7272ea4171 100644 --- a/src/libraries/System.Net.Requests/src/System.Net.Requests.csproj +++ b/src/libraries/System.Net.Requests/src/System.Net.Requests.csproj @@ -110,6 +110,7 @@ + diff --git a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs index 94e6ae7ea99fc6..59978ed9c474ef 100644 --- a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs +++ b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs @@ -6,6 +6,7 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; +using System.Net; using System.Net.Cache; using System.Net.Http; using System.Net.Http.Headers; @@ -1678,6 +1679,9 @@ private static HttpClient CreateHttpClient(HttpClientParameters parameters, Http { var socket = new Socket(SocketType.Stream, ProtocolType.Tcp); + // Start resolve dns task + Task addressesTask = Dns.GetHostAddressesAsync(context.DnsEndPoint.Host, cancellationToken); + IPAddress[]? addresses = null; try { if (parameters.ServicePoint is { } servicePoint) @@ -1693,19 +1697,54 @@ private static HttpClient CreateHttpClient(HttpClientParameters parameters, Http socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime, keepAlive.Time); socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval, keepAlive.Interval); } + + if (servicePoint.BindIPEndPointDelegate != null) + { + addresses ??= await addressesTask.ConfigureAwait(false); + foreach (IPAddress address in addresses) + { + int retryCount = 0; + for (; retryCount < int.MaxValue; retryCount++) + { + IPEndPoint endPoint = servicePoint.BindIPEndPointDelegate(servicePoint, new IPEndPoint(address, context.DnsEndPoint.Port), retryCount); + if (endPoint != null) + { + try + { + socket.Bind(endPoint); + break; + } + catch + { + continue; + } + } + } + + if (retryCount == int.MaxValue) + { + throw new OverflowException(); //TODO (aaksoy): Add SR for this. + } + } + } + + if (servicePoint.UseNagleAlgorithm is not true) + { + socket.NoDelay = true; + } } - socket.NoDelay = true; + addresses ??= await addressesTask.ConfigureAwait(false); if (parameters.Async) { - await socket.ConnectAsync(context.DnsEndPoint, cancellationToken).ConfigureAwait(false); + await socket.ConnectAsync(addresses, context.DnsEndPoint.Port, cancellationToken).ConfigureAwait(false); } else { using (cancellationToken.UnsafeRegister(s => ((Socket)s!).Dispose(), socket)) { - socket.Connect(context.DnsEndPoint); + socket.Connect(addresses, context.DnsEndPoint.Port); } // Throw in case cancellation caused the socket to be disposed after the Connect completed diff --git a/src/libraries/System.Net.Requests/src/System/Net/HttpWebResponse.cs b/src/libraries/System.Net.Requests/src/System/Net/HttpWebResponse.cs index f7fae7869b1e7f..e0fb1d2f3bf2c3 100644 --- a/src/libraries/System.Net.Requests/src/System/Net/HttpWebResponse.cs +++ b/src/libraries/System.Net.Requests/src/System/Net/HttpWebResponse.cs @@ -25,6 +25,7 @@ public class HttpWebResponse : WebResponse, ISerializable private WebHeaderCollection? _webHeaderCollection; private string? _characterSet; private readonly bool _isVersionHttp11 = true; + private readonly int _maxResponseHeadersLength = -1; [Obsolete("This API supports the .NET infrastructure and is not intended to be used directly from your code.", true)] [EditorBrowsable(EditorBrowsableState.Never)] @@ -53,10 +54,11 @@ protected override void GetObjectData(SerializationInfo serializationInfo, Strea throw new PlatformNotSupportedException(); } - internal HttpWebResponse(HttpResponseMessage _message, Uri requestUri, CookieContainer? cookieContainer) + internal HttpWebResponse(HttpResponseMessage _message, Uri requestUri, CookieContainer? cookieContainer, int maxErrorResponseLength) { _httpResponseMessage = _message; _requestUri = requestUri; + _maxResponseHeadersLength = maxErrorResponseLength; // Match Desktop behavior. If the request didn't set a CookieContainer, we don't populate the response's CookieCollection. if (cookieContainer != null) @@ -337,7 +339,28 @@ public override Stream GetResponseStream() CheckDisposed(); if (_httpResponseMessage.Content != null) { - return _httpResponseMessage.Content.ReadAsStream(); + Stream contentStream = _httpResponseMessage.Content.ReadAsStream(); + if (_maxResponseHeadersLength == -1) + { + return contentStream; + } + + MemoryStream memoryStream = new MemoryStream(); + byte[] buffer = new byte[1024]; + int readLength = 0; + + while (readLength < _maxResponseHeadersLength) + { + int len = contentStream.Read(buffer, 0, Math.Min(_maxResponseHeadersLength - readLength, buffer.Length)); + if (len == 0) + { + break; + } + memoryStream.Write(buffer, 0, len); + readLength += len; + } + + return memoryStream; } return Stream.Null; diff --git a/src/libraries/System.Net.Requests/src/System/Net/ServicePoint/ServicePointManager.cs b/src/libraries/System.Net.Requests/src/System/Net/ServicePoint/ServicePointManager.cs index a0cf9dcece157f..a5c34fe33bf735 100644 --- a/src/libraries/System.Net.Requests/src/System/Net/ServicePoint/ServicePointManager.cs +++ b/src/libraries/System.Net.Requests/src/System/Net/ServicePoint/ServicePointManager.cs @@ -78,7 +78,7 @@ public static int MaxServicePointIdleTime } } - public static bool UseNagleAlgorithm { get; set; } = true; + public static bool UseNagleAlgorithm { get; set; } public static bool Expect100Continue { get; set; } = true; From 69055bb3f6ca29e1c7da7fbd749c3263e9db8615 Mon Sep 17 00:00:00 2001 From: "Ahmet Ibrahim Aksoy (from Dev Box)" Date: Thu, 1 Feb 2024 13:56:53 +0100 Subject: [PATCH 03/37] Make implementation correct --- .../src/System/Net/HttpWebRequest.cs | 4 ++-- .../src/System/Net/HttpWebResponse.cs | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs index 59978ed9c474ef..067d8343093364 100644 --- a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs +++ b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs @@ -1199,7 +1199,7 @@ private async Task SendRequest(bool async) HttpResponseMessage responseMessage = await _sendRequestTask.ConfigureAwait(false); - HttpWebResponse response = new HttpWebResponse(responseMessage, _requestUri, _cookieContainer); + HttpWebResponse response = new HttpWebResponse(responseMessage, _requestUri, _cookieContainer, DefaultMaximumErrorResponseLength); int maxSuccessStatusCode = AllowAutoRedirect ? 299 : 399; if ((int)response.StatusCode > maxSuccessStatusCode || (int)response.StatusCode < 200) @@ -1706,7 +1706,7 @@ private static HttpClient CreateHttpClient(HttpClientParameters parameters, Http int retryCount = 0; for (; retryCount < int.MaxValue; retryCount++) { - IPEndPoint endPoint = servicePoint.BindIPEndPointDelegate(servicePoint, new IPEndPoint(address, context.DnsEndPoint.Port), retryCount); + IPEndPoint? endPoint = servicePoint.BindIPEndPointDelegate(servicePoint, new IPEndPoint(address, context.DnsEndPoint.Port), retryCount); if (endPoint != null) { try diff --git a/src/libraries/System.Net.Requests/src/System/Net/HttpWebResponse.cs b/src/libraries/System.Net.Requests/src/System/Net/HttpWebResponse.cs index e0fb1d2f3bf2c3..459e42934c7ceb 100644 --- a/src/libraries/System.Net.Requests/src/System/Net/HttpWebResponse.cs +++ b/src/libraries/System.Net.Requests/src/System/Net/HttpWebResponse.cs @@ -25,7 +25,7 @@ public class HttpWebResponse : WebResponse, ISerializable private WebHeaderCollection? _webHeaderCollection; private string? _characterSet; private readonly bool _isVersionHttp11 = true; - private readonly int _maxResponseHeadersLength = -1; + private readonly int _maxErrorResponseLength = -1; [Obsolete("This API supports the .NET infrastructure and is not intended to be used directly from your code.", true)] [EditorBrowsable(EditorBrowsableState.Never)] @@ -58,7 +58,7 @@ internal HttpWebResponse(HttpResponseMessage _message, Uri requestUri, CookieCon { _httpResponseMessage = _message; _requestUri = requestUri; - _maxResponseHeadersLength = maxErrorResponseLength; + _maxErrorResponseLength = maxErrorResponseLength; // Match Desktop behavior. If the request didn't set a CookieContainer, we don't populate the response's CookieCollection. if (cookieContainer != null) @@ -340,7 +340,7 @@ public override Stream GetResponseStream() if (_httpResponseMessage.Content != null) { Stream contentStream = _httpResponseMessage.Content.ReadAsStream(); - if (_maxResponseHeadersLength == -1) + if (_maxErrorResponseLength == -1 || StatusCode <= (HttpStatusCode)399) { return contentStream; } @@ -349,9 +349,9 @@ public override Stream GetResponseStream() byte[] buffer = new byte[1024]; int readLength = 0; - while (readLength < _maxResponseHeadersLength) + while (readLength < _maxErrorResponseLength) { - int len = contentStream.Read(buffer, 0, Math.Min(_maxResponseHeadersLength - readLength, buffer.Length)); + int len = contentStream.Read(buffer, 0, Math.Min(_maxErrorResponseLength - readLength, buffer.Length)); if (len == 0) { break; From 3591f1ac12001e579be4c286cfd5dccac051ae57 Mon Sep 17 00:00:00 2001 From: "Ahmet Ibrahim Aksoy (from Dev Box)" Date: Thu, 1 Feb 2024 19:18:02 +0100 Subject: [PATCH 04/37] Change UseNagleAlgorithm default to false --- .../tests/ServicePointTests/ServicePointManagerTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Net.Requests/tests/ServicePointTests/ServicePointManagerTest.cs b/src/libraries/System.Net.Requests/tests/ServicePointTests/ServicePointManagerTest.cs index c1230598a8d4e5..ec64068ed456fc 100644 --- a/src/libraries/System.Net.Requests/tests/ServicePointTests/ServicePointManagerTest.cs +++ b/src/libraries/System.Net.Requests/tests/ServicePointTests/ServicePointManagerTest.cs @@ -181,7 +181,7 @@ public static void ServerCertificateValidationCallback_Roundtrips() [Fact] public static void UseNagleAlgorithm_Roundtrips() { - Assert.True(ServicePointManager.UseNagleAlgorithm); + Assert.False(ServicePointManager.UseNagleAlgorithm); try { ServicePointManager.UseNagleAlgorithm = false; @@ -325,7 +325,7 @@ public static void FindServicePoint_ReturnedServicePointMatchesExpectedValues() Assert.Equal(new Version(1, 1), sp.ProtocolVersion); Assert.Equal(-1, sp.ReceiveBufferSize); Assert.True(sp.SupportsPipelining, "SupportsPipelining"); - Assert.True(sp.UseNagleAlgorithm, "UseNagleAlgorithm"); + Assert.False(sp.UseNagleAlgorithm, "UseNagleAlgorithm"); }).Dispose(); } From d8613191cb0b27b4e622922628a58d053743f413 Mon Sep 17 00:00:00 2001 From: Ahmet Ibrahim Aksoy Date: Mon, 5 Feb 2024 18:46:07 +0100 Subject: [PATCH 05/37] Update src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs Co-authored-by: Miha Zupan --- .../System.Net.Requests/src/System/Net/HttpWebRequest.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs index 067d8343093364..22444fba6215ed 100644 --- a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs +++ b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs @@ -1728,10 +1728,7 @@ private static HttpClient CreateHttpClient(HttpClientParameters parameters, Http } } - if (servicePoint.UseNagleAlgorithm is not true) - { - socket.NoDelay = true; - } + socket.NoDelay = !servicePoint.UseNagleAlgorithm; } addresses ??= await addressesTask.ConfigureAwait(false); From 043647c58d07017b557791742eae3257fb5d58d4 Mon Sep 17 00:00:00 2001 From: "Ahmet Ibrahim Aksoy (from Dev Box)" Date: Fri, 9 Feb 2024 20:39:18 +0100 Subject: [PATCH 06/37] Review feedback --- .../src/System/Net/HttpWebRequest.cs | 36 ++++---- .../src/System/Net/HttpWebResponse.cs | 13 ++- .../tests/HttpWebRequestTest.cs | 90 +++++++++++++++++++ 3 files changed, 119 insertions(+), 20 deletions(-) diff --git a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs index 22444fba6215ed..7957997fecd313 100644 --- a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs +++ b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs @@ -1679,11 +1679,10 @@ private static HttpClient CreateHttpClient(HttpClientParameters parameters, Http { var socket = new Socket(SocketType.Stream, ProtocolType.Tcp); - // Start resolve dns task - Task addressesTask = Dns.GetHostAddressesAsync(context.DnsEndPoint.Host, cancellationToken); - IPAddress[]? addresses = null; try { + //Start dns resolve + IPAddress[] addresses = await Dns.GetHostAddressesAsync(context.DnsEndPoint.Host, cancellationToken).ConfigureAwait(false); if (parameters.ServicePoint is { } servicePoint) { if (servicePoint.ReceiveBufferSize != -1) @@ -1700,39 +1699,42 @@ private static HttpClient CreateHttpClient(HttpClientParameters parameters, Http if (servicePoint.BindIPEndPointDelegate != null) { - addresses ??= await addressesTask.ConfigureAwait(false); - foreach (IPAddress address in addresses) + var bindHelper = () => { - int retryCount = 0; - for (; retryCount < int.MaxValue; retryCount++) + const int MaxRetries = 100; + foreach (IPAddress address in addresses) { - IPEndPoint? endPoint = servicePoint.BindIPEndPointDelegate(servicePoint, new IPEndPoint(address, context.DnsEndPoint.Port), retryCount); - if (endPoint != null) + int retryCount = 0; + for (; retryCount < MaxRetries; retryCount++) { + IPEndPoint? endPoint = servicePoint.BindIPEndPointDelegate(servicePoint, new IPEndPoint(address, context.DnsEndPoint.Port), retryCount); + if (endPoint == null) // Get other address to try + { + break; + } try { socket.Bind(endPoint); - break; + return; // Bind successful, exit loops. } catch { continue; } } - } - if (retryCount == int.MaxValue) - { - throw new OverflowException(); //TODO (aaksoy): Add SR for this. + if (retryCount >= MaxRetries) + { + throw new OverflowException(); //TODO (aaksoy): Add SR for this. + } } - } + }; + bindHelper(); } socket.NoDelay = !servicePoint.UseNagleAlgorithm; } - addresses ??= await addressesTask.ConfigureAwait(false); - if (parameters.Async) { await socket.ConnectAsync(addresses, context.DnsEndPoint.Port, cancellationToken).ConfigureAwait(false); diff --git a/src/libraries/System.Net.Requests/src/System/Net/HttpWebResponse.cs b/src/libraries/System.Net.Requests/src/System/Net/HttpWebResponse.cs index 459e42934c7ceb..368fb9aced196a 100644 --- a/src/libraries/System.Net.Requests/src/System/Net/HttpWebResponse.cs +++ b/src/libraries/System.Net.Requests/src/System/Net/HttpWebResponse.cs @@ -340,7 +340,7 @@ public override Stream GetResponseStream() if (_httpResponseMessage.Content != null) { Stream contentStream = _httpResponseMessage.Content.ReadAsStream(); - if (_maxErrorResponseLength == -1 || StatusCode <= (HttpStatusCode)399) + if (_maxErrorResponseLength < 0 || StatusCode < HttpStatusCode.BadRequest) { return contentStream; } @@ -359,8 +359,15 @@ public override Stream GetResponseStream() memoryStream.Write(buffer, 0, len); readLength += len; } - - return memoryStream; + memoryStream.Seek(0, SeekOrigin.Begin); + try + { + return memoryStream; + } + finally + { + contentStream.Dispose(); + } } return Stream.Null; diff --git a/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs b/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs index 45563ccc3dd0ed..cc44ac55de39ad 100644 --- a/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs +++ b/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs @@ -2167,6 +2167,96 @@ await server.AcceptConnectionAsync( ); } + [Fact] + public async Task SendHttpRequest_WhenDefaultMaximumErrorResponseLengthSet_Success() + { + await LoopbackServer.CreateClientAndServerAsync( + async (uri) => + { + HttpWebRequest request = WebRequest.CreateHttp(uri); + HttpWebRequest.DefaultMaximumErrorResponseLength = 5; + var exception = await Assert.ThrowsAsync(() => request.GetResponseAsync()); + using (var responseStream = exception.Response.GetResponseStream()) + { + Assert.Equal(5, responseStream.Length); + var buffer = new byte[10]; + int readLen = responseStream.Read(buffer, 0, buffer.Length); + Assert.Equal(5, readLen); + Assert.Equal(new string('a', 5), Encoding.UTF8.GetString(buffer[0..readLen])); + } + }, + async (server) => + { + await server.AcceptConnectionAsync( + async (client) => + { + await client.SendResponseAsync(statusCode: HttpStatusCode.InternalServerError, content: new string('a', 10)); + }); + }); + } + + [Fact] + public void HttpWebRequest_SetProtocolVersion_Success() + { + HttpWebRequest request = WebRequest.CreateHttp(Configuration.Http.RemoteEchoServer); + + request.ProtocolVersion = HttpVersion.Version10; + Assert.Equal(HttpVersion.Version10, request.ServicePoint.ProtocolVersion); + + request.ProtocolVersion = HttpVersion.Version11; + Assert.Equal(HttpVersion.Version11, request.ServicePoint.ProtocolVersion); + } + + [Fact] + public async Task SendHttpRequest_BindIPEndPoint_Success() + { + await LoopbackServer.CreateClientAndServerAsync( + async (uri) => + { + HttpWebRequest request = WebRequest.CreateHttp(uri); + request.ServicePoint.BindIPEndPointDelegate = + (sp, ep, retryCount) => { return new IPEndPoint(IPAddress.Loopback, 27277); }; + using (var response = (HttpWebResponse)await request.GetResponseAsync()) + { + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + }, + async (server) => + { + await server.AcceptConnectionAsync( + async (client) => + { + var ipEp = (IPEndPoint)client.Socket.RemoteEndPoint; + Assert.Equal(27277, ipEp.Port); + await client.SendResponseAsync(); + }); + }); + } + + [Fact] + public async Task SendHttpRequest_BindIPEndPoint_Throws() + { + Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + socket.Bind(new IPEndPoint(IPAddress.Loopback, 0)); + await LoopbackServer.CreateClientAndServerAsync( + async (uri) => + { + HttpWebRequest request = WebRequest.CreateHttp(uri); + request.ServicePoint.BindIPEndPointDelegate = + (sp, ep, retryCount) => { return (IPEndPoint)socket.LocalEndPoint!; }; + var exception = await Assert.ThrowsAsync(() => request.GetResponseAsync()); + Assert.IsType(exception.InnerException?.InnerException); + }, + async (server) => + { + await server.AcceptConnectionAsync( + async (client) => + { + await client.SendResponseAsync(); + }); + }); + } + private void RequestStreamCallback(IAsyncResult asynchronousResult) { RequestState state = (RequestState)asynchronousResult.AsyncState; From f843727cb392f08c7695702cff3d281697705a84 Mon Sep 17 00:00:00 2001 From: "Ahmet Ibrahim Aksoy (from Dev Box)" Date: Fri, 9 Feb 2024 20:39:33 +0100 Subject: [PATCH 07/37] Start implement DnsRoundRobin --- .../src/System/Net/ServicePoint/ServicePoint.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/libraries/System.Net.Requests/src/System/Net/ServicePoint/ServicePoint.cs b/src/libraries/System.Net.Requests/src/System/Net/ServicePoint/ServicePoint.cs index d48ac0760992c0..6533a086131e47 100644 --- a/src/libraries/System.Net.Requests/src/System/Net/ServicePoint/ServicePoint.cs +++ b/src/libraries/System.Net.Requests/src/System/Net/ServicePoint/ServicePoint.cs @@ -16,6 +16,15 @@ public class ServicePoint internal TcpKeepAlive? KeepAlive { get; set; } + internal int CurrentAddressIndex { get; set; } + + internal DateTime LastDnsResolve { get; set; } + + internal bool NeedDnsResolve => LastDnsResolve + .CompareTo(DateTime.Now.AddMilliseconds(-ServicePointManager.DnsRefreshTimeout)) >= 0; + + internal IPAddress[]? CachedAddresses { get; set; } + internal ServicePoint(Uri address) { Debug.Assert(address != null); From c61e5ed443ac6d7077f3e80d8ad8263ab58253c0 Mon Sep 17 00:00:00 2001 From: Ahmet Ibrahim Aksoy Date: Fri, 9 Feb 2024 20:58:10 +0100 Subject: [PATCH 08/37] remove passing parameter over ctor for static prop --- .../src/System/Net/HttpWebRequest.cs | 2 +- .../src/System/Net/HttpWebResponse.cs | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs index 7957997fecd313..a1c1caf9bac67d 100644 --- a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs +++ b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs @@ -1199,7 +1199,7 @@ private async Task SendRequest(bool async) HttpResponseMessage responseMessage = await _sendRequestTask.ConfigureAwait(false); - HttpWebResponse response = new HttpWebResponse(responseMessage, _requestUri, _cookieContainer, DefaultMaximumErrorResponseLength); + HttpWebResponse response = new HttpWebResponse(responseMessage, _requestUri, _cookieContainer); int maxSuccessStatusCode = AllowAutoRedirect ? 299 : 399; if ((int)response.StatusCode > maxSuccessStatusCode || (int)response.StatusCode < 200) diff --git a/src/libraries/System.Net.Requests/src/System/Net/HttpWebResponse.cs b/src/libraries/System.Net.Requests/src/System/Net/HttpWebResponse.cs index 368fb9aced196a..090c61823b30bd 100644 --- a/src/libraries/System.Net.Requests/src/System/Net/HttpWebResponse.cs +++ b/src/libraries/System.Net.Requests/src/System/Net/HttpWebResponse.cs @@ -25,7 +25,6 @@ public class HttpWebResponse : WebResponse, ISerializable private WebHeaderCollection? _webHeaderCollection; private string? _characterSet; private readonly bool _isVersionHttp11 = true; - private readonly int _maxErrorResponseLength = -1; [Obsolete("This API supports the .NET infrastructure and is not intended to be used directly from your code.", true)] [EditorBrowsable(EditorBrowsableState.Never)] @@ -54,11 +53,10 @@ protected override void GetObjectData(SerializationInfo serializationInfo, Strea throw new PlatformNotSupportedException(); } - internal HttpWebResponse(HttpResponseMessage _message, Uri requestUri, CookieContainer? cookieContainer, int maxErrorResponseLength) + internal HttpWebResponse(HttpResponseMessage _message, Uri requestUri, CookieContainer? cookieContainer) { _httpResponseMessage = _message; _requestUri = requestUri; - _maxErrorResponseLength = maxErrorResponseLength; // Match Desktop behavior. If the request didn't set a CookieContainer, we don't populate the response's CookieCollection. if (cookieContainer != null) @@ -340,7 +338,8 @@ public override Stream GetResponseStream() if (_httpResponseMessage.Content != null) { Stream contentStream = _httpResponseMessage.Content.ReadAsStream(); - if (_maxErrorResponseLength < 0 || StatusCode < HttpStatusCode.BadRequest) + int maxErrorResponseLength = HttpWebRequest.DefaultMaximumErrorResponseLength; + if (maxErrorResponseLength <= 0 || StatusCode < HttpStatusCode.BadRequest) // Keep 0 as NOP value for backward compatibility { return contentStream; } @@ -349,9 +348,9 @@ public override Stream GetResponseStream() byte[] buffer = new byte[1024]; int readLength = 0; - while (readLength < _maxErrorResponseLength) + while (readLength < maxErrorResponseLength) { - int len = contentStream.Read(buffer, 0, Math.Min(_maxErrorResponseLength - readLength, buffer.Length)); + int len = contentStream.Read(buffer, 0, Math.Min(maxErrorResponseLength - readLength, buffer.Length)); if (len == 0) { break; From 30bd75c386590d1ff7d4b3082c6004ea9f65476d Mon Sep 17 00:00:00 2001 From: Ahmet Ibrahim Aksoy Date: Fri, 9 Feb 2024 21:02:27 +0100 Subject: [PATCH 09/37] Change DefaultMaximumErrorResponseLength default value to -1 --- .../System.Net.Requests/src/System/Net/HttpWebRequest.cs | 5 +---- .../System.Net.Requests/src/System/Net/HttpWebResponse.cs | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs index a1c1caf9bac67d..55806d06842bf0 100644 --- a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs +++ b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs @@ -683,10 +683,7 @@ public static int DefaultMaximumResponseHeadersLength } // NOP - public static int DefaultMaximumErrorResponseLength - { - get; set; - } + public static int DefaultMaximumErrorResponseLength { get; set; } = -1; private static RequestCachePolicy? _defaultCachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache); private static bool _isDefaultCachePolicySet; diff --git a/src/libraries/System.Net.Requests/src/System/Net/HttpWebResponse.cs b/src/libraries/System.Net.Requests/src/System/Net/HttpWebResponse.cs index 090c61823b30bd..d0de388457c7cb 100644 --- a/src/libraries/System.Net.Requests/src/System/Net/HttpWebResponse.cs +++ b/src/libraries/System.Net.Requests/src/System/Net/HttpWebResponse.cs @@ -339,7 +339,7 @@ public override Stream GetResponseStream() { Stream contentStream = _httpResponseMessage.Content.ReadAsStream(); int maxErrorResponseLength = HttpWebRequest.DefaultMaximumErrorResponseLength; - if (maxErrorResponseLength <= 0 || StatusCode < HttpStatusCode.BadRequest) // Keep 0 as NOP value for backward compatibility + if (maxErrorResponseLength < 0 || StatusCode < HttpStatusCode.BadRequest) { return contentStream; } From cf3003d248704358cb8233a7dc6d54d5991289de Mon Sep 17 00:00:00 2001 From: Ahmet Ibrahim Aksoy Date: Sun, 11 Feb 2024 17:49:59 +0100 Subject: [PATCH 10/37] Update ServicePoint parameter passing, implement Certificate on ServicePoint --- .../src/System/Net/HttpWebRequest.cs | 109 +++++++++--------- 1 file changed, 53 insertions(+), 56 deletions(-) diff --git a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs index 55806d06842bf0..02068fc37e0ae3 100644 --- a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs +++ b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs @@ -114,7 +114,7 @@ private sealed class HttpClientParameters public readonly RemoteCertificateValidationCallback? ServerCertificateValidationCallback; public readonly X509CertificateCollection? ClientCertificates; public readonly CookieContainer? CookieContainer; - public readonly ServicePoint? ServicePoint; + public readonly ServicePoint ServicePoint; public readonly TimeSpan ContinueTimeout; public HttpClientParameters(HttpWebRequest webRequest, bool async) @@ -136,7 +136,7 @@ public HttpClientParameters(HttpWebRequest webRequest, bool async) ServerCertificateValidationCallback = webRequest.ServerCertificateValidationCallback ?? ServicePointManager.ServerCertificateValidationCallback; ClientCertificates = webRequest._clientCertificates; CookieContainer = webRequest._cookieContainer; - ServicePoint = webRequest._servicePoint; + ServicePoint = webRequest.ServicePoint; ContinueTimeout = TimeSpan.FromMilliseconds(webRequest.ContinueTimeout); } @@ -421,11 +421,7 @@ public string? Referer /// /// Sets the media type header /// - public string? MediaType - { - get; - set; - } + public string? MediaType { get; set; } /// /// @@ -682,7 +678,6 @@ public static int DefaultMaximumResponseHeadersLength } } - // NOP public static int DefaultMaximumErrorResponseLength { get; set; } = -1; private static RequestCachePolicy? _defaultCachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache); @@ -1621,12 +1616,11 @@ private static HttpClient CreateHttpClient(HttpClientParameters parameters, Http handler.UseCookies = false; } - if (parameters.ServicePoint is { } servicePoint) - { - handler.MaxConnectionsPerServer = servicePoint.ConnectionLimit; - handler.PooledConnectionIdleTimeout = servicePoint.MaxIdleTime == -1 ? Threading.Timeout.InfiniteTimeSpan : TimeSpan.FromMilliseconds(servicePoint.MaxIdleTime); - handler.PooledConnectionLifetime = servicePoint.ConnectionLeaseTimeout == -1 ? Threading.Timeout.InfiniteTimeSpan : TimeSpan.FromMilliseconds(servicePoint.ConnectionLeaseTimeout); - } + ServicePoint servicePoint = parameters.ServicePoint; + + handler.MaxConnectionsPerServer = servicePoint.ConnectionLimit; + handler.PooledConnectionIdleTimeout = servicePoint.MaxIdleTime == -1 ? Threading.Timeout.InfiniteTimeSpan : TimeSpan.FromMilliseconds(servicePoint.MaxIdleTime); + handler.PooledConnectionLifetime = servicePoint.ConnectionLeaseTimeout == -1 ? Threading.Timeout.InfiniteTimeSpan : TimeSpan.FromMilliseconds(servicePoint.ConnectionLeaseTimeout); Debug.Assert(handler.UseProxy); // Default of handler.UseProxy is true. Debug.Assert(handler.Proxy == null); // Default of handler.Proxy is null. @@ -1645,7 +1639,7 @@ private static HttpClient CreateHttpClient(HttpClientParameters parameters, Http { handler.UseProxy = false; } - else if (!object.ReferenceEquals(parameters.Proxy, WebRequest.GetSystemWebProxy())) + else if (!ReferenceEquals(parameters.Proxy, GetSystemWebProxy())) { handler.Proxy = parameters.Proxy; } @@ -1666,10 +1660,16 @@ private static HttpClient CreateHttpClient(HttpClientParameters parameters, Http handler.SslOptions.EnabledSslProtocols = (SslProtocols)parameters.SslProtocols; handler.SslOptions.CertificateRevocationCheckMode = parameters.CheckCertificateRevocationList ? X509RevocationMode.Online : X509RevocationMode.NoCheck; RemoteCertificateValidationCallback? rcvc = parameters.ServerCertificateValidationCallback; - if (rcvc != null) + handler.SslOptions.RemoteCertificateValidationCallback = (message, cert, chain, errors) => { - handler.SslOptions.RemoteCertificateValidationCallback = (message, cert, chain, errors) => rcvc(request!, cert, chain, errors); - } + servicePoint.Certificate = cert; + if (rcvc is not null) + { + return rcvc(request!, cert, chain, errors); + } + + return errors == SslPolicyErrors.None; + }; // Set up a ConnectCallback so that we can control Socket-specific settings, like ReadWriteTimeout => socket.Send/ReceiveTimeout. handler.ConnectCallback = async (context, cancellationToken) => @@ -1680,58 +1680,55 @@ private static HttpClient CreateHttpClient(HttpClientParameters parameters, Http { //Start dns resolve IPAddress[] addresses = await Dns.GetHostAddressesAsync(context.DnsEndPoint.Host, cancellationToken).ConfigureAwait(false); - if (parameters.ServicePoint is { } servicePoint) + if (servicePoint.ReceiveBufferSize != -1) { - if (servicePoint.ReceiveBufferSize != -1) - { - socket.ReceiveBufferSize = servicePoint.ReceiveBufferSize; - } + socket.ReceiveBufferSize = servicePoint.ReceiveBufferSize; + } - if (servicePoint.KeepAlive is { } keepAlive) - { - socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true); - socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime, keepAlive.Time); - socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval, keepAlive.Interval); - } + if (servicePoint.KeepAlive is { } keepAlive) + { + socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true); + socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime, keepAlive.Time); + socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval, keepAlive.Interval); + } - if (servicePoint.BindIPEndPointDelegate != null) + if (servicePoint.BindIPEndPointDelegate != null) + { + var bindHelper = () => { - var bindHelper = () => + const int MaxRetries = 100; + foreach (IPAddress address in addresses) { - const int MaxRetries = 100; - foreach (IPAddress address in addresses) + int retryCount = 0; + for (; retryCount < MaxRetries; retryCount++) { - int retryCount = 0; - for (; retryCount < MaxRetries; retryCount++) + IPEndPoint? endPoint = servicePoint.BindIPEndPointDelegate(servicePoint, new IPEndPoint(address, context.DnsEndPoint.Port), retryCount); + if (endPoint == null) // Get other address to try { - IPEndPoint? endPoint = servicePoint.BindIPEndPointDelegate(servicePoint, new IPEndPoint(address, context.DnsEndPoint.Port), retryCount); - if (endPoint == null) // Get other address to try - { - break; - } - try - { - socket.Bind(endPoint); - return; // Bind successful, exit loops. - } - catch - { - continue; - } + break; } - - if (retryCount >= MaxRetries) + try { - throw new OverflowException(); //TODO (aaksoy): Add SR for this. + socket.Bind(endPoint); + return; // Bind successful, exit loops. + } + catch + { + continue; } } - }; - bindHelper(); - } - socket.NoDelay = !servicePoint.UseNagleAlgorithm; + if (retryCount >= MaxRetries) + { + throw new OverflowException(); //TODO (aaksoy): Add SR for this. + } + } + }; + bindHelper(); } + socket.NoDelay = !servicePoint.UseNagleAlgorithm; + if (parameters.Async) { await socket.ConnectAsync(addresses, context.DnsEndPoint.Port, cancellationToken).ConfigureAwait(false); From 33abd52dda1877e72cee34bcbee82fa8203c012d Mon Sep 17 00:00:00 2001 From: Ahmet Ibrahim Aksoy Date: Mon, 12 Feb 2024 12:14:06 +0100 Subject: [PATCH 11/37] Change servicePoint to nullable parameter again and fix bind test --- .../src/System/Net/HttpWebRequest.cs | 93 ++++++++++--------- .../tests/HttpWebRequestTest.cs | 24 ++--- 2 files changed, 56 insertions(+), 61 deletions(-) diff --git a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs index 02068fc37e0ae3..cdc53c2a86d928 100644 --- a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs +++ b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs @@ -114,7 +114,7 @@ private sealed class HttpClientParameters public readonly RemoteCertificateValidationCallback? ServerCertificateValidationCallback; public readonly X509CertificateCollection? ClientCertificates; public readonly CookieContainer? CookieContainer; - public readonly ServicePoint ServicePoint; + public readonly ServicePoint? ServicePoint; public readonly TimeSpan ContinueTimeout; public HttpClientParameters(HttpWebRequest webRequest, bool async) @@ -136,7 +136,7 @@ public HttpClientParameters(HttpWebRequest webRequest, bool async) ServerCertificateValidationCallback = webRequest.ServerCertificateValidationCallback ?? ServicePointManager.ServerCertificateValidationCallback; ClientCertificates = webRequest._clientCertificates; CookieContainer = webRequest._cookieContainer; - ServicePoint = webRequest.ServicePoint; + ServicePoint = webRequest._servicePoint; ContinueTimeout = TimeSpan.FromMilliseconds(webRequest.ContinueTimeout); } @@ -1616,11 +1616,12 @@ private static HttpClient CreateHttpClient(HttpClientParameters parameters, Http handler.UseCookies = false; } - ServicePoint servicePoint = parameters.ServicePoint; - - handler.MaxConnectionsPerServer = servicePoint.ConnectionLimit; - handler.PooledConnectionIdleTimeout = servicePoint.MaxIdleTime == -1 ? Threading.Timeout.InfiniteTimeSpan : TimeSpan.FromMilliseconds(servicePoint.MaxIdleTime); - handler.PooledConnectionLifetime = servicePoint.ConnectionLeaseTimeout == -1 ? Threading.Timeout.InfiniteTimeSpan : TimeSpan.FromMilliseconds(servicePoint.ConnectionLeaseTimeout); + if (parameters.ServicePoint is { } servicePoint) + { + handler.MaxConnectionsPerServer = servicePoint.ConnectionLimit; + handler.PooledConnectionIdleTimeout = servicePoint.MaxIdleTime == -1 ? Threading.Timeout.InfiniteTimeSpan : TimeSpan.FromMilliseconds(servicePoint.MaxIdleTime); + handler.PooledConnectionLifetime = servicePoint.ConnectionLeaseTimeout == -1 ? Threading.Timeout.InfiniteTimeSpan : TimeSpan.FromMilliseconds(servicePoint.ConnectionLeaseTimeout); + } Debug.Assert(handler.UseProxy); // Default of handler.UseProxy is true. Debug.Assert(handler.Proxy == null); // Default of handler.Proxy is null. @@ -1662,7 +1663,10 @@ private static HttpClient CreateHttpClient(HttpClientParameters parameters, Http RemoteCertificateValidationCallback? rcvc = parameters.ServerCertificateValidationCallback; handler.SslOptions.RemoteCertificateValidationCallback = (message, cert, chain, errors) => { - servicePoint.Certificate = cert; + if (parameters.ServicePoint is { } servicePoint) + { + servicePoint.Certificate = cert; + } if (rcvc is not null) { return rcvc(request!, cert, chain, errors); @@ -1680,55 +1684,58 @@ private static HttpClient CreateHttpClient(HttpClientParameters parameters, Http { //Start dns resolve IPAddress[] addresses = await Dns.GetHostAddressesAsync(context.DnsEndPoint.Host, cancellationToken).ConfigureAwait(false); - if (servicePoint.ReceiveBufferSize != -1) + if (parameters.ServicePoint is { } servicePoint) { - socket.ReceiveBufferSize = servicePoint.ReceiveBufferSize; - } + if (servicePoint.ReceiveBufferSize != -1) + { + socket.ReceiveBufferSize = servicePoint.ReceiveBufferSize; + } - if (servicePoint.KeepAlive is { } keepAlive) - { - socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true); - socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime, keepAlive.Time); - socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval, keepAlive.Interval); - } + if (servicePoint.KeepAlive is { } keepAlive) + { + socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true); + socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime, keepAlive.Time); + socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval, keepAlive.Interval); + } - if (servicePoint.BindIPEndPointDelegate != null) - { - var bindHelper = () => + if (servicePoint.BindIPEndPointDelegate is not null) { - const int MaxRetries = 100; - foreach (IPAddress address in addresses) + var bindHelper = () => { - int retryCount = 0; - for (; retryCount < MaxRetries; retryCount++) + const int MaxRetries = 100; + foreach (IPAddress address in addresses) { - IPEndPoint? endPoint = servicePoint.BindIPEndPointDelegate(servicePoint, new IPEndPoint(address, context.DnsEndPoint.Port), retryCount); - if (endPoint == null) // Get other address to try + int retryCount = 0; + for (; retryCount < MaxRetries; retryCount++) { - break; + IPEndPoint? endPoint = servicePoint.BindIPEndPointDelegate(servicePoint, new IPEndPoint(address, context.DnsEndPoint.Port), retryCount); + if (endPoint is null) // Get other address to try + { + break; + } + try + { + socket.Bind(endPoint); + return; // Bind successful, exit loops. + } + catch + { + continue; + } } - try - { - socket.Bind(endPoint); - return; // Bind successful, exit loops. - } - catch + + if (retryCount >= MaxRetries) { - continue; + throw new OverflowException(); //TODO (aaksoy): Add SR for this. } } + }; + bindHelper(); + } - if (retryCount >= MaxRetries) - { - throw new OverflowException(); //TODO (aaksoy): Add SR for this. - } - } - }; - bindHelper(); + socket.NoDelay = !servicePoint.UseNagleAlgorithm; } - socket.NoDelay = !servicePoint.UseNagleAlgorithm; - if (parameters.Async) { await socket.ConnectAsync(addresses, context.DnsEndPoint.Port, cancellationToken).ConfigureAwait(false); diff --git a/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs b/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs index cc44ac55de39ad..aec3cebfc5dac8 100644 --- a/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs +++ b/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs @@ -258,7 +258,7 @@ public void Ctor_VerifyDefaults_Success(Uri remoteServer) Assert.Equal(64, HttpWebRequest.DefaultMaximumResponseHeadersLength); Assert.NotNull(HttpWebRequest.DefaultCachePolicy); Assert.Equal(RequestCacheLevel.BypassCache, HttpWebRequest.DefaultCachePolicy.Level); - Assert.Equal(0, HttpWebRequest.DefaultMaximumErrorResponseLength); + Assert.Equal(-1, HttpWebRequest.DefaultMaximumErrorResponseLength); Assert.NotNull(request.Proxy); Assert.Equal(remoteServer, request.RequestUri); Assert.True(request.SupportsCookieContainer); @@ -2238,23 +2238,11 @@ public async Task SendHttpRequest_BindIPEndPoint_Throws() { Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); socket.Bind(new IPEndPoint(IPAddress.Loopback, 0)); - await LoopbackServer.CreateClientAndServerAsync( - async (uri) => - { - HttpWebRequest request = WebRequest.CreateHttp(uri); - request.ServicePoint.BindIPEndPointDelegate = - (sp, ep, retryCount) => { return (IPEndPoint)socket.LocalEndPoint!; }; - var exception = await Assert.ThrowsAsync(() => request.GetResponseAsync()); - Assert.IsType(exception.InnerException?.InnerException); - }, - async (server) => - { - await server.AcceptConnectionAsync( - async (client) => - { - await client.SendResponseAsync(); - }); - }); + HttpWebRequest request = WebRequest.CreateHttp(Configuration.Http.RemoteEchoServer); + request.ServicePoint.BindIPEndPointDelegate = + (sp, ep, retryCount) => { return (IPEndPoint)socket.LocalEndPoint!; }; + var exception = await Assert.ThrowsAsync(() => request.GetResponseAsync()); + Assert.IsType(exception.InnerException?.InnerException); } private void RequestStreamCallback(IAsyncResult asynchronousResult) From 3033c4abfb4d13918a3d09fd14846d12ec63b3ed Mon Sep 17 00:00:00 2001 From: Ahmet Ibrahim Aksoy Date: Mon, 12 Feb 2024 13:42:03 +0100 Subject: [PATCH 12/37] Change static variable test to RemoteExecutor --- .../tests/HttpWebRequestTest.cs | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs b/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs index aec3cebfc5dac8..d7cad6cbc7ddfc 100644 --- a/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs +++ b/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs @@ -2167,10 +2167,12 @@ await server.AcceptConnectionAsync( ); } - [Fact] - public async Task SendHttpRequest_WhenDefaultMaximumErrorResponseLengthSet_Success() + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public void SendHttpRequest_WhenDefaultMaximumErrorResponseLengthSet_Success() { - await LoopbackServer.CreateClientAndServerAsync( + RemoteExecutor.Invoke(async () => + { + await LoopbackServer.CreateClientAndServerAsync( async (uri) => { HttpWebRequest request = WebRequest.CreateHttp(uri); @@ -2193,6 +2195,7 @@ await server.AcceptConnectionAsync( await client.SendResponseAsync(statusCode: HttpStatusCode.InternalServerError, content: new string('a', 10)); }); }); + }).Dispose(); } [Fact] @@ -2237,7 +2240,18 @@ await server.AcceptConnectionAsync( public async Task SendHttpRequest_BindIPEndPoint_Throws() { Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + var disableSocketOption = (SocketOptionName name) => + { + if (socket.GetSocketOption(SocketOptionLevel.Socket, name) is true) + { + socket.SetSocketOption(SocketOptionLevel.Socket, name, false); + } + }; + disableSocketOption(SocketOptionName.ReuseAddress); + disableSocketOption(SocketOptionName.ReuseUnicastPort); socket.Bind(new IPEndPoint(IPAddress.Loopback, 0)); + + // URI shouldn't matter because it should throw exception before connection open. HttpWebRequest request = WebRequest.CreateHttp(Configuration.Http.RemoteEchoServer); request.ServicePoint.BindIPEndPointDelegate = (sp, ep, retryCount) => { return (IPEndPoint)socket.LocalEndPoint!; }; From 2ee83dece9373d3f1828565a83a53457c5979966 Mon Sep 17 00:00:00 2001 From: Ahmet Ibrahim Aksoy Date: Mon, 12 Feb 2024 15:40:20 +0100 Subject: [PATCH 13/37] Fix bind throw test for linux and add async to remote executor --- .../System.Net.Requests/tests/HttpWebRequestTest.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs b/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs index d7cad6cbc7ddfc..b289604b2becfc 100644 --- a/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs +++ b/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs @@ -2170,14 +2170,16 @@ await server.AcceptConnectionAsync( [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] public void SendHttpRequest_WhenDefaultMaximumErrorResponseLengthSet_Success() { - RemoteExecutor.Invoke(async () => + RemoteExecutor.Invoke(async (async) => { await LoopbackServer.CreateClientAndServerAsync( async (uri) => { HttpWebRequest request = WebRequest.CreateHttp(uri); HttpWebRequest.DefaultMaximumErrorResponseLength = 5; - var exception = await Assert.ThrowsAsync(() => request.GetResponseAsync()); + var exception = bool.Parse(async) ? + await Assert.ThrowsAsync(() => request.GetResponseAsync()) : + Assert.Throws(() => request.GetResponse()); using (var responseStream = exception.Response.GetResponseStream()) { Assert.Equal(5, responseStream.Length); @@ -2195,7 +2197,7 @@ await server.AcceptConnectionAsync( await client.SendResponseAsync(statusCode: HttpStatusCode.InternalServerError, content: new string('a', 10)); }); }); - }).Dispose(); + }, (this is HttpWebRequestTest_Async).ToString()).Dispose(); } [Fact] @@ -2248,7 +2250,6 @@ public async Task SendHttpRequest_BindIPEndPoint_Throws() } }; disableSocketOption(SocketOptionName.ReuseAddress); - disableSocketOption(SocketOptionName.ReuseUnicastPort); socket.Bind(new IPEndPoint(IPAddress.Loopback, 0)); // URI shouldn't matter because it should throw exception before connection open. From 3be453290ef46c5a911831c6b6598a1cb80ea5c8 Mon Sep 17 00:00:00 2001 From: Ahmet Ibrahim Aksoy Date: Thu, 15 Feb 2024 10:17:23 +0100 Subject: [PATCH 14/37] Update src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs Co-authored-by: Miha Zupan --- .../System.Net.Requests/src/System/Net/HttpWebRequest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs index cdc53c2a86d928..d237de11c2dfb1 100644 --- a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs +++ b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs @@ -1619,8 +1619,8 @@ private static HttpClient CreateHttpClient(HttpClientParameters parameters, Http if (parameters.ServicePoint is { } servicePoint) { handler.MaxConnectionsPerServer = servicePoint.ConnectionLimit; - handler.PooledConnectionIdleTimeout = servicePoint.MaxIdleTime == -1 ? Threading.Timeout.InfiniteTimeSpan : TimeSpan.FromMilliseconds(servicePoint.MaxIdleTime); - handler.PooledConnectionLifetime = servicePoint.ConnectionLeaseTimeout == -1 ? Threading.Timeout.InfiniteTimeSpan : TimeSpan.FromMilliseconds(servicePoint.ConnectionLeaseTimeout); + handler.PooledConnectionIdleTimeout = TimeSpan.FromMilliseconds(servicePoint.MaxIdleTime); + handler.PooledConnectionLifetime = TimeSpan.FromMilliseconds(servicePoint.ConnectionLeaseTimeout); } Debug.Assert(handler.UseProxy); // Default of handler.UseProxy is true. From f1ea11780391cd1d111895e4f0a3f7ab1cd34971 Mon Sep 17 00:00:00 2001 From: Ahmet Ibrahim Aksoy Date: Thu, 15 Feb 2024 15:22:46 +0100 Subject: [PATCH 15/37] Review feedback --- .../src/System/Net/HttpWebRequest.cs | 60 ++++++++++--------- .../System/Net/ServicePoint/ServicePoint.cs | 9 --- 2 files changed, 32 insertions(+), 37 deletions(-) diff --git a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs index cdc53c2a86d928..c64d4aebae24bc 100644 --- a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs +++ b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs @@ -1683,7 +1683,9 @@ private static HttpClient CreateHttpClient(HttpClientParameters parameters, Http try { //Start dns resolve - IPAddress[] addresses = await Dns.GetHostAddressesAsync(context.DnsEndPoint.Host, cancellationToken).ConfigureAwait(false); + IPAddress[] addresses = parameters.Async ? + await Dns.GetHostAddressesAsync(context.DnsEndPoint.Host, cancellationToken).ConfigureAwait(false) : + Dns.GetHostAddresses(context.DnsEndPoint.Host); if (parameters.ServicePoint is { } servicePoint) { if (servicePoint.ReceiveBufferSize != -1) @@ -1698,44 +1700,46 @@ private static HttpClient CreateHttpClient(HttpClientParameters parameters, Http socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval, keepAlive.Interval); } - if (servicePoint.BindIPEndPointDelegate is not null) + BindHelper(servicePoint, addresses, socket, context.DnsEndPoint.Port); + static void BindHelper(ServicePoint servicePoint, IPAddress[] addresses, Socket socket, int port) { - var bindHelper = () => + if (servicePoint.BindIPEndPointDelegate is null) { - const int MaxRetries = 100; - foreach (IPAddress address in addresses) + return; + } + + const int MaxRetries = 100; + foreach (IPAddress address in addresses) + { + int retryCount = 0; + for (; retryCount < MaxRetries; retryCount++) { - int retryCount = 0; - for (; retryCount < MaxRetries; retryCount++) + IPEndPoint? endPoint = servicePoint.BindIPEndPointDelegate(servicePoint, new IPEndPoint(address, port), retryCount); + if (endPoint is null) // Get other address to try { - IPEndPoint? endPoint = servicePoint.BindIPEndPointDelegate(servicePoint, new IPEndPoint(address, context.DnsEndPoint.Port), retryCount); - if (endPoint is null) // Get other address to try - { - break; - } - try - { - socket.Bind(endPoint); - return; // Bind successful, exit loops. - } - catch - { - continue; - } + break; } - - if (retryCount >= MaxRetries) + try + { + socket.Bind(endPoint); + return; // Bind successful, exit loops. + } + catch { - throw new OverflowException(); //TODO (aaksoy): Add SR for this. + continue; } } - }; - bindHelper(); - } - socket.NoDelay = !servicePoint.UseNagleAlgorithm; + if (retryCount >= MaxRetries) + { + throw new OverflowException(); //TODO (aaksoy): Add SR for this. + } + } + } } + socket.NoDelay = !(parameters.ServicePoint?.UseNagleAlgorithm) ?? true; + if (parameters.Async) { await socket.ConnectAsync(addresses, context.DnsEndPoint.Port, cancellationToken).ConfigureAwait(false); diff --git a/src/libraries/System.Net.Requests/src/System/Net/ServicePoint/ServicePoint.cs b/src/libraries/System.Net.Requests/src/System/Net/ServicePoint/ServicePoint.cs index 6533a086131e47..d48ac0760992c0 100644 --- a/src/libraries/System.Net.Requests/src/System/Net/ServicePoint/ServicePoint.cs +++ b/src/libraries/System.Net.Requests/src/System/Net/ServicePoint/ServicePoint.cs @@ -16,15 +16,6 @@ public class ServicePoint internal TcpKeepAlive? KeepAlive { get; set; } - internal int CurrentAddressIndex { get; set; } - - internal DateTime LastDnsResolve { get; set; } - - internal bool NeedDnsResolve => LastDnsResolve - .CompareTo(DateTime.Now.AddMilliseconds(-ServicePointManager.DnsRefreshTimeout)) >= 0; - - internal IPAddress[]? CachedAddresses { get; set; } - internal ServicePoint(Uri address) { Debug.Assert(address != null); From 8d28f97c47dd8ebc69933fdc1e0c537bfce1ada1 Mon Sep 17 00:00:00 2001 From: Ahmet Ibrahim Aksoy Date: Fri, 16 Feb 2024 13:16:53 +0100 Subject: [PATCH 16/37] Skip throw bind on Linux --- src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs b/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs index b289604b2becfc..ea91efa7adcce7 100644 --- a/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs +++ b/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs @@ -2239,6 +2239,9 @@ await server.AcceptConnectionAsync( } [Fact] + [SkipOnPlatform(TestPlatforms.Linux, + "Socket.Bind() auto-enables SO_REUSEADDR on Unix to allow Bind() during TIME_WAIT to " + + "emulate Windows behavior, see SystemNative_Bind() in 'pal_networking.c'.")] public async Task SendHttpRequest_BindIPEndPoint_Throws() { Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); From 970bbb132cbc2fc08bbf129667e1b2d2efeaadec Mon Sep 17 00:00:00 2001 From: Ahmet Ibrahim Aksoy Date: Sun, 18 Feb 2024 08:34:43 +0100 Subject: [PATCH 17/37] Add disable reuseaddr --- .../src/System/Net/HttpWebRequest.cs | 4 ++++ .../System.Net.Requests/tests/HttpWebRequestTest.cs | 13 ++----------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs index 186fdcd672a83b..49f9cd5d331416 100644 --- a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs +++ b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs @@ -1722,6 +1722,10 @@ static void BindHelper(ServicePoint servicePoint, IPAddress[] addresses, Socket try { socket.Bind(endPoint); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, false); + } return; // Bind successful, exit loops. } catch diff --git a/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs b/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs index ea91efa7adcce7..1d5da69005c5a7 100644 --- a/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs +++ b/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs @@ -2180,6 +2180,7 @@ await LoopbackServer.CreateClientAndServerAsync( var exception = bool.Parse(async) ? await Assert.ThrowsAsync(() => request.GetResponseAsync()) : Assert.Throws(() => request.GetResponse()); + Assert.NotNull(exception.Response); using (var responseStream = exception.Response.GetResponseStream()) { Assert.Equal(5, responseStream.Length); @@ -2239,21 +2240,11 @@ await server.AcceptConnectionAsync( } [Fact] - [SkipOnPlatform(TestPlatforms.Linux, - "Socket.Bind() auto-enables SO_REUSEADDR on Unix to allow Bind() during TIME_WAIT to " + - "emulate Windows behavior, see SystemNative_Bind() in 'pal_networking.c'.")] public async Task SendHttpRequest_BindIPEndPoint_Throws() { Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - var disableSocketOption = (SocketOptionName name) => - { - if (socket.GetSocketOption(SocketOptionLevel.Socket, name) is true) - { - socket.SetSocketOption(SocketOptionLevel.Socket, name, false); - } - }; - disableSocketOption(SocketOptionName.ReuseAddress); socket.Bind(new IPEndPoint(IPAddress.Loopback, 0)); + socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, false); // URI shouldn't matter because it should throw exception before connection open. HttpWebRequest request = WebRequest.CreateHttp(Configuration.Http.RemoteEchoServer); From d9ef66d13af28973fa467b24f21a501ccbcb74a0 Mon Sep 17 00:00:00 2001 From: Ahmet Ibrahim Aksoy Date: Sun, 18 Feb 2024 08:49:31 +0100 Subject: [PATCH 18/37] Revert "Add disable reuseaddr" This reverts commit 970bbb132cbc2fc08bbf129667e1b2d2efeaadec. --- .../src/System/Net/HttpWebRequest.cs | 4 ---- .../System.Net.Requests/tests/HttpWebRequestTest.cs | 13 +++++++++++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs index 49f9cd5d331416..186fdcd672a83b 100644 --- a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs +++ b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs @@ -1722,10 +1722,6 @@ static void BindHelper(ServicePoint servicePoint, IPAddress[] addresses, Socket try { socket.Bind(endPoint); - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, false); - } return; // Bind successful, exit loops. } catch diff --git a/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs b/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs index 1d5da69005c5a7..ea91efa7adcce7 100644 --- a/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs +++ b/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs @@ -2180,7 +2180,6 @@ await LoopbackServer.CreateClientAndServerAsync( var exception = bool.Parse(async) ? await Assert.ThrowsAsync(() => request.GetResponseAsync()) : Assert.Throws(() => request.GetResponse()); - Assert.NotNull(exception.Response); using (var responseStream = exception.Response.GetResponseStream()) { Assert.Equal(5, responseStream.Length); @@ -2240,11 +2239,21 @@ await server.AcceptConnectionAsync( } [Fact] + [SkipOnPlatform(TestPlatforms.Linux, + "Socket.Bind() auto-enables SO_REUSEADDR on Unix to allow Bind() during TIME_WAIT to " + + "emulate Windows behavior, see SystemNative_Bind() in 'pal_networking.c'.")] public async Task SendHttpRequest_BindIPEndPoint_Throws() { Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + var disableSocketOption = (SocketOptionName name) => + { + if (socket.GetSocketOption(SocketOptionLevel.Socket, name) is true) + { + socket.SetSocketOption(SocketOptionLevel.Socket, name, false); + } + }; + disableSocketOption(SocketOptionName.ReuseAddress); socket.Bind(new IPEndPoint(IPAddress.Loopback, 0)); - socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, false); // URI shouldn't matter because it should throw exception before connection open. HttpWebRequest request = WebRequest.CreateHttp(Configuration.Http.RemoteEchoServer); From 67655be3801b168f3e949037222a3484ce58ecb2 Mon Sep 17 00:00:00 2001 From: Ahmet Ibrahim Aksoy Date: Sun, 18 Feb 2024 08:50:22 +0100 Subject: [PATCH 19/37] some changes on tests --- .../System.Net.Requests/tests/HttpWebRequestTest.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs b/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs index ea91efa7adcce7..4f63b0246ab54f 100644 --- a/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs +++ b/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs @@ -2180,6 +2180,7 @@ await LoopbackServer.CreateClientAndServerAsync( var exception = bool.Parse(async) ? await Assert.ThrowsAsync(() => request.GetResponseAsync()) : Assert.Throws(() => request.GetResponse()); + Assert.NotNull(exception.Response); using (var responseStream = exception.Response.GetResponseStream()) { Assert.Equal(5, responseStream.Length); @@ -2245,14 +2246,6 @@ await server.AcceptConnectionAsync( public async Task SendHttpRequest_BindIPEndPoint_Throws() { Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - var disableSocketOption = (SocketOptionName name) => - { - if (socket.GetSocketOption(SocketOptionLevel.Socket, name) is true) - { - socket.SetSocketOption(SocketOptionLevel.Socket, name, false); - } - }; - disableSocketOption(SocketOptionName.ReuseAddress); socket.Bind(new IPEndPoint(IPAddress.Loopback, 0)); // URI shouldn't matter because it should throw exception before connection open. From 86f156c9fcf6b8c64c68e243f05623a78899f644 Mon Sep 17 00:00:00 2001 From: Ahmet Ibrahim Aksoy Date: Tue, 20 Feb 2024 13:42:49 +0100 Subject: [PATCH 20/37] Refactor GetResponseStream method to use TruncatedReadStream --- .../src/System/Net/HttpWebResponse.cs | 74 +++++++++++++------ 1 file changed, 51 insertions(+), 23 deletions(-) diff --git a/src/libraries/System.Net.Requests/src/System/Net/HttpWebResponse.cs b/src/libraries/System.Net.Requests/src/System/Net/HttpWebResponse.cs index d0de388457c7cb..841c4ccafa0c36 100644 --- a/src/libraries/System.Net.Requests/src/System/Net/HttpWebResponse.cs +++ b/src/libraries/System.Net.Requests/src/System/Net/HttpWebResponse.cs @@ -8,6 +8,8 @@ using System.Net.Http; using System.Runtime.Serialization; using System.Text; +using System.Threading; +using System.Threading.Tasks; namespace System.Net { @@ -344,29 +346,7 @@ public override Stream GetResponseStream() return contentStream; } - MemoryStream memoryStream = new MemoryStream(); - byte[] buffer = new byte[1024]; - int readLength = 0; - - while (readLength < maxErrorResponseLength) - { - int len = contentStream.Read(buffer, 0, Math.Min(maxErrorResponseLength - readLength, buffer.Length)); - if (len == 0) - { - break; - } - memoryStream.Write(buffer, 0, len); - readLength += len; - } - memoryStream.Seek(0, SeekOrigin.Begin); - try - { - return memoryStream; - } - finally - { - contentStream.Dispose(); - } + return new TruncatedReadStream(contentStream, maxErrorResponseLength); } return Stream.Null; @@ -400,5 +380,53 @@ private void CheckDisposed() } private static string GetHeaderValueAsString(IEnumerable values) => string.Join(", ", values); + + internal sealed class TruncatedReadStream(Stream innerStream, int maxSize) : Stream + { + public override bool CanRead => true; + + public override bool CanSeek => false; + + public override bool CanWrite => false; + + public override long Length => throw new NotSupportedException(); + + public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } + + public override void Flush() => innerStream.Flush(); + public override Task FlushAsync(CancellationToken cancellationToken) => base.FlushAsync(cancellationToken); + public override int Read(byte[] buffer, int offset, int count) + { + return Read(new Span(buffer, offset, count)); + } + public override int Read(Span buffer) + { + int readBytes = innerStream.Read(buffer.Slice(0, Math.Min(buffer.Length, maxSize))); + maxSize -= readBytes; + return readBytes; + } + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + return ReadAsync(new Memory(buffer, offset, count), cancellationToken).AsTask(); + } + public override async ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) + { + int readBytes = await innerStream.ReadAsync(buffer.Slice(0, Math.Min(buffer.Length, maxSize)), cancellationToken) + .ConfigureAwait(false); + maxSize -= readBytes; + return readBytes; + } + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + public override void SetLength(long value) => throw new NotSupportedException(); + public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); + public override ValueTask DisposeAsync() => base.DisposeAsync(); + protected override void Dispose(bool disposing) + { + if (disposing) + { + innerStream.Dispose(); + } + } + } } } From ca6b379002b8abb051948f4120a8375d8c391cb9 Mon Sep 17 00:00:00 2001 From: Ahmet Ibrahim Aksoy Date: Tue, 20 Feb 2024 13:42:55 +0100 Subject: [PATCH 21/37] Fix tests --- .../tests/HttpWebRequestTest.cs | 61 +++++++++++-------- 1 file changed, 37 insertions(+), 24 deletions(-) diff --git a/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs b/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs index 4f63b0246ab54f..fa521b69604711 100644 --- a/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs +++ b/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs @@ -2089,7 +2089,7 @@ await LoopbackServer.CreateClientAndServerAsync( request.ContinueTimeout = 30000; Stream requestStream = await request.GetRequestStreamAsync(); requestStream.Write("aaaa\r\n\r\n"u8); - await request.GetResponseAsync(); + await GetResponseAsync(request); }, async (server) => { @@ -2118,7 +2118,7 @@ await LoopbackServer.CreateClientAndServerAsync( request.ContinueTimeout = continueTimeout; Stream requestStream = await request.GetRequestStreamAsync(); requestStream.Write("aaaa\r\n\r\n"u8); - await request.GetResponseAsync(); + await GetResponseAsync(request); }, async (server) => { @@ -2144,7 +2144,7 @@ await LoopbackServer.CreateClientAndServerAsync( HttpWebRequest request = WebRequest.CreateHttp(uri); request.Method = "POST"; request.ServicePoint.Expect100Continue = expect100Continue; - await request.GetResponseAsync(); + await GetResponseAsync(request); }, async (server) => { @@ -2167,23 +2167,18 @@ await server.AcceptConnectionAsync( ); } - [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] - public void SendHttpRequest_WhenDefaultMaximumErrorResponseLengthSet_Success() + [Fact] + public async Task SendHttpRequest_WhenDefaultMaximumErrorResponseLengthSet_Success() { - RemoteExecutor.Invoke(async (async) => - { - await LoopbackServer.CreateClientAndServerAsync( + await LoopbackServer.CreateClientAndServerAsync( async (uri) => { HttpWebRequest request = WebRequest.CreateHttp(uri); HttpWebRequest.DefaultMaximumErrorResponseLength = 5; - var exception = bool.Parse(async) ? - await Assert.ThrowsAsync(() => request.GetResponseAsync()) : - Assert.Throws(() => request.GetResponse()); + var exception = await Assert.ThrowsAsync(() => GetResponseAsync(request)); Assert.NotNull(exception.Response); using (var responseStream = exception.Response.GetResponseStream()) { - Assert.Equal(5, responseStream.Length); var buffer = new byte[10]; int readLen = responseStream.Read(buffer, 0, buffer.Length); Assert.Equal(5, readLen); @@ -2198,7 +2193,6 @@ await server.AcceptConnectionAsync( await client.SendResponseAsync(statusCode: HttpStatusCode.InternalServerError, content: new string('a', 10)); }); }); - }, (this is HttpWebRequestTest_Async).ToString()).Dispose(); } [Fact] @@ -2216,16 +2210,18 @@ public void HttpWebRequest_SetProtocolVersion_Success() [Fact] public async Task SendHttpRequest_BindIPEndPoint_Success() { + TaskCompletionSource tcs = new TaskCompletionSource(); await LoopbackServer.CreateClientAndServerAsync( async (uri) => { HttpWebRequest request = WebRequest.CreateHttp(uri); request.ServicePoint.BindIPEndPointDelegate = (sp, ep, retryCount) => { return new IPEndPoint(IPAddress.Loopback, 27277); }; - using (var response = (HttpWebResponse)await request.GetResponseAsync()) + using (var response = (HttpWebResponse)await GetResponseAsync(request)) { Assert.Equal(HttpStatusCode.OK, response.StatusCode); } + tcs.SetResult(); }, async (server) => { @@ -2235,25 +2231,42 @@ await server.AcceptConnectionAsync( var ipEp = (IPEndPoint)client.Socket.RemoteEndPoint; Assert.Equal(27277, ipEp.Port); await client.SendResponseAsync(); + await tcs.Task; }); }); } [Fact] - [SkipOnPlatform(TestPlatforms.Linux, - "Socket.Bind() auto-enables SO_REUSEADDR on Unix to allow Bind() during TIME_WAIT to " + - "emulate Windows behavior, see SystemNative_Bind() in 'pal_networking.c'.")] public async Task SendHttpRequest_BindIPEndPoint_Throws() { - Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + Socket socket = new Socket(SocketType.Stream, ProtocolType.Tcp); socket.Bind(new IPEndPoint(IPAddress.Loopback, 0)); + ValueTask? clientSocket = null; + CancellationTokenSource cts = new CancellationTokenSource(); + if (PlatformDetection.IsLinux) + { + socket.Listen(); + clientSocket = socket.AcceptAsync(cts.Token); + } - // URI shouldn't matter because it should throw exception before connection open. - HttpWebRequest request = WebRequest.CreateHttp(Configuration.Http.RemoteEchoServer); - request.ServicePoint.BindIPEndPointDelegate = - (sp, ep, retryCount) => { return (IPEndPoint)socket.LocalEndPoint!; }; - var exception = await Assert.ThrowsAsync(() => request.GetResponseAsync()); - Assert.IsType(exception.InnerException?.InnerException); + try + { + // URI shouldn't matter because it should throw exception before connection open. + HttpWebRequest request = WebRequest.CreateHttp(Configuration.Http.RemoteEchoServer); + request.ServicePoint.BindIPEndPointDelegate = + (sp, ep, retryCount) => { return (IPEndPoint)socket.LocalEndPoint!; }; + var exception = await Assert.ThrowsAsync(() => GetResponseAsync(request)); + Assert.IsType(exception.InnerException?.InnerException); + } + finally + { + if (clientSocket is not null) + { + await cts.CancelAsync(); + } + socket.Dispose(); + cts.Dispose(); + } } private void RequestStreamCallback(IAsyncResult asynchronousResult) From 89040f6ba74d6cb43dec137edf032b0c0c6185f3 Mon Sep 17 00:00:00 2001 From: Ahmet Ibrahim Aksoy Date: Tue, 20 Feb 2024 13:49:01 +0100 Subject: [PATCH 22/37] Convert static property test to RemoteExecutor --- .../System.Net.Requests/tests/HttpWebRequestTest.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs b/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs index fa521b69604711..21603abb71d9ad 100644 --- a/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs +++ b/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs @@ -2167,15 +2167,19 @@ await server.AcceptConnectionAsync( ); } - [Fact] - public async Task SendHttpRequest_WhenDefaultMaximumErrorResponseLengthSet_Success() + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public void SendHttpRequest_WhenDefaultMaximumErrorResponseLengthSet_Success() { - await LoopbackServer.CreateClientAndServerAsync( + RemoteExecutor.Invoke(async (async) => + { + await LoopbackServer.CreateClientAndServerAsync( async (uri) => { HttpWebRequest request = WebRequest.CreateHttp(uri); HttpWebRequest.DefaultMaximumErrorResponseLength = 5; - var exception = await Assert.ThrowsAsync(() => GetResponseAsync(request)); + var exception = bool.Parse(async) ? + await Assert.ThrowsAsync(() => request.GetResponseAsync()) : + Assert.Throws(() => request.GetResponse()); Assert.NotNull(exception.Response); using (var responseStream = exception.Response.GetResponseStream()) { @@ -2193,6 +2197,7 @@ await server.AcceptConnectionAsync( await client.SendResponseAsync(statusCode: HttpStatusCode.InternalServerError, content: new string('a', 10)); }); }); + }, (this is HttpWebRequestTest_Async).ToString()).Dispose(); } [Fact] From 66f7abd8e2e34c4d2f53cdeb7093a4bb5f94d482 Mon Sep 17 00:00:00 2001 From: Ahmet Ibrahim Aksoy Date: Tue, 20 Feb 2024 13:52:12 +0100 Subject: [PATCH 23/37] Delete unused NameResolution project from Requests csproj --- src/libraries/System.Net.Requests/src/System.Net.Requests.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libraries/System.Net.Requests/src/System.Net.Requests.csproj b/src/libraries/System.Net.Requests/src/System.Net.Requests.csproj index b53b7272ea4171..397622b4806a03 100644 --- a/src/libraries/System.Net.Requests/src/System.Net.Requests.csproj +++ b/src/libraries/System.Net.Requests/src/System.Net.Requests.csproj @@ -110,7 +110,6 @@ - From 50a0f2f256e49de7a2d8d240be3a636f6b9cfc55 Mon Sep 17 00:00:00 2001 From: Ahmet Ibrahim Aksoy Date: Tue, 20 Feb 2024 15:31:40 +0100 Subject: [PATCH 24/37] Revert "Delete unused NameResolution project from Requests csproj" This reverts commit 66f7abd8e2e34c4d2f53cdeb7093a4bb5f94d482. --- src/libraries/System.Net.Requests/src/System.Net.Requests.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libraries/System.Net.Requests/src/System.Net.Requests.csproj b/src/libraries/System.Net.Requests/src/System.Net.Requests.csproj index 397622b4806a03..b53b7272ea4171 100644 --- a/src/libraries/System.Net.Requests/src/System.Net.Requests.csproj +++ b/src/libraries/System.Net.Requests/src/System.Net.Requests.csproj @@ -110,6 +110,7 @@ + From f9cb55fa6056f17e973fcfae936dee16fd23946a Mon Sep 17 00:00:00 2001 From: Ahmet Ibrahim Aksoy Date: Tue, 20 Feb 2024 17:00:09 +0100 Subject: [PATCH 25/37] Fix socket shutdown --- src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs b/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs index 21603abb71d9ad..d704a8ba38df98 100644 --- a/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs +++ b/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs @@ -2172,6 +2172,7 @@ public void SendHttpRequest_WhenDefaultMaximumErrorResponseLengthSet_Success() { RemoteExecutor.Invoke(async (async) => { + TaskCompletionSource tcs = new TaskCompletionSource(); await LoopbackServer.CreateClientAndServerAsync( async (uri) => { @@ -2188,6 +2189,7 @@ await Assert.ThrowsAsync(() => request.GetResponseAsync()) : Assert.Equal(5, readLen); Assert.Equal(new string('a', 5), Encoding.UTF8.GetString(buffer[0..readLen])); } + tcs.SetResult(); }, async (server) => { @@ -2195,6 +2197,7 @@ await server.AcceptConnectionAsync( async (client) => { await client.SendResponseAsync(statusCode: HttpStatusCode.InternalServerError, content: new string('a', 10)); + await tcs.Task; }); }); }, (this is HttpWebRequestTest_Async).ToString()).Dispose(); From 8c3f0c44f6cddad7d7da512bf3e68b7c386c3b2f Mon Sep 17 00:00:00 2001 From: Ahmet Ibrahim Aksoy Date: Tue, 20 Feb 2024 21:24:31 +0100 Subject: [PATCH 26/37] simple changes on test --- src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs b/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs index d704a8ba38df98..7186311dd5a921 100644 --- a/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs +++ b/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs @@ -2181,6 +2181,7 @@ await LoopbackServer.CreateClientAndServerAsync( var exception = bool.Parse(async) ? await Assert.ThrowsAsync(() => request.GetResponseAsync()) : Assert.Throws(() => request.GetResponse()); + tcs.SetResult(); Assert.NotNull(exception.Response); using (var responseStream = exception.Response.GetResponseStream()) { @@ -2189,14 +2190,13 @@ await Assert.ThrowsAsync(() => request.GetResponseAsync()) : Assert.Equal(5, readLen); Assert.Equal(new string('a', 5), Encoding.UTF8.GetString(buffer[0..readLen])); } - tcs.SetResult(); }, async (server) => { await server.AcceptConnectionAsync( async (client) => { - await client.SendResponseAsync(statusCode: HttpStatusCode.InternalServerError, content: new string('a', 10)); + await client.SendResponseAsync(statusCode: HttpStatusCode.BadRequest, content: new string('a', 10)); await tcs.Task; }); }); From a5c136a5c843964beb1f40c5d5d2446231eef7f5 Mon Sep 17 00:00:00 2001 From: Ahmet Ibrahim Aksoy Date: Thu, 22 Feb 2024 21:46:29 +0100 Subject: [PATCH 27/37] Change sync call inside RemoteExecutor --- .../System.Net.Requests/tests/HttpWebRequestTest.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs b/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs index 7186311dd5a921..c151372b9c47d7 100644 --- a/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs +++ b/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs @@ -2178,9 +2178,8 @@ await LoopbackServer.CreateClientAndServerAsync( { HttpWebRequest request = WebRequest.CreateHttp(uri); HttpWebRequest.DefaultMaximumErrorResponseLength = 5; - var exception = bool.Parse(async) ? - await Assert.ThrowsAsync(() => request.GetResponseAsync()) : - Assert.Throws(() => request.GetResponse()); + var exception = + await Assert.ThrowsAsync(() => bool.Parse(async) ? request.GetResponseAsync() : Task.Run(() => request.GetResponse())); tcs.SetResult(); Assert.NotNull(exception.Response); using (var responseStream = exception.Response.GetResponseStream()) From 1d9708d06125c0daa91a82988a736d5c045b37af Mon Sep 17 00:00:00 2001 From: Ahmet Ibrahim Aksoy Date: Tue, 27 Feb 2024 08:22:20 +0100 Subject: [PATCH 28/37] Update src/libraries/System.Net.Requests/src/System/Net/HttpWebResponse.cs Co-authored-by: Anton Firszov --- .../System.Net.Requests/src/System/Net/HttpWebResponse.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Net.Requests/src/System/Net/HttpWebResponse.cs b/src/libraries/System.Net.Requests/src/System/Net/HttpWebResponse.cs index 841c4ccafa0c36..4579fa5c52a217 100644 --- a/src/libraries/System.Net.Requests/src/System/Net/HttpWebResponse.cs +++ b/src/libraries/System.Net.Requests/src/System/Net/HttpWebResponse.cs @@ -394,7 +394,7 @@ internal sealed class TruncatedReadStream(Stream innerStream, int maxSize) : Str public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } public override void Flush() => innerStream.Flush(); - public override Task FlushAsync(CancellationToken cancellationToken) => base.FlushAsync(cancellationToken); + public override Task FlushAsync(CancellationToken cancellationToken) => innerStream.FlushAsync(cancellationToken); public override int Read(byte[] buffer, int offset, int count) { return Read(new Span(buffer, offset, count)); From 06f8d95988907c33a68063d1ad1ea298e5e1a758 Mon Sep 17 00:00:00 2001 From: Ahmet Ibrahim Aksoy Date: Wed, 28 Feb 2024 09:22:25 +0100 Subject: [PATCH 29/37] Apply suggestions from code review Co-authored-by: Miha Zupan --- .../src/System/Net/HttpWebRequest.cs | 4 +++- .../src/System/Net/HttpWebResponse.cs | 12 ++++++++---- .../System.Net.Requests/tests/HttpWebRequestTest.cs | 9 ++++----- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs index 186fdcd672a83b..df4675803f16b7 100644 --- a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs +++ b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs @@ -1667,6 +1667,7 @@ private static HttpClient CreateHttpClient(HttpClientParameters parameters, Http { servicePoint.Certificate = cert; } + if (rcvc is not null) { return rcvc(request!, cert, chain, errors); @@ -1682,10 +1683,10 @@ private static HttpClient CreateHttpClient(HttpClientParameters parameters, Http try { - //Start dns resolve IPAddress[] addresses = parameters.Async ? await Dns.GetHostAddressesAsync(context.DnsEndPoint.Host, cancellationToken).ConfigureAwait(false) : Dns.GetHostAddresses(context.DnsEndPoint.Host); + if (parameters.ServicePoint is { } servicePoint) { if (servicePoint.ReceiveBufferSize != -1) @@ -1719,6 +1720,7 @@ static void BindHelper(ServicePoint servicePoint, IPAddress[] addresses, Socket { break; } + try { socket.Bind(endPoint); diff --git a/src/libraries/System.Net.Requests/src/System/Net/HttpWebResponse.cs b/src/libraries/System.Net.Requests/src/System/Net/HttpWebResponse.cs index 4579fa5c52a217..52152b7bd5aba8 100644 --- a/src/libraries/System.Net.Requests/src/System/Net/HttpWebResponse.cs +++ b/src/libraries/System.Net.Requests/src/System/Net/HttpWebResponse.cs @@ -384,31 +384,32 @@ private void CheckDisposed() internal sealed class TruncatedReadStream(Stream innerStream, int maxSize) : Stream { public override bool CanRead => true; - public override bool CanSeek => false; - public override bool CanWrite => false; public override long Length => throw new NotSupportedException(); - public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } public override void Flush() => innerStream.Flush(); public override Task FlushAsync(CancellationToken cancellationToken) => innerStream.FlushAsync(cancellationToken); + public override int Read(byte[] buffer, int offset, int count) { return Read(new Span(buffer, offset, count)); } + public override int Read(Span buffer) { int readBytes = innerStream.Read(buffer.Slice(0, Math.Min(buffer.Length, maxSize))); maxSize -= readBytes; return readBytes; } + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { return ReadAsync(new Memory(buffer, offset, count), cancellationToken).AsTask(); } + public override async ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) { int readBytes = await innerStream.ReadAsync(buffer.Slice(0, Math.Min(buffer.Length, maxSize)), cancellationToken) @@ -416,10 +417,13 @@ public override async ValueTask ReadAsync(Memory buffer, Cancellation maxSize -= readBytes; return readBytes; } + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); public override void SetLength(long value) => throw new NotSupportedException(); public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); - public override ValueTask DisposeAsync() => base.DisposeAsync(); + + public override ValueTask DisposeAsync() => innerStream.DisposeAsync(); + protected override void Dispose(bool disposing) { if (disposing) diff --git a/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs b/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs index c151372b9c47d7..e69f78274d6f14 100644 --- a/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs +++ b/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs @@ -2188,6 +2188,7 @@ await LoopbackServer.CreateClientAndServerAsync( int readLen = responseStream.Read(buffer, 0, buffer.Length); Assert.Equal(5, readLen); Assert.Equal(new string('a', 5), Encoding.UTF8.GetString(buffer[0..readLen])); + Assert.Equal(0, responseStream.Read(buffer)); } }, async (server) => @@ -2199,7 +2200,7 @@ await server.AcceptConnectionAsync( await tcs.Task; }); }); - }, (this is HttpWebRequestTest_Async).ToString()).Dispose(); + }, IsAsync.ToString()).Dispose(); } [Fact] @@ -2222,8 +2223,7 @@ await LoopbackServer.CreateClientAndServerAsync( async (uri) => { HttpWebRequest request = WebRequest.CreateHttp(uri); - request.ServicePoint.BindIPEndPointDelegate = - (sp, ep, retryCount) => { return new IPEndPoint(IPAddress.Loopback, 27277); }; + request.ServicePoint.BindIPEndPointDelegate = (_, _, _) => new IPEndPoint(IPAddress.Loopback, 27277); using (var response = (HttpWebResponse)await GetResponseAsync(request)) { Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -2260,8 +2260,7 @@ public async Task SendHttpRequest_BindIPEndPoint_Throws() { // URI shouldn't matter because it should throw exception before connection open. HttpWebRequest request = WebRequest.CreateHttp(Configuration.Http.RemoteEchoServer); - request.ServicePoint.BindIPEndPointDelegate = - (sp, ep, retryCount) => { return (IPEndPoint)socket.LocalEndPoint!; }; + request.ServicePoint.BindIPEndPointDelegate = (_, _, _) => (IPEndPoint)socket.LocalEndPoint!; var exception = await Assert.ThrowsAsync(() => GetResponseAsync(request)); Assert.IsType(exception.InnerException?.InnerException); } From 05af2e0afca8c2a223dbf1fac907f97bf323d618 Mon Sep 17 00:00:00 2001 From: Ahmet Ibrahim Aksoy Date: Wed, 28 Feb 2024 10:24:31 +0100 Subject: [PATCH 30/37] Review feedback --- .../src/Resources/Strings.resx | 3 + .../src/System/Net/HttpWebRequest.cs | 16 ++- .../Net/ServicePoint/ServicePointManager.cs | 3 +- .../tests/HttpWebRequestTest.cs | 108 ++++++++++-------- 4 files changed, 77 insertions(+), 53 deletions(-) diff --git a/src/libraries/System.Net.Requests/src/Resources/Strings.resx b/src/libraries/System.Net.Requests/src/Resources/Strings.resx index b33f2a02440356..5408f19947b9b7 100644 --- a/src/libraries/System.Net.Requests/src/Resources/Strings.resx +++ b/src/libraries/System.Net.Requests/src/Resources/Strings.resx @@ -264,4 +264,7 @@ The ServicePointManager does not support proxies with the {0} scheme. + + Reached maximum number of BindIPEndPointDelegate retries + diff --git a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs index df4675803f16b7..9b8e000ae73533 100644 --- a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs +++ b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs @@ -42,6 +42,7 @@ public class HttpWebRequest : WebRequest, ISerializable private Task? _sendRequestTask; private static int _defaultMaxResponseHeadersLength = HttpHandlerDefaults.DefaultMaxResponseHeadersLength; + private static int _defaultMaximumErrorResponseLength = -1; private int _beginGetRequestStreamCalled; private int _beginGetResponseCalled; @@ -674,11 +675,22 @@ public static int DefaultMaximumResponseHeadersLength } set { + ArgumentOutOfRangeException.ThrowIfLessThan(value, 0); _defaultMaxResponseHeadersLength = value; } } - public static int DefaultMaximumErrorResponseLength { get; set; } = -1; + public static int DefaultMaximumErrorResponseLength { + get + { + return _defaultMaximumErrorResponseLength; + } + set + { + ArgumentOutOfRangeException.ThrowIfLessThan(value, -1); + _defaultMaximumErrorResponseLength = value; + } + } private static RequestCachePolicy? _defaultCachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache); private static bool _isDefaultCachePolicySet; @@ -1734,7 +1746,7 @@ static void BindHelper(ServicePoint servicePoint, IPAddress[] addresses, Socket if (retryCount >= MaxRetries) { - throw new OverflowException(); //TODO (aaksoy): Add SR for this. + throw new OverflowException(SR.net_maximumbindretries); } } } diff --git a/src/libraries/System.Net.Requests/src/System/Net/ServicePoint/ServicePointManager.cs b/src/libraries/System.Net.Requests/src/System/Net/ServicePoint/ServicePointManager.cs index a5c34fe33bf735..bbf20b3e808b62 100644 --- a/src/libraries/System.Net.Requests/src/System/Net/ServicePoint/ServicePointManager.cs +++ b/src/libraries/System.Net.Requests/src/System/Net/ServicePoint/ServicePointManager.cs @@ -156,7 +156,8 @@ public static ServicePoint FindServicePoint(Uri address, IWebProxy? proxy) IdleSince = DateTime.Now, Expect100Continue = Expect100Continue, UseNagleAlgorithm = UseNagleAlgorithm, - KeepAlive = KeepAlive + KeepAlive = KeepAlive, + MaxIdleTime = MaxServicePointIdleTime }; s_servicePointTable[tableKey] = new WeakReference(sp); diff --git a/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs b/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs index e69f78274d6f14..a0720c6d416da7 100644 --- a/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs +++ b/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs @@ -2194,9 +2194,9 @@ await LoopbackServer.CreateClientAndServerAsync( async (server) => { await server.AcceptConnectionAsync( - async (client) => + async connection => { - await client.SendResponseAsync(statusCode: HttpStatusCode.BadRequest, content: new string('a', 10)); + await connection.SendResponseAsync(statusCode: HttpStatusCode.BadRequest, content: new string('a', 10)); await tcs.Task; }); }); @@ -2215,64 +2215,72 @@ public void HttpWebRequest_SetProtocolVersion_Success() Assert.Equal(HttpVersion.Version11, request.ServicePoint.ProtocolVersion); } - [Fact] - public async Task SendHttpRequest_BindIPEndPoint_Success() + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public void SendHttpRequest_BindIPEndPoint_Success() { - TaskCompletionSource tcs = new TaskCompletionSource(); - await LoopbackServer.CreateClientAndServerAsync( - async (uri) => - { - HttpWebRequest request = WebRequest.CreateHttp(uri); - request.ServicePoint.BindIPEndPointDelegate = (_, _, _) => new IPEndPoint(IPAddress.Loopback, 27277); - using (var response = (HttpWebResponse)await GetResponseAsync(request)) + RemoteExecutor.Invoke(async (async) => + { + TaskCompletionSource tcs = new TaskCompletionSource(); + await LoopbackServer.CreateClientAndServerAsync( + async (uri) => { - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - } - tcs.SetResult(); - }, - async (server) => - { - await server.AcceptConnectionAsync( - async (client) => + HttpWebRequest request = WebRequest.CreateHttp(uri); + request.ServicePoint.BindIPEndPointDelegate = (_, _, _) => new IPEndPoint(IPAddress.Loopback, 27277); + var responseTask = bool.Parse(async) ? request.GetResponseAsync() : Task.Run(() => request.GetResponse()); + using (var response = (HttpWebResponse)await responseTask) { - var ipEp = (IPEndPoint)client.Socket.RemoteEndPoint; - Assert.Equal(27277, ipEp.Port); - await client.SendResponseAsync(); - await tcs.Task; - }); - }); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + tcs.SetResult(); + }, + async (server) => + { + await server.AcceptConnectionAsync( + async connection => + { + var ipEp = (IPEndPoint)connection.Socket.RemoteEndPoint; + Assert.Equal(27277, ipEp.Port); + await connection.SendResponseAsync(); + await tcs.Task; + }); + }); + }, IsAsync.ToString()); } - [Fact] - public async Task SendHttpRequest_BindIPEndPoint_Throws() + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public void SendHttpRequest_BindIPEndPoint_Throws() { - Socket socket = new Socket(SocketType.Stream, ProtocolType.Tcp); - socket.Bind(new IPEndPoint(IPAddress.Loopback, 0)); - ValueTask? clientSocket = null; - CancellationTokenSource cts = new CancellationTokenSource(); - if (PlatformDetection.IsLinux) + RemoteExecutor.Invoke(async (async) => { - socket.Listen(); - clientSocket = socket.AcceptAsync(cts.Token); - } + Socket socket = new Socket(SocketType.Stream, ProtocolType.Tcp); + socket.Bind(new IPEndPoint(IPAddress.Loopback, 0)); + ValueTask? clientSocket = null; + CancellationTokenSource cts = new CancellationTokenSource(); + if (PlatformDetection.IsLinux) + { + socket.Listen(); + clientSocket = socket.AcceptAsync(cts.Token); + } - try - { - // URI shouldn't matter because it should throw exception before connection open. - HttpWebRequest request = WebRequest.CreateHttp(Configuration.Http.RemoteEchoServer); - request.ServicePoint.BindIPEndPointDelegate = (_, _, _) => (IPEndPoint)socket.LocalEndPoint!; - var exception = await Assert.ThrowsAsync(() => GetResponseAsync(request)); - Assert.IsType(exception.InnerException?.InnerException); - } - finally - { - if (clientSocket is not null) + try { - await cts.CancelAsync(); + // URI shouldn't matter because it should throw exception before connection open. + HttpWebRequest request = WebRequest.CreateHttp(Configuration.Http.RemoteEchoServer); + request.ServicePoint.BindIPEndPointDelegate = (_, _, _) => (IPEndPoint)socket.LocalEndPoint!; + var exception = await Assert.ThrowsAsync(() => + bool.Parse(async) ? request.GetResponseAsync() : Task.Run(() => request.GetResponse())); + Assert.IsType(exception.InnerException?.InnerException); } - socket.Dispose(); - cts.Dispose(); - } + finally + { + if (clientSocket is not null) + { + await cts.CancelAsync(); + } + socket.Dispose(); + cts.Dispose(); + } + }, IsAsync.ToString()); } private void RequestStreamCallback(IAsyncResult asynchronousResult) From 47d7f276188aa7a6315930d73737e02a9a84fe3c Mon Sep 17 00:00:00 2001 From: Ahmet Ibrahim Aksoy Date: Wed, 28 Feb 2024 14:13:59 +0100 Subject: [PATCH 31/37] Change exception handling on remote executor test --- .../tests/HttpWebRequestTest.cs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs b/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs index a0720c6d416da7..baa8392fdcfb11 100644 --- a/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs +++ b/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs @@ -2267,9 +2267,22 @@ public void SendHttpRequest_BindIPEndPoint_Throws() // URI shouldn't matter because it should throw exception before connection open. HttpWebRequest request = WebRequest.CreateHttp(Configuration.Http.RemoteEchoServer); request.ServicePoint.BindIPEndPointDelegate = (_, _, _) => (IPEndPoint)socket.LocalEndPoint!; - var exception = await Assert.ThrowsAsync(() => - bool.Parse(async) ? request.GetResponseAsync() : Task.Run(() => request.GetResponse())); - Assert.IsType(exception.InnerException?.InnerException); + try + { + if (bool.Parse(async)) + { + await request.GetResponseAsync(); + } + else + { + request.GetResponse(); + } + Assert.Fail("Should throw OverflowException"); + } + catch (Exception ex) + { + Assert.IsType(ex.InnerException?.InnerException); + } } finally { From 7aa734fa63da4ab7b6e47ee3a3aecd04559ce4bc Mon Sep 17 00:00:00 2001 From: Ahmet Ibrahim Aksoy Date: Wed, 28 Feb 2024 15:07:32 +0100 Subject: [PATCH 32/37] Change connection logic --- .../src/System/Net/HttpWebRequest.cs | 88 ++++++++++++------- 1 file changed, 54 insertions(+), 34 deletions(-) diff --git a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs index 9b8e000ae73533..f0f8d096cfa9ef 100644 --- a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs +++ b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs @@ -1713,60 +1713,80 @@ await Dns.GetHostAddressesAsync(context.DnsEndPoint.Host, cancellationToken).Con socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval, keepAlive.Interval); } - BindHelper(servicePoint, addresses, socket, context.DnsEndPoint.Port); - static void BindHelper(ServicePoint servicePoint, IPAddress[] addresses, Socket socket, int port) - { - if (servicePoint.BindIPEndPointDelegate is null) - { - return; - } + socket.NoDelay = !servicePoint.UseNagleAlgorithm; - const int MaxRetries = 100; + if (servicePoint.BindIPEndPointDelegate is not null) + { foreach (IPAddress address in addresses) { - int retryCount = 0; - for (; retryCount < MaxRetries; retryCount++) + IPEndPoint remoteEp = new IPEndPoint(address, context.DnsEndPoint.Port); + BindHelper(servicePoint, remoteEp, socket); + if (parameters.Async) + { + await socket.ConnectAsync(remoteEp, cancellationToken).ConfigureAwait(false); + } + else { - IPEndPoint? endPoint = servicePoint.BindIPEndPointDelegate(servicePoint, new IPEndPoint(address, port), retryCount); - if (endPoint is null) // Get other address to try + using (cancellationToken.UnsafeRegister(s => ((Socket)s!).Dispose(), socket)) { - break; + socket.Connect(remoteEp); } - try - { - socket.Bind(endPoint); - return; // Bind successful, exit loops. - } - catch - { - continue; - } + // Throw in case cancellation caused the socket to be disposed after the Connect completed + cancellationToken.ThrowIfCancellationRequested(); + } + } + } + static void BindHelper(ServicePoint servicePoint, IPEndPoint remoteEp, Socket socket) + { + const int MaxRetries = 100; + int retryCount = 0; + for (; retryCount < MaxRetries; retryCount++) + { + IPEndPoint? endPoint = servicePoint.BindIPEndPointDelegate!(servicePoint, remoteEp, retryCount); + if (endPoint is null) // Get other address to try + { + return; } - if (retryCount >= MaxRetries) + try + { + socket.Bind(endPoint); + return; + } + catch { - throw new OverflowException(SR.net_maximumbindretries); + continue; } } + + if (retryCount >= MaxRetries) + { + throw new OverflowException(SR.net_maximumbindretries); + } } } - - socket.NoDelay = !(parameters.ServicePoint?.UseNagleAlgorithm) ?? true; - - if (parameters.Async) + else { - await socket.ConnectAsync(addresses, context.DnsEndPoint.Port, cancellationToken).ConfigureAwait(false); + socket.NoDelay = true; } - else + + if (!socket.Connected) { - using (cancellationToken.UnsafeRegister(s => ((Socket)s!).Dispose(), socket)) + if (parameters.Async) { - socket.Connect(addresses, context.DnsEndPoint.Port); + await socket.ConnectAsync(addresses, context.DnsEndPoint.Port, cancellationToken).ConfigureAwait(false); } + else + { + using (cancellationToken.UnsafeRegister(s => ((Socket)s!).Dispose(), socket)) + { + socket.Connect(addresses, context.DnsEndPoint.Port); + } - // Throw in case cancellation caused the socket to be disposed after the Connect completed - cancellationToken.ThrowIfCancellationRequested(); + // Throw in case cancellation caused the socket to be disposed after the Connect completed + cancellationToken.ThrowIfCancellationRequested(); + } } if (parameters.ReadWriteTimeout > 0) // default is 5 minutes, so this is generally going to be true From 657a33493792d1e37c8d0b39ccbc63376d10211e Mon Sep 17 00:00:00 2001 From: Ahmet Ibrahim Aksoy Date: Wed, 28 Feb 2024 16:39:14 +0100 Subject: [PATCH 33/37] Revert "Change exception handling on remote executor test" This reverts commit 47d7f276188aa7a6315930d73737e02a9a84fe3c. --- .../tests/HttpWebRequestTest.cs | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs b/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs index baa8392fdcfb11..a0720c6d416da7 100644 --- a/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs +++ b/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs @@ -2267,22 +2267,9 @@ public void SendHttpRequest_BindIPEndPoint_Throws() // URI shouldn't matter because it should throw exception before connection open. HttpWebRequest request = WebRequest.CreateHttp(Configuration.Http.RemoteEchoServer); request.ServicePoint.BindIPEndPointDelegate = (_, _, _) => (IPEndPoint)socket.LocalEndPoint!; - try - { - if (bool.Parse(async)) - { - await request.GetResponseAsync(); - } - else - { - request.GetResponse(); - } - Assert.Fail("Should throw OverflowException"); - } - catch (Exception ex) - { - Assert.IsType(ex.InnerException?.InnerException); - } + var exception = await Assert.ThrowsAsync(() => + bool.Parse(async) ? request.GetResponseAsync() : Task.Run(() => request.GetResponse())); + Assert.IsType(exception.InnerException?.InnerException); } finally { From 06f1324570ca015151359963872d3c7daa3499da Mon Sep 17 00:00:00 2001 From: Ahmet Ibrahim Aksoy Date: Wed, 28 Feb 2024 16:40:26 +0100 Subject: [PATCH 34/37] Add forgotten disposal --- src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs b/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs index a0720c6d416da7..73c66872a7e56a 100644 --- a/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs +++ b/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs @@ -2244,7 +2244,7 @@ await server.AcceptConnectionAsync( await tcs.Task; }); }); - }, IsAsync.ToString()); + }, IsAsync.ToString()).Dispose(); } [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] @@ -2280,7 +2280,7 @@ public void SendHttpRequest_BindIPEndPoint_Throws() socket.Dispose(); cts.Dispose(); } - }, IsAsync.ToString()); + }, IsAsync.ToString()).Dispose(); } private void RequestStreamCallback(IAsyncResult asynchronousResult) From 520ba041dff09df8fbf5751af229834d66c27183 Mon Sep 17 00:00:00 2001 From: Ahmet Ibrahim Aksoy Date: Wed, 28 Feb 2024 16:49:54 +0100 Subject: [PATCH 35/37] Revert "Change connection logic" This reverts commit 7aa734fa63da4ab7b6e47ee3a3aecd04559ce4bc. --- .../src/System/Net/HttpWebRequest.cs | 88 +++++++------------ 1 file changed, 34 insertions(+), 54 deletions(-) diff --git a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs index f0f8d096cfa9ef..9b8e000ae73533 100644 --- a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs +++ b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs @@ -1713,80 +1713,60 @@ await Dns.GetHostAddressesAsync(context.DnsEndPoint.Host, cancellationToken).Con socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval, keepAlive.Interval); } - socket.NoDelay = !servicePoint.UseNagleAlgorithm; - - if (servicePoint.BindIPEndPointDelegate is not null) + BindHelper(servicePoint, addresses, socket, context.DnsEndPoint.Port); + static void BindHelper(ServicePoint servicePoint, IPAddress[] addresses, Socket socket, int port) { + if (servicePoint.BindIPEndPointDelegate is null) + { + return; + } + + const int MaxRetries = 100; foreach (IPAddress address in addresses) { - IPEndPoint remoteEp = new IPEndPoint(address, context.DnsEndPoint.Port); - BindHelper(servicePoint, remoteEp, socket); - if (parameters.Async) - { - await socket.ConnectAsync(remoteEp, cancellationToken).ConfigureAwait(false); - } - else + int retryCount = 0; + for (; retryCount < MaxRetries; retryCount++) { - using (cancellationToken.UnsafeRegister(s => ((Socket)s!).Dispose(), socket)) + IPEndPoint? endPoint = servicePoint.BindIPEndPointDelegate(servicePoint, new IPEndPoint(address, port), retryCount); + if (endPoint is null) // Get other address to try { - socket.Connect(remoteEp); + break; } - // Throw in case cancellation caused the socket to be disposed after the Connect completed - cancellationToken.ThrowIfCancellationRequested(); - } - } - } - static void BindHelper(ServicePoint servicePoint, IPEndPoint remoteEp, Socket socket) - { - const int MaxRetries = 100; - int retryCount = 0; - for (; retryCount < MaxRetries; retryCount++) - { - IPEndPoint? endPoint = servicePoint.BindIPEndPointDelegate!(servicePoint, remoteEp, retryCount); - if (endPoint is null) // Get other address to try - { - return; + try + { + socket.Bind(endPoint); + return; // Bind successful, exit loops. + } + catch + { + continue; + } } - try - { - socket.Bind(endPoint); - return; - } - catch + if (retryCount >= MaxRetries) { - continue; + throw new OverflowException(SR.net_maximumbindretries); } } - - if (retryCount >= MaxRetries) - { - throw new OverflowException(SR.net_maximumbindretries); - } } } - else + + socket.NoDelay = !(parameters.ServicePoint?.UseNagleAlgorithm) ?? true; + + if (parameters.Async) { - socket.NoDelay = true; + await socket.ConnectAsync(addresses, context.DnsEndPoint.Port, cancellationToken).ConfigureAwait(false); } - - if (!socket.Connected) + else { - if (parameters.Async) + using (cancellationToken.UnsafeRegister(s => ((Socket)s!).Dispose(), socket)) { - await socket.ConnectAsync(addresses, context.DnsEndPoint.Port, cancellationToken).ConfigureAwait(false); + socket.Connect(addresses, context.DnsEndPoint.Port); } - else - { - using (cancellationToken.UnsafeRegister(s => ((Socket)s!).Dispose(), socket)) - { - socket.Connect(addresses, context.DnsEndPoint.Port); - } - // Throw in case cancellation caused the socket to be disposed after the Connect completed - cancellationToken.ThrowIfCancellationRequested(); - } + // Throw in case cancellation caused the socket to be disposed after the Connect completed + cancellationToken.ThrowIfCancellationRequested(); } if (parameters.ReadWriteTimeout > 0) // default is 5 minutes, so this is generally going to be true From a9368bf7ca2ad37b30bc046dde9d58f3796ab8f5 Mon Sep 17 00:00:00 2001 From: Ahmet Ibrahim Aksoy Date: Wed, 28 Feb 2024 16:53:01 +0100 Subject: [PATCH 36/37] Review feedback --- .../System.Net.Requests/src/System/Net/HttpWebRequest.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs index 9b8e000ae73533..eb9c9f3d4612c2 100644 --- a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs +++ b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs @@ -1713,8 +1713,8 @@ await Dns.GetHostAddressesAsync(context.DnsEndPoint.Host, cancellationToken).Con socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval, keepAlive.Interval); } - BindHelper(servicePoint, addresses, socket, context.DnsEndPoint.Port); - static void BindHelper(ServicePoint servicePoint, IPAddress[] addresses, Socket socket, int port) + BindHelper(servicePoint, ref addresses, socket, context.DnsEndPoint.Port); + static void BindHelper(ServicePoint servicePoint, ref IPAddress[] addresses, Socket socket, int port) { if (servicePoint.BindIPEndPointDelegate is null) { @@ -1736,6 +1736,7 @@ static void BindHelper(ServicePoint servicePoint, IPAddress[] addresses, Socket try { socket.Bind(endPoint); + addresses = [address]; return; // Bind successful, exit loops. } catch From f8cdea3b926bec2f471e16556f16b964c797640a Mon Sep 17 00:00:00 2001 From: Ahmet Ibrahim Aksoy Date: Fri, 1 Mar 2024 12:00:16 +0300 Subject: [PATCH 37/37] Apply suggestions from code review Co-authored-by: Miha Zupan --- src/libraries/System.Net.Requests/src/Resources/Strings.resx | 2 +- .../System.Net.Requests/src/System/Net/HttpWebRequest.cs | 3 ++- .../System.Net.Requests/src/System/Net/HttpWebResponse.cs | 3 +-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Net.Requests/src/Resources/Strings.resx b/src/libraries/System.Net.Requests/src/Resources/Strings.resx index 5408f19947b9b7..4c0a7a45c146ff 100644 --- a/src/libraries/System.Net.Requests/src/Resources/Strings.resx +++ b/src/libraries/System.Net.Requests/src/Resources/Strings.resx @@ -265,6 +265,6 @@ The ServicePointManager does not support proxies with the {0} scheme. - Reached maximum number of BindIPEndPointDelegate retries + Reached the maximum number of BindIPEndPointDelegate retries. diff --git a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs index eb9c9f3d4612c2..44c60115913654 100644 --- a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs +++ b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs @@ -680,7 +680,8 @@ public static int DefaultMaximumResponseHeadersLength } } - public static int DefaultMaximumErrorResponseLength { + public static int DefaultMaximumErrorResponseLength + { get { return _defaultMaximumErrorResponseLength; diff --git a/src/libraries/System.Net.Requests/src/System/Net/HttpWebResponse.cs b/src/libraries/System.Net.Requests/src/System/Net/HttpWebResponse.cs index 52152b7bd5aba8..7b0e9b90681fc9 100644 --- a/src/libraries/System.Net.Requests/src/System/Net/HttpWebResponse.cs +++ b/src/libraries/System.Net.Requests/src/System/Net/HttpWebResponse.cs @@ -390,8 +390,7 @@ internal sealed class TruncatedReadStream(Stream innerStream, int maxSize) : Str public override long Length => throw new NotSupportedException(); public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } - public override void Flush() => innerStream.Flush(); - public override Task FlushAsync(CancellationToken cancellationToken) => innerStream.FlushAsync(cancellationToken); + public override void Flush() => throw new NotSupportedException(); public override int Read(byte[] buffer, int offset, int count) {