How to GET aHEAD with MessageHandlers!

Published on May 22, 2011

It appears I need to go on vacation more often.  I seem to get more chance to experiment.  One of my first discussions about RACK was with Mike Kelly where he suggested a simple solution to implementing HEAD across an API.  Simply use a RACK application to convert a HEAD request to a GET and then when the response comes back, drop the body.  Seeing as I am on a roll implementing obscure HTTP methods using HttpMessageHandler I decided to give it a try.

If you wondering what HEAD does, here is the official explanation.  It can be useful to allow clients to check to see how big a response is going to be before committing to the request.   It can also be used to check if a client is allowed to access some content prior to the user requesting it.

The HTTPMessageHandler looks like,

    public class HeadMessageHandler : DelegatingChannel {
        public HeadMessageHandler(HttpMessageChannel innerChannel)
            : base(innerChannel) {
        }
    <span class="kwrd">protected</span> <span class="kwrd">override</span> Task&lt;HttpResponseMessage&gt; SendAsync(
                                        HttpRequestMessage request, 
                                        CancellationToken cancellationToken) {

        <span class="kwrd">if</span> (request.Method == HttpMethod.Head) {
            request.Method = HttpMethod.Get;
            <span class="kwrd">return</span> <span class="kwrd">base</span>.SendAsync(request, cancellationToken)
                .ContinueWith&lt;HttpResponseMessage&gt;(task =&gt; {
                    var response = task.Result;
                    response.RequestMessage.Method = HttpMethod.Head;
                    response.Content = <span class="kwrd">new</span> HeadContent(response.Content);
                    <span class="kwrd">return</span> task.Result;
                });
            
        }

        <span class="kwrd">return</span> <span class="kwrd">base</span>.SendAsync(request, cancellationToken);
    }
}</pre>

 

If the request is a HEAD, I simply change the method to a GET and pass the request along.  The ContinueWith() method allows me to provide a function that will be executed when the response returns.  What I need to do here is to remove the Content.  However, I cannot just set the Content to null as I want to return the Content headers.  In order to do this, I created a new HeadContent class that returns no body but holds the Content Headers. 

The HeadContent class is simply,

    public class HeadContent : HttpContent {

        public HeadContent(HttpContent content) {
            CopyHeaders(content.Headers, Headers);
        }

        protected override Task SerializeToStreamAsync(
                                                Stream stream, 
                                                TransportContext context) {
            return new Task(() => { });
        }

        protected override void SerializeToStream(
                                                Stream stream, 
                                                TransportContext context) {
        }

        protected override bool TryComputeLength(out long length) {
            length = -1;
            return false;
        }

        private static void CopyHeaders(HttpContentHeaders fromHeaders, 
                                        HttpContentHeaders toHeaders) {
         
            foreach (KeyValuePair<string, IEnumerable<string>> header in fromHeaders) {
                toHeaders.Add(header.Key, header.Value);
            }
        }
    }

 

As with the Trace message handler it can be added easily to your config by doing

var config = HttpHostConfiguration.Create()
                .AddMessageHandlers(new[] { typeof(HeadMessageHandler) }); 

Once this is done, all of your endpoints now implement HEAD, assuming of course that there is a GET method!   As you can probably tell this is more of an experiment than an attempt to produce a piece of production code, but hopefully, as an example, it will give people a better idea of what can be done with HttpMessageHandlers.