Skip to content

Instantly share code, notes, and snippets.

@MihaZupan
Last active December 6, 2022 00:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MihaZupan/8f28566fdc4b8e20491221ed84362fcf to your computer and use it in GitHub Desktop.
Save MihaZupan/8f28566fdc4b8e20491221ed84362fcf to your computer and use it in GitHub Desktop.
var handler = new SocketsHttpHandler();
HttpClientDiagnosticsHelper.ConfigureDebugConnectCallback(handler);
var client = new HttpClient(handler);
var request = new HttpRequestMessage(HttpMethod.Get, "https://httpbin.org/get");
var (response, endPoint, ex) = await HttpClientDiagnosticsHelper.SendAsync(client, request, CancellationToken.None);
if (endPoint is not null)
{
Console.WriteLine(endPoint);
}
public static class HttpClientDiagnosticsHelper
{
private static readonly AsyncLocal<StrongBox<IPEndPoint>> s_endPoint = new();
public static void ConfigureDebugConnectCallback(SocketsHttpHandler handler)
{
var innerCallback = handler.ConnectCallback ?? DefaultConnectCallback;
handler.ConnectCallback = async (context, ct) =>
{
Stream s = await innerCallback(context, ct);
Debug.Assert(s is NetworkStream, "This helper only works on NetworkStreams");
if (s is NetworkStream ns && ns.Socket.RemoteEndPoint is IPEndPoint endPoint)
{
s = new DiagStream(ns, endPoint);
}
return s;
};
static async ValueTask<Stream> DefaultConnectCallback(SocketsHttpConnectionContext context, CancellationToken ct)
{
var socket = new Socket(SocketType.Stream, ProtocolType.Tcp) { NoDelay = true };
try
{
await socket.ConnectAsync(context.DnsEndPoint, ct);
return new NetworkStream(socket, ownsSocket: true);
}
catch
{
socket.Dispose();
throw;
}
}
}
public static async Task<(HttpResponseMessage Response, IPEndPoint EndPoint, Exception Ex)> SendAsync(HttpMessageInvoker client, HttpRequestMessage request, CancellationToken ct = default)
{
if (request.Version.Major != 1 || request.VersionPolicy == HttpVersionPolicy.RequestVersionOrHigher)
{
throw new ArgumentException("This helper only works for HTTP/1.X requests.", nameof(request));
}
var box = new StrongBox<IPEndPoint>();
s_endPoint.Value = box;
try
{
HttpResponseMessage response = await client.SendAsync(request, ct);
return (response, box.Value, null);
}
catch (Exception ex)
{
return (null, box.Value, ex);
}
}
private sealed class DiagStream : Stream
{
private readonly NetworkStream _innerStream;
private readonly IPEndPoint _endPoint;
public DiagStream(NetworkStream innerStream, IPEndPoint endPoint)
{
_innerStream = innerStream;
_endPoint = endPoint;
}
private void OnWrite()
{
if (s_endPoint.Value is StrongBox<IPEndPoint> box)
{
box.Value = _endPoint;
}
}
public override void Write(byte[] buffer, int offset, int count)
{
OnWrite();
_innerStream.Write(buffer, offset, count);
}
public override void Write(ReadOnlySpan<byte> buffer)
{
OnWrite();
_innerStream.Write(buffer);
}
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
OnWrite();
return _innerStream.WriteAsync(buffer, offset, count, cancellationToken);
}
public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
{
OnWrite();
return _innerStream.WriteAsync(buffer, cancellationToken);
}
public override void WriteByte(byte value)
{
OnWrite();
_innerStream.WriteByte(value);
}
public override bool CanRead => _innerStream.CanRead;
public override bool CanSeek => _innerStream.CanSeek;
public override bool CanWrite => _innerStream.CanWrite;
public override long Length => _innerStream.Length;
public override long Position { get => _innerStream.Position; set => _innerStream.Position = value; }
public override void Flush() => _innerStream.Flush();
public override Task FlushAsync(CancellationToken cancellationToken) => _innerStream.FlushAsync(cancellationToken);
public override int Read(byte[] buffer, int offset, int count) => _innerStream.Read(buffer, offset, count);
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => _innerStream.ReadAsync(buffer, offset, count, cancellationToken);
public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default) => _innerStream.ReadAsync(buffer, cancellationToken);
public override int ReadByte() => _innerStream.ReadByte();
public override int Read(Span<byte> buffer) => _innerStream.Read(buffer);
public override long Seek(long offset, SeekOrigin origin) => _innerStream.Seek(offset, origin);
public override void SetLength(long value) => _innerStream.SetLength(value);
public override ValueTask DisposeAsync() => _innerStream.DisposeAsync();
protected override void Dispose(bool disposing)
{
if (disposing)
{
_innerStream.Dispose();
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment