How diagnostic HTTP requests and not only. Part 2.
Tags: csharp, http, dotnet, programming
Published: 2023-09-17 08:50:37

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:

```C#
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.

```C#
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`.

```C#
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.

```C#
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.

```C#
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:

```C#
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.


![result](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cb65p6yj3lhtj7e2eyb7.png)

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](https://github.com/SergKorol/EventListenerSample).

<a href="https://www.buymeacoffee.com/b9z99kntvtQ" target="_blank"><img src="http://dartfordwaffler.co.uk/wp-content/uploads/2021/03/buy-me-a-beer.jpg" alt="Buy Me A Beer" style="height: 60px !important;width: 317px !important;" ></a>