Skip to content

Commit 4a89242

Browse files
liveansMihaZupanantonfirsov
authored
Implement more ServicePoint properties (#97537)
* Implement more ServicePoint properties * Implement more properties * Make implementation correct * Change UseNagleAlgorithm default to false * Update src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs Co-authored-by: Miha Zupan <[email protected]> * Review feedback * Start implement DnsRoundRobin * remove passing parameter over ctor for static prop * Change DefaultMaximumErrorResponseLength default value to -1 * Update ServicePoint parameter passing, implement Certificate on ServicePoint * Change servicePoint to nullable parameter again and fix bind test * Change static variable test to RemoteExecutor * Fix bind throw test for linux and add async to remote executor * Update src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs Co-authored-by: Miha Zupan <[email protected]> * Review feedback * Skip throw bind on Linux * Add disable reuseaddr * Revert "Add disable reuseaddr" This reverts commit 970bbb1. * some changes on tests * Refactor GetResponseStream method to use TruncatedReadStream * Fix tests * Convert static property test to RemoteExecutor * Delete unused NameResolution project from Requests csproj * Revert "Delete unused NameResolution project from Requests csproj" This reverts commit 66f7abd. * Fix socket shutdown * simple changes on test * Change sync call inside RemoteExecutor * Update src/libraries/System.Net.Requests/src/System/Net/HttpWebResponse.cs Co-authored-by: Anton Firszov <[email protected]> * Apply suggestions from code review Co-authored-by: Miha Zupan <[email protected]> * Review feedback * Change exception handling on remote executor test * Change connection logic * Revert "Change exception handling on remote executor test" This reverts commit 47d7f27. * Add forgotten disposal * Revert "Change connection logic" This reverts commit 7aa734f. * Review feedback * Apply suggestions from code review Co-authored-by: Miha Zupan <[email protected]> --------- Co-authored-by: Miha Zupan <[email protected]> Co-authored-by: Anton Firszov <[email protected]>
1 parent 1bebdb0 commit 4a89242

File tree

7 files changed

+272
-23
lines changed

7 files changed

+272
-23
lines changed

src/libraries/System.Net.Requests/src/Resources/Strings.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,4 +264,7 @@
264264
<data name="net_proxyschemenotsupported" xml:space="preserve">
265265
<value>The ServicePointManager does not support proxies with the {0} scheme.</value>
266266
</data>
267+
<data name="net_maximumbindretries" xml:space="preserve">
268+
<value>Reached the maximum number of BindIPEndPointDelegate retries.</value>
269+
</data>
267270
</root>

src/libraries/System.Net.Requests/src/System.Net.Requests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@
110110
<Reference Include="System.Diagnostics.Tracing" />
111111
<Reference Include="System.Memory" />
112112
<Reference Include="System.Net.Http" />
113+
<Reference Include="System.Net.NameResolution" />
113114
<Reference Include="System.Net.Primitives" />
114115
<Reference Include="System.Net.Security" />
115116
<Reference Include="System.Net.Sockets" />

src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs

Lines changed: 82 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Diagnostics.CodeAnalysis;
77
using System.Globalization;
88
using System.IO;
9+
using System.Net;
910
using System.Net.Cache;
1011
using System.Net.Http;
1112
using System.Net.Http.Headers;
@@ -41,6 +42,7 @@ public class HttpWebRequest : WebRequest, ISerializable
4142
private Task<HttpResponseMessage>? _sendRequestTask;
4243

4344
private static int _defaultMaxResponseHeadersLength = HttpHandlerDefaults.DefaultMaxResponseHeadersLength;
45+
private static int _defaultMaximumErrorResponseLength = -1;
4446

4547
private int _beginGetRequestStreamCalled;
4648
private int _beginGetResponseCalled;
@@ -420,11 +422,7 @@ public string? Referer
420422
/// <devdoc>
421423
/// <para>Sets the media type header</para>
422424
/// </devdoc>
423-
public string? MediaType
424-
{
425-
get;
426-
set;
427-
}
425+
public string? MediaType { get; set; }
428426

429427
/// <devdoc>
430428
/// <para>
@@ -677,14 +675,22 @@ public static int DefaultMaximumResponseHeadersLength
677675
}
678676
set
679677
{
678+
ArgumentOutOfRangeException.ThrowIfLessThan(value, 0);
680679
_defaultMaxResponseHeadersLength = value;
681680
}
682681
}
683682

684-
// NOP
685683
public static int DefaultMaximumErrorResponseLength
686684
{
687-
get; set;
685+
get
686+
{
687+
return _defaultMaximumErrorResponseLength;
688+
}
689+
set
690+
{
691+
ArgumentOutOfRangeException.ThrowIfLessThan(value, -1);
692+
_defaultMaximumErrorResponseLength = value;
693+
}
688694
}
689695

690696
private static RequestCachePolicy? _defaultCachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache);
@@ -806,10 +812,12 @@ public Version ProtocolVersion
806812
if (value.Equals(HttpVersion.Version11))
807813
{
808814
IsVersionHttp10 = false;
815+
ServicePoint.ProtocolVersion = HttpVersion.Version11;
809816
}
810817
else if (value.Equals(HttpVersion.Version10))
811818
{
812819
IsVersionHttp10 = true;
820+
ServicePoint.ProtocolVersion = HttpVersion.Version10;
813821
}
814822
else
815823
{
@@ -1621,6 +1629,13 @@ private static HttpClient CreateHttpClient(HttpClientParameters parameters, Http
16211629
handler.UseCookies = false;
16221630
}
16231631

1632+
if (parameters.ServicePoint is { } servicePoint)
1633+
{
1634+
handler.MaxConnectionsPerServer = servicePoint.ConnectionLimit;
1635+
handler.PooledConnectionIdleTimeout = TimeSpan.FromMilliseconds(servicePoint.MaxIdleTime);
1636+
handler.PooledConnectionLifetime = TimeSpan.FromMilliseconds(servicePoint.ConnectionLeaseTimeout);
1637+
}
1638+
16241639
Debug.Assert(handler.UseProxy); // Default of handler.UseProxy is true.
16251640
Debug.Assert(handler.Proxy == null); // Default of handler.Proxy is null.
16261641

@@ -1638,7 +1653,7 @@ private static HttpClient CreateHttpClient(HttpClientParameters parameters, Http
16381653
{
16391654
handler.UseProxy = false;
16401655
}
1641-
else if (!object.ReferenceEquals(parameters.Proxy, WebRequest.GetSystemWebProxy()))
1656+
else if (!ReferenceEquals(parameters.Proxy, GetSystemWebProxy()))
16421657
{
16431658
handler.Proxy = parameters.Proxy;
16441659
}
@@ -1659,10 +1674,20 @@ private static HttpClient CreateHttpClient(HttpClientParameters parameters, Http
16591674
handler.SslOptions.EnabledSslProtocols = (SslProtocols)parameters.SslProtocols;
16601675
handler.SslOptions.CertificateRevocationCheckMode = parameters.CheckCertificateRevocationList ? X509RevocationMode.Online : X509RevocationMode.NoCheck;
16611676
RemoteCertificateValidationCallback? rcvc = parameters.ServerCertificateValidationCallback;
1662-
if (rcvc != null)
1677+
handler.SslOptions.RemoteCertificateValidationCallback = (message, cert, chain, errors) =>
16631678
{
1664-
handler.SslOptions.RemoteCertificateValidationCallback = (message, cert, chain, errors) => rcvc(request!, cert, chain, errors);
1665-
}
1679+
if (parameters.ServicePoint is { } servicePoint)
1680+
{
1681+
servicePoint.Certificate = cert;
1682+
}
1683+
1684+
if (rcvc is not null)
1685+
{
1686+
return rcvc(request!, cert, chain, errors);
1687+
}
1688+
1689+
return errors == SslPolicyErrors.None;
1690+
};
16661691

16671692
// Set up a ConnectCallback so that we can control Socket-specific settings, like ReadWriteTimeout => socket.Send/ReceiveTimeout.
16681693
handler.ConnectCallback = async (context, cancellationToken) =>
@@ -1671,6 +1696,10 @@ private static HttpClient CreateHttpClient(HttpClientParameters parameters, Http
16711696

16721697
try
16731698
{
1699+
IPAddress[] addresses = parameters.Async ?
1700+
await Dns.GetHostAddressesAsync(context.DnsEndPoint.Host, cancellationToken).ConfigureAwait(false) :
1701+
Dns.GetHostAddresses(context.DnsEndPoint.Host);
1702+
16741703
if (parameters.ServicePoint is { } servicePoint)
16751704
{
16761705
if (servicePoint.ReceiveBufferSize != -1)
@@ -1684,19 +1713,58 @@ private static HttpClient CreateHttpClient(HttpClientParameters parameters, Http
16841713
socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime, keepAlive.Time);
16851714
socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval, keepAlive.Interval);
16861715
}
1716+
1717+
BindHelper(servicePoint, ref addresses, socket, context.DnsEndPoint.Port);
1718+
static void BindHelper(ServicePoint servicePoint, ref IPAddress[] addresses, Socket socket, int port)
1719+
{
1720+
if (servicePoint.BindIPEndPointDelegate is null)
1721+
{
1722+
return;
1723+
}
1724+
1725+
const int MaxRetries = 100;
1726+
foreach (IPAddress address in addresses)
1727+
{
1728+
int retryCount = 0;
1729+
for (; retryCount < MaxRetries; retryCount++)
1730+
{
1731+
IPEndPoint? endPoint = servicePoint.BindIPEndPointDelegate(servicePoint, new IPEndPoint(address, port), retryCount);
1732+
if (endPoint is null) // Get other address to try
1733+
{
1734+
break;
1735+
}
1736+
1737+
try
1738+
{
1739+
socket.Bind(endPoint);
1740+
addresses = [address];
1741+
return; // Bind successful, exit loops.
1742+
}
1743+
catch
1744+
{
1745+
continue;
1746+
}
1747+
}
1748+
1749+
if (retryCount >= MaxRetries)
1750+
{
1751+
throw new OverflowException(SR.net_maximumbindretries);
1752+
}
1753+
}
1754+
}
16871755
}
16881756

1689-
socket.NoDelay = true;
1757+
socket.NoDelay = !(parameters.ServicePoint?.UseNagleAlgorithm) ?? true;
16901758

16911759
if (parameters.Async)
16921760
{
1693-
await socket.ConnectAsync(context.DnsEndPoint, cancellationToken).ConfigureAwait(false);
1761+
await socket.ConnectAsync(addresses, context.DnsEndPoint.Port, cancellationToken).ConfigureAwait(false);
16941762
}
16951763
else
16961764
{
16971765
using (cancellationToken.UnsafeRegister(s => ((Socket)s!).Dispose(), socket))
16981766
{
1699-
socket.Connect(context.DnsEndPoint);
1767+
socket.Connect(addresses, context.DnsEndPoint.Port);
17001768
}
17011769

17021770
// Throw in case cancellation caused the socket to be disposed after the Connect completed

src/libraries/System.Net.Requests/src/System/Net/HttpWebResponse.cs

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
using System.Net.Http;
99
using System.Runtime.Serialization;
1010
using System.Text;
11+
using System.Threading;
12+
using System.Threading.Tasks;
1113

1214
namespace System.Net
1315
{
@@ -337,7 +339,14 @@ public override Stream GetResponseStream()
337339
CheckDisposed();
338340
if (_httpResponseMessage.Content != null)
339341
{
340-
return _httpResponseMessage.Content.ReadAsStream();
342+
Stream contentStream = _httpResponseMessage.Content.ReadAsStream();
343+
int maxErrorResponseLength = HttpWebRequest.DefaultMaximumErrorResponseLength;
344+
if (maxErrorResponseLength < 0 || StatusCode < HttpStatusCode.BadRequest)
345+
{
346+
return contentStream;
347+
}
348+
349+
return new TruncatedReadStream(contentStream, maxErrorResponseLength);
341350
}
342351

343352
return Stream.Null;
@@ -371,5 +380,56 @@ private void CheckDisposed()
371380
}
372381

373382
private static string GetHeaderValueAsString(IEnumerable<string> values) => string.Join(", ", values);
383+
384+
internal sealed class TruncatedReadStream(Stream innerStream, int maxSize) : Stream
385+
{
386+
public override bool CanRead => true;
387+
public override bool CanSeek => false;
388+
public override bool CanWrite => false;
389+
390+
public override long Length => throw new NotSupportedException();
391+
public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }
392+
393+
public override void Flush() => throw new NotSupportedException();
394+
395+
public override int Read(byte[] buffer, int offset, int count)
396+
{
397+
return Read(new Span<byte>(buffer, offset, count));
398+
}
399+
400+
public override int Read(Span<byte> buffer)
401+
{
402+
int readBytes = innerStream.Read(buffer.Slice(0, Math.Min(buffer.Length, maxSize)));
403+
maxSize -= readBytes;
404+
return readBytes;
405+
}
406+
407+
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
408+
{
409+
return ReadAsync(new Memory<byte>(buffer, offset, count), cancellationToken).AsTask();
410+
}
411+
412+
public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
413+
{
414+
int readBytes = await innerStream.ReadAsync(buffer.Slice(0, Math.Min(buffer.Length, maxSize)), cancellationToken)
415+
.ConfigureAwait(false);
416+
maxSize -= readBytes;
417+
return readBytes;
418+
}
419+
420+
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
421+
public override void SetLength(long value) => throw new NotSupportedException();
422+
public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
423+
424+
public override ValueTask DisposeAsync() => innerStream.DisposeAsync();
425+
426+
protected override void Dispose(bool disposing)
427+
{
428+
if (disposing)
429+
{
430+
innerStream.Dispose();
431+
}
432+
}
433+
}
374434
}
375435
}

src/libraries/System.Net.Requests/src/System/Net/ServicePoint/ServicePointManager.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ public static int MaxServicePointIdleTime
7878
}
7979
}
8080

81-
public static bool UseNagleAlgorithm { get; set; } = true;
81+
public static bool UseNagleAlgorithm { get; set; }
8282

8383
public static bool Expect100Continue { get; set; } = true;
8484

@@ -156,7 +156,8 @@ public static ServicePoint FindServicePoint(Uri address, IWebProxy? proxy)
156156
IdleSince = DateTime.Now,
157157
Expect100Continue = Expect100Continue,
158158
UseNagleAlgorithm = UseNagleAlgorithm,
159-
KeepAlive = KeepAlive
159+
KeepAlive = KeepAlive,
160+
MaxIdleTime = MaxServicePointIdleTime
160161
};
161162
s_servicePointTable[tableKey] = new WeakReference<ServicePoint>(sp);
162163

0 commit comments

Comments
 (0)