I will tell you how to intercept and diagnose HTTP requests in this article. I'll show you how to get headers from requests. Let's start.
Before we begin writing code, it would be good if you created a simple console application.
The first that we'll make is creating an observer. Into your Program.cs
file, add this class:
internal sealed class HttpRequestsObserver : IDisposable, IObserver<DiagnosticListener>
{
public void Dispose()
{
throw new NotImplementedException();
}
public void OnCompleted()
{
throw new NotImplementedException();
}
public void OnError(Exception error)
{
throw new NotImplementedException();
}
public void OnNext(DiagnosticListener value)
{
throw new NotImplementedException();
}
}
We inherited from IObserver
and also added IDisposable for disposing of the subscription. Let's implement realized methods. As you can have noticed, the class is responsible for subscription.
internal sealed class HttpRequestsObserver : IDisposable, IObserver<DiagnosticListener>
{
private IDisposable? _subscription;
public void OnNext(DiagnosticListener value)
{
if (value.Name != "HttpHandlerDiagnosticListener") return;
Debug.Assert(_subscription == null);
_subscription = value.Subscribe(new HttpHandlerDiagnosticListener()!);
}
public void OnCompleted() { }
public void OnError(Exception error) { }
public void Dispose()
{
_subscription?.Dispose();
}
}
Now, we need to create a class in which we get access to requests and responses. You should create a new class that will also be inherited from IObserver
.
internal sealed class HttpRequestsObserver : IDisposable, IObserver<DiagnosticListener>
{
private IDisposable? _subscription;
public void OnNext(DiagnosticListener value)
{
if (value.Name != "HttpHandlerDiagnosticListener") return;
Debug.Assert(_subscription == null);
_subscription = value.Subscribe(new HttpHandlerDiagnosticListener()!);
}
public void OnCompleted() { }
public void OnError(Exception error) { }
public void Dispose()
{
_subscription?.Dispose();
}
private sealed class HttpHandlerDiagnosticListener : IObserver<KeyValuePair<string, object>>
{
private static readonly Func<object, HttpRequestMessage?> RequestAccessor = CreateGetRequest();
private static readonly Func<object, HttpResponseMessage?> ResponseAccessor = CreateGetResponse();
public void OnCompleted() { }
public void OnError(Exception error) { }
public void OnNext(KeyValuePair<string, object> value)
{
switch (value.Key)
{
case "System.Net.Http.HttpRequestOut.Start":
{
var request = RequestAccessor(value.Value);
if (request != null)
Console.WriteLine(
$"{request.Method} {request.RequestUri} {request.Version} ({request.Headers}) {request.Headers.NonValidated["Hello"]}");
break;
}
case "System.Net.Http.HttpRequestOut.Stop":
{
var response = ResponseAccessor(value.Value);
var json = response?.Content.ReadAsStringAsync().ConfigureAwait(false).GetAwaiter().GetResult();
if (response != null)
Console.WriteLine(
$"{response.StatusCode} {response.RequestMessage?.RequestUri} Content: {json}");
break;
}
}
}
private static Func<object, HttpRequestMessage?> CreateGetRequest()
{
var requestDataType = Type.GetType("System.Net.Http.DiagnosticsHandler+ActivityStartData, System.Net.Http", throwOnError: true);
var requestProperty = requestDataType?.GetProperty("Request");
return o => (HttpRequestMessage)requestProperty?.GetValue(o)!;
}
private static Func<object, HttpResponseMessage?> CreateGetResponse()
{
var requestDataType = Type.GetType("System.Net.Http.DiagnosticsHandler+ActivityStopData, System.Net.Http", throwOnError: true);
var requestProperty = requestDataType?.GetProperty("Response");
return o => (HttpResponseMessage)requestProperty?.GetValue(o)!;
}
}
Let me explain in more detail what was implemented in this class. I added two properties where we get requests and responses since we can't get them directly.
private static readonly Func<object, HttpRequestMessage?> RequestAccessor = CreateGetRequest();
private static readonly Func<object, HttpResponseMessage?> ResponseAccessor = CreateGetResponse();
I would like it if you pay attention to OnNext
method.
public void OnNext(KeyValuePair<string, object> value)
{
switch (value.Key)
{
case "System.Net.Http.HttpRequestOut.Start":
{
var request = RequestAccessor(value.Value);
if (request != null)
Console.WriteLine(
$"{request.Method} {request.RequestUri} {request.Version} ({request.Headers}) {request.Headers.NonValidated["Hello"]}");
break;
}
case "System.Net.Http.HttpRequestOut.Stop":
{
var response = ResponseAccessor(value.Value);
var json = response.Content.ReadAsStringAsync().ConfigureAwait(false).GetAwaiter().GetResult();
if (response != null) Console.WriteLine($"{response.StatusCode} {response.RequestMessage?.RequestUri} Content: {json}");
break;
}
}
}
In the obsolete .NET frameworks, the System.Net.Http.HttpRequestOut.Start
and System.Net.Http.HttpRequestOut.Stop
will not work. It would help if you used System.Net.Http.HttpRequest
and System.Net.Http.Response
instead. However, you can use it in this case. This method allows you to handle requests with headers. The standard headers you can get by well-known names like request.Headers.Referrer
or request.Headers.Authorization
. However, if you created custom headers, you need to get non-validated headers by key request.Headers.NonValidated["Hello"]
. In the response block, you can get the returned content. You can convert it to any convenient format as you do with each HTTP call. And finally, let's add a call to the HTTP request:
using var observer = new HttpRequestsObserver();
using (DiagnosticListener.AllListeners.Subscribe(observer))
{
var client = new HttpClient();
client.DefaultRequestHeaders.Add("Hello", "World");
await client.GetStringAsync("https://catfact.ninja/fact");
}
Let's check this out.
As you can see, this implementation can be helpful for any diagnostic and handling of HTTP requests from one place.
I hope this article was helpful to you. See you in the following articles. Happy coding!
The source code you can find by this link.