Skip to content

Add ConnectCallback on SocketsHttpHandler (aka custom Dialers) #28721

Closed
@simonferquel

Description

@simonferquel

Summary

The current SocketHttpHandler implementation cleanly separates the HTTP grammar to the actual TCP connection handling. However, while Kestrel on the server side allows to expose a service over non-TCP protocols such as Unix Sockets (and probably named pipes as well on Windows), the HttpClient does not provide such functionality.
As the SocketHttpHandler main logic only depends on Socket and Stream (and actually has a fallback mechanism to be able to work with no Socket, and just a Stream), this proposal is about adding custom Dialers to the SocketHttpHandler, allowing a user to use whatever transport he wants to connect to the server.
Also, there is no way to reuse all the Http grammar goodness implemented in System.Net.Http in a custom MessageHandler in a foreign assembly, because all this is internal. One potential alternative to this proposal would then be to refactor all this internal code in pieces consumable from a user.

Proposed API

This Proposal adds two properties to SocketHttpHandler:

public sealed partial class SocketsHttpHandler : System.Net.Http.HttpMessageHandler
{
// < ... >
    public Func<HttpRequestMessage, Threading.CancellationToken, Threading.Tasks.ValueTask<IO.Stream>> ConnectCallback { get { throw null; } set { } }
}

Usage

var handler = new SocketsHttpHandler{
   ConnectCallback = async (HttpRequestMessage message, CancellationToken cancellationToken)=>{
      // custom logic here
      // ....
      //
      return stream
   }
};
var client = new HttpClient(handler);
var response = await client.GetAsync(uri);

Open Questions

  • We could only use a single Dialer property, but it would not be obvious to the user that it is actually ok to not return a Socket. -> Only one property now. If required, we can introduce an IWithSocket interface that the returned stream might implement to provide raw socket access to the SocketsHttpHandler
  • C# 8 constructs such as public Func<string, int, Threading.CancellationToken, Threading.Tasks.ValueTask<(Sockets.Socket?, IO.Stream)>> could help, but I am not sure how this kind of constructs is exposed to other users. Single stream return value (optionally implementing IWithSocket) fixes the question
  • Another suggestion, is to introduce a delegate type for this Dialer callback with explicit documentation about the optional nature of the Socket return value. Ruled out by single return value + HttpRequestMessage in the callback parameter
  • Yet another one would be to have a single Dialer callback property, and add an extension method WithStreamDialer on SocketsHttpHandler that would wrap a "Stream only" callback. No extension method should be introduced

Reference

WIP implementation: dotnet/corefx@master...simonferquel:custom-dialers
Prototype of previous related work done for Docker Desktop: https://github.com/simonferquel/HttpOverStream/tree/master/src/HttpOverStream (illustrate the difficulty of implementing a custom MessageHandler without access to internal code from System.Net.Http)

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