Skip to content

API Proposal: SocketsHttpConnectionContext.ConnectAsynchronously #44876

@stephentoub

Description

@stephentoub

Background and Motivation

In .NET 5, we added SocketsHttpHandler.ConnectCallback, which enables a developer to supply a delegate that'll be used to establish connections. In .NET 5, the callback is only usable for asynchronous operations, but we also added sync support to SocketsHttpHandler (and HttpClient), and if you try to both use the ConnectCallback and sync APIs, you get an exception. We can trivially remove this restriction simply by telling the ConnectCallback in what mode it should operate.

Proposed API

namespace System.Net.Http
{
    public sealed class SocketsHttpConnectionContext
    {
+        public bool ConnectAsynchronously { get; } // true for SendAsync, false for Send
         ...
     }
}

Usage Examples

socketsHttpHandler.ConnectCallback = async (ctx, cancellationToken) =>
{
    var socket = new Socket(SocketType.Stream, ProtocolType.Tcp) { NoDelay = true };
    try
    {
        if (context.ConnectAsynchronously)
        {
            await socket.ConnectAsync(context.DnsEndPoint, cancellationToken).ConfigureAwait(false);
        }
        else
        {
            using (cancellationToken.UnsafeRegister(static s => ((Socket)s!).Dispose(), socket))
                socket.Connect(context.DnsEndPoint);
        }
    }
    catch
    {
        socket.Dispose();
        throw;
    }

    return new NetworkStream(socket, ownsSocket: true);
};

ConnectAsynchronously will be true for all existing uses that work today, because today the synchronous Send will throw an exception if a callback is provided. If once this API exists the ConnectCallback hasn't been updated to respect a false ConnectAsynchronously and a synchronous Send is issued, the caller will simply block waiting for the operation to complete (alternatives are also possible, like throwing an exception).

This also helps to consolidate/simplify logic in SocketsHttpHandler, which no longer needs to maintain two separate connection code paths, to special-case ConnectCallback, etc.

Example implementation:
stephentoub@3674c8d

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions