Skip to content

Commit

Permalink
Implement more ServicePoint properties (#97537)
Browse files Browse the repository at this point in the history
* 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 <mihazupan.zupan1@gmail.com>

* 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 <mihazupan.zupan1@gmail.com>

* 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 <antonfir@gmail.com>

* Apply suggestions from code review

Co-authored-by: Miha Zupan <mihazupan.zupan1@gmail.com>

* 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 <mihazupan.zupan1@gmail.com>

---------

Co-authored-by: Miha Zupan <mihazupan.zupan1@gmail.com>
Co-authored-by: Anton Firszov <antonfir@gmail.com>
  • Loading branch information
3 people committed Mar 1, 2024
1 parent 1bebdb0 commit 4a89242
Show file tree
Hide file tree
Showing 7 changed files with 272 additions and 23 deletions.
3 changes: 3 additions & 0 deletions src/libraries/System.Net.Requests/src/Resources/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -264,4 +264,7 @@
<data name="net_proxyschemenotsupported" xml:space="preserve">
<value>The ServicePointManager does not support proxies with the {0} scheme.</value>
</data>
<data name="net_maximumbindretries" xml:space="preserve">
<value>Reached the maximum number of BindIPEndPointDelegate retries.</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@
<Reference Include="System.Diagnostics.Tracing" />
<Reference Include="System.Memory" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Net.NameResolution" />
<Reference Include="System.Net.Primitives" />
<Reference Include="System.Net.Security" />
<Reference Include="System.Net.Sockets" />
Expand Down
96 changes: 82 additions & 14 deletions src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -41,6 +42,7 @@ public class HttpWebRequest : WebRequest, ISerializable
private Task<HttpResponseMessage>? _sendRequestTask;

private static int _defaultMaxResponseHeadersLength = HttpHandlerDefaults.DefaultMaxResponseHeadersLength;
private static int _defaultMaximumErrorResponseLength = -1;

private int _beginGetRequestStreamCalled;
private int _beginGetResponseCalled;
Expand Down Expand Up @@ -420,11 +422,7 @@ public string? Referer
/// <devdoc>
/// <para>Sets the media type header</para>
/// </devdoc>
public string? MediaType
{
get;
set;
}
public string? MediaType { get; set; }

/// <devdoc>
/// <para>
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
{
Expand Down Expand Up @@ -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.

Expand All @@ -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;
}
Expand All @@ -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) =>
Expand All @@ -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)
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -371,5 +380,56 @@ private void CheckDisposed()
}

private static string GetHeaderValueAsString(IEnumerable<string> 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<byte>(buffer, offset, count));
}

public override int Read(Span<byte> buffer)
{
int readBytes = innerStream.Read(buffer.Slice(0, Math.Min(buffer.Length, maxSize)));
maxSize -= readBytes;
return readBytes;
}

public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
return ReadAsync(new Memory<byte>(buffer, offset, count), cancellationToken).AsTask();
}

public override async ValueTask<int> ReadAsync(Memory<byte> 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();
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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<ServicePoint>(sp);

Expand Down
Loading

0 comments on commit 4a89242

Please sign in to comment.