Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement SocketsHttpHandler.ConnectCallback #41955

Merged
merged 10 commits into from
Sep 10, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/libraries/System.Net.Http/ref/System.Net.Http.cs
Original file line number Diff line number Diff line change
@@ -360,7 +360,15 @@ protected override void Dispose(bool disposing) { }
protected internal override System.Net.Http.HttpResponseMessage Send(System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) { throw null; }
protected internal override System.Threading.Tasks.Task<System.Net.Http.HttpResponseMessage> SendAsync(System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) { throw null; }
public bool EnableMultipleHttp2Connections { get { throw null; } set { } }
public Func<SocketsHttpConnectionContext, System.Threading.CancellationToken, System.Threading.Tasks.ValueTask<System.IO.Stream>>? ConnectCallback { get { throw null; } set { } }
}
public sealed class SocketsHttpConnectionContext
{
internal SocketsHttpConnectionContext() { }
public DnsEndPoint DnsEndPoint { get { throw null; } }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may have been discussed in API review. It struck me as a little strange that we dropped the Http from HttpRequestMessage but not the Dns from DnsEndPoint: I'd have expected this to be named EndPoint for consistency. Not a big deal, though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It wasn't discussed in API review. I think it's fine as it is -- EndPoint is actually the base class of DnsEndPoint, so calling it DnsEndPoint specifically emphasizes that it's a DnsEndPoint and not just any EndPoint.

public HttpRequestMessage RequestMessage { get { throw null; } }
}

public enum HttpKeepAlivePingPolicy
{
WithActiveRequests,
3 changes: 3 additions & 0 deletions src/libraries/System.Net.Http/src/Resources/Strings.resx
Original file line number Diff line number Diff line change
@@ -585,4 +585,7 @@
<data name="net_http_requested_version_server_refused" xml:space="preserve">
<value>Requesting HTTP version {0} with version policy {1} while server offers only version fallback.</value>
</data>
<data name="net_http_sync_operations_not_allowed_with_connect_callback" xml:space="preserve">
<value>Synchronous operation is not supported when a ConnectCallback is specified on the SocketsHttpHandler instance.</value>
</data>
</root>
3 changes: 2 additions & 1 deletion src/libraries/System.Net.Http/src/System.Net.Http.csproj
Original file line number Diff line number Diff line change
@@ -173,7 +173,7 @@
<Compile Include="System\Net\Http\SocketsHttpHandler\MultiProxy.cs" />
<Compile Include="System\Net\Http\SocketsHttpHandler\RawConnectionStream.cs" />
<Compile Include="System\Net\Http\SocketsHttpHandler\RedirectHandler.cs" />
<Compile Include="System\Net\Http\SocketsHttpHandler\SocketsConnectionFactory.cs" />
<Compile Include="System\Net\Http\SocketsHttpHandler\SocketsHttpConnectionContext.cs" />
<Compile Include="System\Net\Http\SocketsHttpHandler\SocketsHttpHandler.cs" />
<Compile Include="System\Net\Http\SocketsHttpHandler\SystemProxyInfo.cs" />
<Compile Include="$(CommonPath)System\Net\NTAuthentication.Common.cs"
@@ -671,6 +671,7 @@
Link="System\System\Threading\Tasks\TaskToApm.cs" />
<Compile Include="System\Net\Http\SocketsHttpHandler\HttpKeepAlivePingPolicy.cs" />
<Compile Include="System\Net\Http\SocketsHttpHandler\HttpNoProxy.cs" />
<Compile Include="System\Net\Http\SocketsHttpHandler\SocketsHttpConnectionContext.cs" />
<Compile Include="System\Net\Http\BrowserHttpHandler\SystemProxyInfo.Browser.cs" />
<Compile Include="System\Net\Http\BrowserHttpHandler\SocketsHttpHandler.cs" />
<Compile Include="System\Net\Http\BrowserHttpHandler\BrowserHttpHandler.cs" />
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.IO;
using System.Net.Security;
using System.Threading;
using System.Threading.Tasks;
@@ -170,5 +171,11 @@ public bool EnableMultipleHttp2Connections
get => throw new PlatformNotSupportedException();
set => throw new PlatformNotSupportedException();
}

public Func<SocketsHttpConnectionContext, CancellationToken, ValueTask<Stream>>? ConnectCallback
{
get => throw new PlatformNotSupportedException();
set => throw new PlatformNotSupportedException();
}
}
}
Original file line number Diff line number Diff line change
@@ -31,11 +31,11 @@ public CertificateCallbackMapper(Func<HttpRequestMessage, X509Certificate2?, X50
}
}

public static async ValueTask<Stream> ConnectAsync(SocketsConnectionFactory factory, DnsEndPoint endPoint, CancellationToken cancellationToken)
public static async ValueTask<Stream> ConnectAsync(Func<SocketsHttpConnectionContext, CancellationToken, ValueTask<Stream>> callback, DnsEndPoint endPoint, HttpRequestMessage requestMessage, CancellationToken cancellationToken)
{
try
{
return await factory.ConnectAsync(endPoint, cancellationToken).ConfigureAwait(false);
return await callback(new SocketsHttpConnectionContext(endPoint, requestMessage), cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException ex) when (ex.CancellationToken == cancellationToken)
{
Original file line number Diff line number Diff line change
@@ -1286,20 +1286,42 @@ public ValueTask<HttpResponseMessage> SendAsync(HttpRequestMessage request, bool
}
}

private static readonly SocketsConnectionFactory s_defaultConnectionFactory = new SocketsConnectionFactory(SocketType.Stream, ProtocolType.Tcp);
private static async ValueTask<Stream> DefaultConnectAsync(SocketsHttpConnectionContext context, CancellationToken cancellationToken)
{
Socket socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
socket.NoDelay = true;

try
{
await socket.ConnectAsync(context.DnsEndPoint, cancellationToken).ConfigureAwait(false);
return new NetworkStream(socket, ownsSocket: true);
}
catch
{
socket.Dispose();
throw;
}
}

private static readonly Func<SocketsHttpConnectionContext, CancellationToken, ValueTask<Stream>> s_defaultConnectCallback = DefaultConnectAsync;

private ValueTask<Stream> ConnectToTcpHostAsync(string host, int port, HttpRequestMessage initialRequest, bool async, CancellationToken cancellationToken)
{
if (async)
{
SocketsConnectionFactory connectionFactory = s_defaultConnectionFactory;
Func<SocketsHttpConnectionContext, CancellationToken, ValueTask<Stream>> connectCallback = Settings._connectCallback ?? s_defaultConnectCallback;

var endPoint = new DnsEndPoint(host, port);
return ConnectHelper.ConnectAsync(connectionFactory, endPoint, cancellationToken);
return ConnectHelper.ConnectAsync(connectCallback, endPoint, initialRequest, cancellationToken);
}

// Synchronous path.

if (Settings._connectCallback is not null)
{
throw new NotSupportedException(SR.net_http_sync_operations_not_allowed_with_connect_callback);
}

try
{
return new ValueTask<Stream>(ConnectHelper.Connect(host, port, cancellationToken));
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@

using System.Collections.Generic;
using System.Net.Security;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

@@ -55,6 +56,8 @@ internal sealed class HttpConnectionSettings

internal bool _enableMultipleHttp2Connections;

internal Func<SocketsHttpConnectionContext, CancellationToken, ValueTask<Stream>>? _connectCallback;

internal IDictionary<string, object?>? _properties;

public HttpConnectionSettings()
@@ -108,6 +111,7 @@ public HttpConnectionSettings CloneAndNormalize()
_requestHeaderEncodingSelector = _requestHeaderEncodingSelector,
_responseHeaderEncodingSelector = _responseHeaderEncodingSelector,
_enableMultipleHttp2Connections = _enableMultipleHttp2Connections,
_connectCallback = _connectCallback,
};
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace System.Net.Http
{
/// <summary>
/// Represents the context passed to the ConnectCallback for a SocketsHttpHandler instance.
/// </summary>
public sealed class SocketsHttpConnectionContext
{
private readonly DnsEndPoint _dnsEndPoint;
private readonly HttpRequestMessage _requestMessage;
Comment on lines +11 to +12
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: these could be auto props.


internal SocketsHttpConnectionContext(DnsEndPoint dnsEndPoint, HttpRequestMessage requestMessage)
{
_dnsEndPoint = dnsEndPoint;
_requestMessage = requestMessage;
}

/// <summary>
/// The DnsEndPoint to be used by the ConnectCallback to establish the connection.
/// </summary>
public DnsEndPoint DnsEndPoint => _dnsEndPoint;

/// <summary>
/// The initial HttpRequestMessage that is causing the connection to be created.
/// </summary>
public HttpRequestMessage RequestMessage => _requestMessage;
}
}
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@

using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net.Security;
using System.Threading;
using System.Threading.Tasks;
@@ -362,6 +363,19 @@ public bool EnableMultipleHttp2Connections
internal bool SupportsProxy => true;
internal bool SupportsRedirectConfiguration => true;

/// <summary>
/// When non-null, a custom callback used to open new connections.
/// </summary>
public Func<SocketsHttpConnectionContext, CancellationToken, ValueTask<Stream>>? ConnectCallback
{
get => _settings._connectCallback;
set
{
CheckDisposedOrStarted();
_settings._connectCallback = value;
}
}

public IDictionary<string, object?> Properties =>
_settings._properties ?? (_settings._properties = new Dictionary<string, object?>());

Loading