Download the source at: Com.Hertkorn.TaskBasedIHttpAsyncHandler
If you want to serve up files or other content that has not much in common with the webforms or mvc engine, you should serve them using http handlers. The corresponding interface is IHttpHandler. They are light-weight and give great performance, especially when combined with IIS 7.
The basics - registering a custom handler
Http handler can be registered on multiple levels within IIS. One of the most common one is within the confines of the web application. That is done using web.config. If you have a handler defined in the class Com.Hertkorn.TaskBasedIHttpAsyncHandler.Www.Handlers.SampleAwaitHttpAsyncHandler which is in the dll Com.Hertkorn.TaskBasedIHttpAsyncHandler.Www use the following syntax:
-
<configuration>
-
<system.webServer>
-
<handlers>
-
<add name="testAwait" path="testAwait.axd" type="Com.Hertkorn.TaskBasedIHttpAsyncHandler.Www.Handlers.SampleAwaitHttpAsyncHandler, Com.Hertkorn.TaskBasedIHttpAsyncHandler.Www" verb="*"/>
-
</handlers>
-
</system.webServer>
-
</configuration>
Make sure you understand the ramifications of the verb setting, before you copy and paste the above code.
Now the http handler listens under the URL testAwait.axd for incoming requests.
Taking it up a notch - async
Using async handlers, when done correctly, will offload IO bound work to IO completion ports and therefore will free up the http pipeline threads to work on other requests. In high-stress situations this is crucial in order to achieve high throughput. IHttpAsyncHandler though is not for every situation, a introduction into the advantages and disadvantages can be found on msdn.
The original interface for an async IHttpHandler is called IHttpAsyncHandler and is implementing like this:
-
public abstract class HttpAsyncHandler : IHttpAsyncHandler
-
{
-
public abstract bool IsReusable { get; }
-
-
IAsyncResult IHttpAsyncHandler.BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
-
{
-
// ...
-
}
-
-
void IHttpAsyncHandler.EndProcessRequest(IAsyncResult result)
-
{
-
// ...
-
}
-
-
void IHttpHandler.ProcessRequest(HttpContext context)
-
{
-
// ...
-
}
-
}
Of course this is not an easy and "modern" interface into the async world. Nowadays we are trying to expose async operations using the Task class. So ideally an asynchronous handler would look something like this:
-
public class SampleTaskHttpAsyncHandler : HttpAsyncHandler
-
{
-
public override Task ProcessRequestAsync(HttpContextBase context)
-
{
-
{
-
var data = DateTime.Now.ToString("hh:MM:ss,fff");
-
Thread.Sleep(1000);
-
context.Response.Output.Write("<html><body><p>start: ");
-
context.Response.Output.Write(data);
-
context.Response.Output.Write("<br/>done: ");
-
context.Response.Output.Write("{0:hh:MM:ss,fff}", DateTime.Now);
-
context.Response.Output.WriteLine("</p>");
-
context.Response.Output.Write("</body></html>");
-
});
-
}
-
-
public override bool IsReusable
-
{
-
get
-
{
-
return true; // Not keeping any state in this handler
-
}
-
}
-
}
or even better using the new async await syntax introduced in .NET 4.5:
-
public class SampleTaskHttpAsyncHandler : HttpAsyncHandler
-
{
-
public override async Task ProcessRequestAsync(HttpContextBase context)
-
{
-
var data = DateTime.Now.ToString("hh:MM:ss,fff");
-
await Task.Delay(1000);
-
context.Response.Output.Write("<html><body><p>start: ");
-
context.Response.Output.Write(data);
-
context.Response.Output.Write("<br/>done: ");
-
context.Response.Output.Write("{0:hh:MM:ss,fff}", DateTime.Now);
-
context.Response.Output.WriteLine("</p>");
-
context.Response.Output.Write("</body></html>");
-
}
-
-
public override bool IsReusable
-
{
-
get
-
{
-
return true; // Not keeping any state in this handler
-
}
-
}
-
}
In order to achieve that we will implement an abstact HttpAsyncHandler base class that will handle all the ugly continuation handling for us and will present us with a simple Task based mechanism.
The implementation
-
public abstract class HttpAsyncHandler : IHttpAsyncHandler
-
{
-
// In very high performance situations you might want to change the argument to HttpContext
-
// and save one object allocation per request.
-
public abstract Task ProcessRequestAsync(HttpContextBase context);
-
-
public abstract bool IsReusable { get; }
-
-
IAsyncResult IHttpAsyncHandler.BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
-
{
-
if (cb == null)
-
{
-
}
-
-
var task = CreateTask(context);
-
-
task.ContinueWith((ar, state) => (state as AsyncCallback)(ar), cb);
-
-
if (task.Status == TaskStatus.Created)
-
{
-
// Got a cold task -> start it
-
task.Start();
-
}
-
-
return task;
-
}
-
-
void IHttpAsyncHandler.EndProcessRequest(IAsyncResult result)
-
{
-
var task = result as Task;
-
-
// Await task --> Will throw pending exceptions
-
// (to avoid an exception being thrown on the finalizer thread)
-
task.Wait();
-
-
task.Dispose();
-
}
-
-
void IHttpHandler.ProcessRequest(HttpContext context)
-
{
-
var task = CreateTask(context);
-
-
if (task.Status == TaskStatus.Created)
-
{
-
task.RunSynchronously();
-
}
-
else
-
{
-
task.Wait();
-
}
-
}
-
-
private Task CreateTask(HttpContext context)
-
{
-
-
if (task == null)
-
{
-
}
-
-
return task;
-
}
-
}
The trick is to use the ContinueWith in order to complete the request after the actual task is done.
Download the source with sample at: Com.Hertkorn.TaskBasedIHttpAsyncHandler


