Web API – message handlers – usage

Web API – message handlers – usage

message handlers

The delegating handler in WebAPI is the most basic mechanism to intercept HTTP message lifecycle. We can find a very clear and useful visualisation of on WebAPI poster. We can see that message handlers is the first place in HTTP request processing which is able to read of modify the message. It is many cases when we would need to place some code before request will be executed and after. But first, we will introduce how to write that kind of handlers.

Create your own Web API delegating handler

There are two kind of that handlers:

  • global
  • route scoped

On each case we have to define our handler that extend DelegatingHandler class. If we would like to write custom behaviour to whole message processing we can extend HttpMessageHandler class and override method SendAsync. In our case we want to just intercept standard message processing.

public class CustomHandler : DelegatingHandler
{
    protected async override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        Debug.WriteLine("Process request");
        // Call the inner handler.
        var response = await base.SendAsync(request, cancellationToken);
        Debug.WriteLine("Process response");
        return response;
    }
}

As you can see in this code, we have a place for actions before request processing and after. We can operate although on request and response. We can also completely modify the behaviour of WebAPI. In our example we just write some messages to log during the processing. I want to warn you in this point that we should be fully aware of performance issues in that kind of handlers. All actions which we decide to put into handler, will be executed for each request. That’s why we shouldn’t write too complex processing here. We shouldn’t but we can if we have a good reason.

Global

Message handlers can be configured as a global, which means that they will be executed for each action in system.

GlobalConfiguration.Configuration
    .MessageHandlers
    .Add(new DateObsessedHandler());

Route scoped

Also we can define them specific to route simply by defining additional parameter during route creation.

IHttpRoute route = config.Routes.CreateRoute(
    routeTemplate: "api/MyRoute",
    defaults: new HttpRouteValueDictionary("route"),
    constraints: null,
    dataTokens: null,
    parameters: null,
    handler: new CustomHandler());

config.Routes.Add("MyRoute", route);

This is all options to create a message handler. This is really simple, but powerful mechanism.

Usage

In this part we will see some examples how we can use message handlers for common problems. Below I show only a sample implementation of this examples. Further details can be found in related blog posts.

Message logging

The most basic use of message handlers is logging information about requests.

Description: http://weblogs.asp.net/fredriknormen/log-message-request-and-response-in-asp-net-webapi

public class MessageLoggingHandler : MessageHandler
{
    protected override async Task IncommingMessageAsync(string correlationId, string requestInfo, byte[] message)
    {
        await Task.Run(() =>
            Debug.WriteLine(string.Format("{0} - Request: {1}rn{2}", correlationId, requestInfo, Encoding.UTF8.GetString(message))));
    }


    protected override async Task OutgoingMessageAsync(string correlationId, string requestInfo, byte[] message)
    {
        await Task.Run(() =>
            Debug.WriteLine(string.Format("{0} - Response: {1}rn{2}", correlationId, requestInfo, Encoding.UTF8.GetString(message))));
    }
}

Authentication

Other type of handlers can be a part of authentication functionality. We can check Autorization header globally for all requests.

Description: https://weblog.west-wind.com/posts/2013/Apr/30/A-WebAPI-Basic-Authentication-MessageHandler

public class BasicAuthenticationHandler : DelegatingHandler
{
    private const string WWWAuthenticateHeader = "WWW-Authenticate";

    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, 
    CancellationToken cancellationToken)
    {
        var credentials = ParseAuthorizationHeader(request);

        if (credentials != null)
        {
            var identity = new BasicAuthenticationIdentity(credentials.Name, credentials.Password);
            var principal = new GenericPrincipal(identity, null);

            Thread.CurrentPrincipal = principal;
            if (HttpContext.Current != null)
                HttpContext.Current.User = principal;
        }

        return base.SendAsync(request, cancellationToken)
            .ContinueWith(task =>
            {
                var response = task.Result;
                if (credentials == null && response.StatusCode == HttpStatusCode.Unauthorized)
                Challenge(request, response);

                return response;
            });
    }

    /// <summary>
    /// Parses the Authorization header and creates user credentials
    /// </summary>
    /// <param name="actionContext"></param>
    protected virtual BasicAuthenticationIdentity ParseAuthorizationHeader(HttpRequestMessage request)
    {
        string authHeader = null;
        var auth = request.Headers.Authorization;
        if (auth != null && auth.Scheme == "Basic")
            authHeader = auth.Parameter;

        if (string.IsNullOrEmpty(authHeader))
            return null;

        authHeader = Encoding.Default.GetString(Convert.FromBase64String(authHeader));

        var tokens = authHeader.Split(':');
        if (tokens.Length < 2)
            return null;

        return new BasicAuthenticationIdentity(tokens[0], tokens[1]);
    }


    /// <summary>
    /// Send the Authentication Challenge request
    /// </summary>
    /// <param name="message"></param>
    /// <param name="actionContext"></param>
    void Challenge(HttpRequestMessage request, HttpResponseMessage response)
    {
        var host = request.RequestUri.DnsSafeHost;                    
        response.Headers.Add(WWWAuthenticateHeader, string.Format("Basic realm="{0}"", host));
    }
}

Checking API keys

Sometimes when we publish API as a public service, it can be useful to add API key functionality to restrict access to our API.

Description: http://www.asp.net/web-api/overview/advanced/http-message-handlers

public class ApiKeyHandler : DelegatingHandler
{
    public string Key { get; set; }

    public ApiKeyHandler(string key)
    {
        this.Key = key;
    }

    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        if (!ValidateKey(request))
        {
            var response = new HttpResponseMessage(HttpStatusCode.Forbidden);
            var tsc = new TaskCompletionSource<HttpResponseMessage>();
            tsc.SetResult(response);    
            return tsc.Task;
        }
        return base.SendAsync(request, cancellationToken);
    }

    private bool ValidateKey(HttpRequestMessage message)
    {
        var query = message.RequestUri.ParseQueryString();
        string key = query["key"];
        return (key == Key);
    }
}

Requests rate limiting

Other functionality of API that suits well to message handlers is rate limiting. It is useful to protect API from to many requests made by single users, which may cause system delays.

Description: http://blog.maartenballiauw.be/post/2013/05/28/Throttling-ASPNET-Web-API-calls.aspx

public class ThrottlingHandler
    : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var identifier = request.GetClientIpAddress();

        long currentRequests = 1;
        long maxRequestsPerHour = 60;

        if (HttpContext.Current.Cache[string.Format("throttling_{0}", identifier)] != null)
        {
            currentRequests = (long)System.Web.HttpContext.Current.Cache[string.Format("throttling_{0}", identifier)] + 1;
            HttpContext.Current.Cache[string.Format("throttling_{0}", identifier)] = currentRequests;
        }
        else
        {
            HttpContext.Current.Cache.Add(string.Format("throttling_{0}", identifier), currentRequests,
                null, Cache.NoAbsoluteExpiration, TimeSpan.FromHours(1),
                CacheItemPriority.Low, null);
        }

        Task<HttpResponseMessage> response = null;
        if (currentRequests > maxRequestsPerHour)
        {
            response = CreateResponse(request, HttpStatusCode.Conflict, "You are being throttled.");
        }
        else
        {
            response = base.SendAsync(request, cancellationToken);
        }

        return response;
    }

    protected Task<HttpResponseMessage> CreateResponse(HttpRequestMessage request, HttpStatusCode statusCode, string message)
    {
        var tsc = new TaskCompletionSource<HttpResponseMessage>();
        var response = request.CreateResponse(statusCode);
        response.ReasonPhrase = message;
        response.Content = new StringContent(message);
        tsc.SetResult(response);
        return tsc.Task;
    }
}

https://github.com/stefanprodan/WebApiThrottle

Additional resources:

http://www.strathweb.com/2012/05/implementing-message-handlers-to-track-your-asp-net-web-api-usage/
http://blog.karbyn.com/index.php/message-handlers-in-web-api/

Photo source