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<HttpResponseMessage> 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<HttpResponseMessage>(task => { 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.