How diagnostic HTTP requests and not only. Part 2.

Serhii Korol - Sep 17 '23 - - Dev Community

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();
    }
}
Enter fullscreen mode Exit fullscreen mode

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();
    }
}
Enter fullscreen mode Exit fullscreen mode

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)!;
        }
    }
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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");
}
Enter fullscreen mode Exit fullscreen mode

Let's check this out.

result

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.

Buy Me A Beer

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .