HttpRequest Interceptor

 

~ 3 min read.

HttpRequest Interceptor is an attempt for an elegant and easily configurable solution for intercepting the HTTP requests.

Motivation for the Interceptor

In scenarios where we have multiple APIs communicating between each other the cases where we’d like to be able to mock the external dependency are not rare. That dependency might be an actual 3rd party API, or an internal API that our service consume in any case there are cases where we just don’t care about them and just want an OK response, or maybe we’d like to have that service start returning 401s or 404s. Creating mocks in your unit tests is easy enough, but what about if we want to change the behavior of external API in QA environment?

The HttpClient allow us to implement a custom handler by implementing DelegatingHandler class, which we can than register so it becomes a part of request processing pipeline. The options pattern gives us a way to load and reload configurations with easy. Combination of two gave birth to HttpRequestInterceptor, a simple to use, yet highly configurable handler that can be a built in part of your HttpClient instance, since without configuration it will do nothing, so it would be safe to have it running always in any environment.

How to use

The HttpRequestInterceptor can be registered using the built in AspNetCore DI mechanism.

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<Collection<HttpInterceptorOptions>>(Configuration.GetSection("HttpInterceptorOptions"));

    services.AddTransient<InterceptingHandler>();
    
    services.AddHttpClient("GitHubClient", client =>
    {
        client.BaseAddress = new Uri("https://api.github.com/");
        client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
        client.DefaultRequestHeaders.Add("User-Agent", "HttpRequestInterceptor-Test");
    })
    .AddHttpMessageHandler<InterceptingHandler>();

    services.AddMvc();
}

Options - HttpInterceptorOptions schema

This class represent a configuration entry that belong to a specific route or path. It consist of:

  • MethdoName: Represents one of the standard HTTP methods and it is a required value.
  • Path: Represents a route or a specific path that’s going to be intercepted. It allows usage of any “*”. The parameter is required.
  • ReturnStatusCode: Represent response status code. The parameter is required and it must belong to one of standard status codes that can be found in HttpStatusCode enum.
  • ReturnJsonContent: Represents the serializes response content. It’s an optional parameter.

Examples

Let’s say that we want to override the behavior of a specific GET request. The request we want to override is: /api/product/{id} where id = 2 and we want the response to be 200 with some predefined content. To achieve that we would specify the configuration below:

"HttpInterceptorOptions": [
{
    "MethodName": "GET",
    "Path": "api/product/2",
    "ReturnStatusCode": 200,
    "ReturnJsonContent": "{ "Id": 1, "Name": "Product1" }"
}

The example above will only work for GET request where prameter id is 2, if we want to intercept all GET request no matter the value of id than we can use this configuration:

"HttpInterceptorOptions": [
{
    "MethodName": "GET",
    "Path": "api/product/*",
    "ReturnStatusCode": 200,
    "ReturnJsonContent": "{ "Id": 1, "Name": "Product1" }"
}

The asterisk * obviously means any, and you can have multiple * per configuration entry, so the below configuration will also work:

"HttpInterceptorOptions": [
{
    "MethodName": "GET",
    "Path": "api/product/*/locations/*",
    "ReturnStatusCode": 200,
    "ReturnJsonContent": "{ "Id": 1, "Name": "Product1" }"
}

The configuration above will intercept routes similar to these:

  • api/products/2/locations/1
  • api/products/2/locations/london
  • api/products/car/locations/4

Interceptor doesn’t care about the type behind the * it’s all string anyway and you who configures the interceptor should configure it properly.

Returning negative statuses

Example below will demonstrate how to intercept a route and return a response with 404 status:

"HttpInterceptorOptions": [
{
    "MethodName": "GET",
    "Path": "api/product/42",
    "ReturnStatusCode": 404
}

As you can see the response content has not been provided since we do not need it. The same approach can be used for other negative statuses like: 401 or 400 or even 500.

Conclusion

That’s all. I believe it should be straight forward and easy to use and above else a very useful utility that can give you that extra level of extendability without need for a code change, proxy or a fake API implementation.

The source code can be found on GitHub feedback of any kind is very welcome.