Description
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 anIWithSocket
interface that the returned stream might implement to provide raw socket access to theSocketsHttpHandler
C# 8 constructs such asSingle stream return value (optionally implementing IWithSocket) fixes the questionpublic 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.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 parameterYet another one would be to have a single Dialer callback property, and add an extension methodNo extension method should be introducedWithStreamDialer
onSocketsHttpHandler
that would wrap a "Stream only" callback.
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)