diff --git a/src/libraries/System.Net.Requests/src/Resources/Strings.resx b/src/libraries/System.Net.Requests/src/Resources/Strings.resx
index b33f2a02440356..4c0a7a45c146ff 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 the maximum number of BindIPEndPointDelegate retries.
+
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 2a54bbb4d8d50d..44c60115913654 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;
@@ -41,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;
@@ -420,11 +422,7 @@ public string? Referer
///
/// Sets the media type header
///
- public string? MediaType
- {
- get;
- set;
- }
+ public string? MediaType { get; set; }
///
///
@@ -677,14 +675,22 @@ public static int DefaultMaximumResponseHeadersLength
}
set
{
+ ArgumentOutOfRangeException.ThrowIfLessThan(value, 0);
_defaultMaxResponseHeadersLength = value;
}
}
- // NOP
public static int DefaultMaximumErrorResponseLength
{
- get; set;
+ get
+ {
+ return _defaultMaximumErrorResponseLength;
+ }
+ set
+ {
+ ArgumentOutOfRangeException.ThrowIfLessThan(value, -1);
+ _defaultMaximumErrorResponseLength = value;
+ }
}
private static RequestCachePolicy? _defaultCachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache);
@@ -806,10 +812,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 +1629,13 @@ private static HttpClient CreateHttpClient(HttpClientParameters parameters, Http
handler.UseCookies = false;
}
+ if (parameters.ServicePoint is { } servicePoint)
+ {
+ handler.MaxConnectionsPerServer = servicePoint.ConnectionLimit;
+ handler.PooledConnectionIdleTimeout = TimeSpan.FromMilliseconds(servicePoint.MaxIdleTime);
+ handler.PooledConnectionLifetime = 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.
@@ -1638,7 +1653,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;
}
@@ -1659,10 +1674,20 @@ 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);
- }
+ if (parameters.ServicePoint is { } servicePoint)
+ {
+ 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) =>
@@ -1671,6 +1696,10 @@ private static HttpClient CreateHttpClient(HttpClientParameters parameters, Http
try
{
+ 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)
@@ -1684,19 +1713,58 @@ private static HttpClient CreateHttpClient(HttpClientParameters parameters, Http
socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime, keepAlive.Time);
socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval, keepAlive.Interval);
}
+
+ 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)
+ {
+ return;
+ }
+
+ const int MaxRetries = 100;
+ foreach (IPAddress address in addresses)
+ {
+ 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
+ {
+ break;
+ }
+
+ try
+ {
+ socket.Bind(endPoint);
+ addresses = [address];
+ return; // Bind successful, exit loops.
+ }
+ catch
+ {
+ continue;
+ }
+ }
+
+ if (retryCount >= MaxRetries)
+ {
+ throw new OverflowException(SR.net_maximumbindretries);
+ }
+ }
+ }
}
- socket.NoDelay = true;
+ socket.NoDelay = !(parameters.ServicePoint?.UseNagleAlgorithm) ?? true;
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..7b0e9b90681fc9 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
{
@@ -337,7 +339,14 @@ public override Stream GetResponseStream()
CheckDisposed();
if (_httpResponseMessage.Content != null)
{
- return _httpResponseMessage.Content.ReadAsStream();
+ Stream contentStream = _httpResponseMessage.Content.ReadAsStream();
+ int maxErrorResponseLength = HttpWebRequest.DefaultMaximumErrorResponseLength;
+ if (maxErrorResponseLength < 0 || StatusCode < HttpStatusCode.BadRequest)
+ {
+ return contentStream;
+ }
+
+ return new TruncatedReadStream(contentStream, maxErrorResponseLength);
}
return Stream.Null;
@@ -371,5 +380,56 @@ 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() => throw new NotSupportedException();
+
+ 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() => innerStream.DisposeAsync();
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ innerStream.Dispose();
+ }
+ }
+ }
}
}
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..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
@@ -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;
@@ -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 45563ccc3dd0ed..73c66872a7e56a 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);
@@ -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,6 +2167,122 @@ await server.AcceptConnectionAsync(
);
}
+ [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
+ public void SendHttpRequest_WhenDefaultMaximumErrorResponseLengthSet_Success()
+ {
+ RemoteExecutor.Invoke(async (async) =>
+ {
+ TaskCompletionSource tcs = new TaskCompletionSource();
+ await LoopbackServer.CreateClientAndServerAsync(
+ async (uri) =>
+ {
+ HttpWebRequest request = WebRequest.CreateHttp(uri);
+ HttpWebRequest.DefaultMaximumErrorResponseLength = 5;
+ 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())
+ {
+ 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]));
+ Assert.Equal(0, responseStream.Read(buffer));
+ }
+ },
+ async (server) =>
+ {
+ await server.AcceptConnectionAsync(
+ async connection =>
+ {
+ await connection.SendResponseAsync(statusCode: HttpStatusCode.BadRequest, content: new string('a', 10));
+ await tcs.Task;
+ });
+ });
+ }, IsAsync.ToString()).Dispose();
+ }
+
+ [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);
+ }
+
+ [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
+ public void SendHttpRequest_BindIPEndPoint_Success()
+ {
+ RemoteExecutor.Invoke(async (async) =>
+ {
+ TaskCompletionSource tcs = new TaskCompletionSource();
+ await LoopbackServer.CreateClientAndServerAsync(
+ async (uri) =>
+ {
+ 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)
+ {
+ 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()).Dispose();
+ }
+
+ [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
+ public void SendHttpRequest_BindIPEndPoint_Throws()
+ {
+ RemoteExecutor.Invoke(async (async) =>
+ {
+ 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(() =>
+ bool.Parse(async) ? request.GetResponseAsync() : Task.Run(() => request.GetResponse()));
+ Assert.IsType(exception.InnerException?.InnerException);
+ }
+ finally
+ {
+ if (clientSocket is not null)
+ {
+ await cts.CancelAsync();
+ }
+ socket.Dispose();
+ cts.Dispose();
+ }
+ }, IsAsync.ToString()).Dispose();
+ }
+
private void RequestStreamCallback(IAsyncResult asynchronousResult)
{
RequestState state = (RequestState)asynchronousResult.AsyncState;
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();
}