Skip to content

Instantly share code, notes, and snippets.

@bent-rasmussen
Last active January 2, 2022 15:30
Show Gist options
  • Save bent-rasmussen/7bd3be2606e09759c2f3d84bbefb5626 to your computer and use it in GitHub Desktop.
Save bent-rasmussen/7bd3be2606e09759c2f3d84bbefb5626 to your computer and use it in GitHub Desktop.
LINQPad HTTP request tracing tool
<Query Kind="Statements">
<Namespace>System.Net.Http</Namespace>
<Namespace>System.Threading.Tasks</Namespace>
<Namespace>System.Dynamic</Namespace>
</Query>
await TestAsync("Foo", http => http.GetStringAsync("http://www.google.com/"), HttpFormatOptions.Verbose);
static object Title(string message, bool isError = false)
{
var color = isError ? "red" : "black";
return Util.RawHtml($"<em style='font-weight:bold;font-style:normal;color:{color}'>{message}</em>");
}
async Task TestAsync(string label, Func<HttpClient, Task> test, HttpFormatOptions format)
{
var dc = new DumpContainer().Dump(label);
var handler = new TransformingHttpHandler(new HttpClientHandler());
handler.Format = format;
using var http = new HttpClient(handler);
try
{
await test(http);
dc.AppendContent(Title("Request"));
dc.AppendContent(handler.TransformedRequest);
dc.AppendContent(Title("Response"));
dc.AppendContent(handler.TransformedResponse);
}
catch (Exception ex)
{
dc.AppendContent(Title("Exception"));
dc.AppendContent(ex);
}
}
public record HttpFormatOptions
{
public bool IncludeRequestContent = true;
public bool IncludeResponseContent = true;
public static HttpFormatOptions Succint => new HttpFormatOptions()
{
IncludeRequestContent = false,
IncludeResponseContent = false
};
public static HttpFormatOptions Verbose => new HttpFormatOptions()
{
IncludeRequestContent = true,
IncludeResponseContent = true
};
}
public class TransformingHttpHandler : DelegatingHandler
{
public TransformingHttpHandler(HttpMessageHandler innerHandler)
: base(innerHandler)
{
}
public HttpFormatOptions Format { get; set; } = HttpFormatOptions.Succint;
public object TransformedRequest { get; private set; }
public object TransformedResponse { get; private set; }
static async Task<dynamic> AbbreviateRequestAsync(HttpRequestMessage message, HttpFormatOptions format)
{
await Task.Yield();
if (message is null)
return new { RequestIsNull = true };
dynamic result = new ExpandoObject();
result.RequestUri = message.RequestUri;
result.Method = message.Method.ToString();
if (format.IncludeRequestContent && message.Content is not null) result.Content = await message.Content.ReadAsStringAsync();
if (message.Headers.Any()) result.Headers = message.Headers;
return result;
}
static async Task<dynamic> AbbreviateResponseAsync(HttpResponseMessage message, HttpFormatOptions format)
{
await Task.Yield();
if (message is null)
return new { ResponseIsNull = true };
dynamic result = new ExpandoObject();
result.Status = message.StatusCode;
result.StatusCode = (int)message.StatusCode;
result.IsSuccessStatusCode = message.IsSuccessStatusCode;
if (format.IncludeResponseContent && message.Content is not null) result.Content = await message.Content.ReadAsStringAsync();
if (message.Headers.Any()) result.Headers = message.Headers;
if (message.TrailingHeaders.Any()) result.TrailingHeaders = message.TrailingHeaders;
return result;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
TransformedRequest = await AbbreviateRequestAsync(request, Format);
var response = await base.SendAsync(request, cancellationToken);
TransformedResponse = await AbbreviateResponseAsync(response, Format);
return response;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment