From e40aedc56c0983a4323f87d105868564d615284f Mon Sep 17 00:00:00 2001 From: Vladimir Date: Sat, 10 Mar 2018 16:55:51 +0100 Subject: [PATCH] SocketsHttpHandler, support for custom endpoint address resolving --- src/System.Net.Http/ref/System.Net.Http.cs | 3 ++ .../src/System/Net/Http/HttpRequestMessage.cs | 35 +++++++++++++++---- .../Http/SocketsHttpHandler/ConnectHelper.cs | 13 +++++-- .../SocketsHttpHandler/HttpConnectionPool.cs | 4 +-- .../SocketsHttpHandler/SocketsHttpHandler.cs | 10 ++++++ 5 files changed, 55 insertions(+), 10 deletions(-) diff --git a/src/System.Net.Http/ref/System.Net.Http.cs b/src/System.Net.Http/ref/System.Net.Http.cs index 855c87091b69..c9506488b8e9 100644 --- a/src/System.Net.Http/ref/System.Net.Http.cs +++ b/src/System.Net.Http/ref/System.Net.Http.cs @@ -176,6 +176,8 @@ public partial class HttpRequestMessage : System.IDisposable public HttpRequestMessage() { } public HttpRequestMessage(System.Net.Http.HttpMethod method, string requestUri) { } public HttpRequestMessage(System.Net.Http.HttpMethod method, System.Uri requestUri) { } + public HttpRequestMessage(HttpMethod method, Uri requestUri, System.Net.Sockets.AddressFamily resolveAddressFamily) { } + public HttpRequestMessage(HttpMethod method, string requestUri, System.Net.Sockets.AddressFamily resolveAddressFamily) { } public System.Net.Http.HttpContent Content { get { throw null; } set { } } public System.Net.Http.Headers.HttpRequestHeaders Headers { get { throw null; } } public System.Net.Http.HttpMethod Method { get { throw null; } set { } } @@ -185,6 +187,7 @@ public HttpRequestMessage(System.Net.Http.HttpMethod method, System.Uri requestU public void Dispose() { } protected virtual void Dispose(bool disposing) { } public override string ToString() { throw null; } + public System.Net.Sockets.AddressFamily ResolveAddressFamily { get { throw null; } set { } } } public partial class HttpResponseMessage : System.IDisposable { diff --git a/src/System.Net.Http/src/System/Net/Http/HttpRequestMessage.cs b/src/System.Net.Http/src/System/Net/Http/HttpRequestMessage.cs index 36bf667d7cac..8fb0d012c299 100644 --- a/src/System.Net.Http/src/System/Net/Http/HttpRequestMessage.cs +++ b/src/System.Net.Http/src/System/Net/Http/HttpRequestMessage.cs @@ -7,6 +7,7 @@ using System.Text; using System.Threading; using System.Collections.Generic; +using System.Net.Sockets; namespace System.Net.Http { @@ -26,6 +27,7 @@ public class HttpRequestMessage : IDisposable private HttpContent _content; private bool _disposed; private IDictionary _properties; + private AddressFamily _resolveAddressFamily; public Version Version { @@ -98,6 +100,16 @@ public Uri RequestUri } } + public AddressFamily ResolveAddressFamily + { + get { return _resolveAddressFamily; } + set + { + CheckDisposed(); + _resolveAddressFamily = value; + } + } + public HttpRequestHeaders Headers { get @@ -129,16 +141,26 @@ public HttpRequestMessage() { } - public HttpRequestMessage(HttpMethod method, Uri requestUri) + private const AddressFamily DefaultAddressFamily = AddressFamily.Unspecified; + + public HttpRequestMessage(HttpMethod method, Uri requestUri) : this(method, requestUri, DefaultAddressFamily) + { + } + + public HttpRequestMessage(HttpMethod method, Uri requestUri, AddressFamily resolveAddressFamily) { if (NetEventSource.IsEnabled) NetEventSource.Enter(this, method, requestUri); - InitializeValues(method, requestUri); + InitializeValues(method, requestUri, resolveAddressFamily); if (NetEventSource.IsEnabled) NetEventSource.Exit(this); } + public HttpRequestMessage(HttpMethod method, string requestUri) : this(method, requestUri, DefaultAddressFamily) + { + } + [SuppressMessage("Microsoft.Design", "CA1057:StringUriOverloadsCallSystemUriOverloads", Justification = "It is OK to provide 'null' values. A Uri instance is created from 'requestUri' if it is != null.")] - public HttpRequestMessage(HttpMethod method, string requestUri) + public HttpRequestMessage(HttpMethod method, string requestUri, AddressFamily resolveAddressFamily) { if (NetEventSource.IsEnabled) NetEventSource.Enter(this, method, requestUri); @@ -147,11 +169,11 @@ public HttpRequestMessage(HttpMethod method, string requestUri) // Note that we also allow the string to be empty: null and empty are considered equivalent. if (string.IsNullOrEmpty(requestUri)) { - InitializeValues(method, null); + InitializeValues(method, null, resolveAddressFamily); } else { - InitializeValues(method, new Uri(requestUri, UriKind.RelativeOrAbsolute)); + InitializeValues(method, new Uri(requestUri, UriKind.RelativeOrAbsolute), resolveAddressFamily); } if (NetEventSource.IsEnabled) NetEventSource.Exit(this); @@ -179,7 +201,7 @@ public override string ToString() return sb.ToString(); } - private void InitializeValues(HttpMethod method, Uri requestUri) + private void InitializeValues(HttpMethod method, Uri requestUri, AddressFamily resolveAddressFamily) { if (method == null) { @@ -192,6 +214,7 @@ private void InitializeValues(HttpMethod method, Uri requestUri) _method = method; _requestUri = requestUri; + _resolveAddressFamily = resolveAddressFamily; _version = HttpUtilities.DefaultRequestVersion; } diff --git a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ConnectHelper.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ConnectHelper.cs index c11a188be457..dce9ab295fcd 100644 --- a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ConnectHelper.cs +++ b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ConnectHelper.cs @@ -32,7 +32,7 @@ public CertificateCallbackMapper(Func ConnectAsync(string host, int port, CancellationToken cancellationToken) + public static async ValueTask ConnectAsync(string host, int port, AddressFamily addressFamily, CancellationToken cancellationToken) { try { @@ -42,9 +42,18 @@ public static async ValueTask ConnectAsync(string host, int port, Cancel using (var saea = new BuilderAndCancellationTokenSocketAsyncEventArgs(cancellationToken)) { // Configure which server to which to connect. - saea.RemoteEndPoint = IPAddress.TryParse(host, out IPAddress address) ? + if (SocketsHttpHandler.ResolveEndpointAsync == null) + { + saea.RemoteEndPoint = IPAddress.TryParse(host, out IPAddress address) ? (EndPoint)new IPEndPoint(address, port) : new DnsEndPoint(host, port); + } + else + { + //give user control how to resolve endpoint address + var resolveEndpointTask = SocketsHttpHandler.ResolveEndpointAsync(host, port, addressFamily, cancellationToken); + saea.RemoteEndPoint = resolveEndpointTask.IsCompleted ? resolveEndpointTask.Result : await resolveEndpointTask.ConfigureAwait(false); + } // Hook up a callback that'll complete the Task when the operation completes. saea.Completed += (s, e) => diff --git a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs index 81e6d5975096..b6644c97bf1e 100644 --- a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs +++ b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs @@ -306,11 +306,11 @@ public Task SendAsync(HttpRequestMessage request, bool doRe case HttpConnectionKind.Http: case HttpConnectionKind.Https: case HttpConnectionKind.ProxyConnect: - stream = await ConnectHelper.ConnectAsync(_host, _port, cancellationToken).ConfigureAwait(false); + stream = await ConnectHelper.ConnectAsync(_host, _port, request.ResolveAddressFamily, cancellationToken).ConfigureAwait(false); break; case HttpConnectionKind.Proxy: - stream = await ConnectHelper.ConnectAsync(_proxyUri.IdnHost, _proxyUri.Port, cancellationToken).ConfigureAwait(false); + stream = await ConnectHelper.ConnectAsync(_proxyUri.IdnHost, _proxyUri.Port, request.ResolveAddressFamily, cancellationToken).ConfigureAwait(false); break; case HttpConnectionKind.SslProxyTunnel: diff --git a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs index ee4b6a2f7687..38e9eab3842d 100644 --- a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs +++ b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Net.Security; +using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; @@ -388,5 +389,14 @@ private Exception ValidateAndNormalizeRequest(HttpRequestMessage request) return null; } + + public delegate Task ResolveEndpointAsyncDelegate(string host, int port, AddressFamily addressFamily, CancellationToken cancellationToken); + + /// + /// With this property user can have more control when resolving IP address. For example one could prefer to resolve IPv6 addresses over IPv4 + /// or completely override system default DNS resolving logic. + /// If set to null (default) client behaves same as before (backward compatible). + /// + public static ResolveEndpointAsyncDelegate ResolveEndpointAsync { get; set; } = null; } }