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) {
}
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken) {
if (request.Method == HttpMethod.Head) {
request.Method = HttpMethod.Get;
return base.SendAsync(request, cancellationToken)
.ContinueWith<HttpResponseMessage>(task => {
var response = task.Result;
response.RequestMessage.Method = HttpMethod.Head;
response.Content = new HeadContent(response.Content);
return task.Result;
});
}
return base.SendAsync(request, cancellationToken);
}
}
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.