Implementing an Asynchronous HttpModule

When I was researching REST and writing my part of Scott's and my book on REST (Effective REST Services via .NET: For .NET Framework 3.5), on page 234 I mentioned that the authentication/authorization module I'd written should be asynchronous and that someday I'd write an asynchronous version. Thanks to reader Carl asking me to come through on my promise, I've completed the asynchronous version. Actually, I refactored the synchronous version as well so that both implementations can share the code that actually deals with authentication and authorization. If you've read the book and have downloaded the source (which I updated, downloadable from here), you can download the AuthModule.cs files themselves for Chapters 6-8 here.

I noticed a distinct lack of example implementations on the Web while deciding how I wanted to implement my version of the asynch module. Jeff Prosise has the most-referenced version (available here), but this wasn't complete enough for my needs. Jeff's version uses FileStream.BeginWrite to generate the necessary IAsyncResult object. In my case, I couldn't use that (of course) since my module is performing authentication and authorization for RESTful services.

Jeff still provides a hint, though. In his article he shows an asynchronous HttpHandler, and the code for that handler implements IAsyncResult. I wanted a touch more functionality--a boolean return value and exception support--so I modified his work to look like the following:

/// <summary>
/// A class containing an IAsyncResult implementation
/// that wraps a ManualResetEvent. Note this is  relatively
/// simple implementation. A more complete (generic)
/// implementation is available by Jeffrey Richter, found at
/// http://msdn.microsoft.com/en-us/magazine/cc163467.aspx.
/// My thanks to Jeff Prosise for the basis of this
/// implementation.
/// </summary>
internal class AsyncAuthResult : IAsyncResult
{
    private AsyncCallback _callback;
    private object _state;
    private ManualResetEvent _event;
    private bool _completed = false;
    private bool _result = false;
    private object _lock = new object();
    private Exception _ex = null;

    public AsyncAuthResult(AsyncCallback callback,
            object state)
    {
        _callback = callback;
        _state = state;
    }

    public Object AsyncState
    {
        get { return _state; }
    }

    public bool CompletedSynchronously
    {
        get { return false; }
    }

    public bool IsCompleted
    {
        get { return _completed; }
    }

    public WaitHandle AsyncWaitHandle
    {
        get
        {
            lock (_lock)
            {
                if (_event == null)
                    _event = new ManualResetEvent(IsCompleted);
                return _event;
            }
        }
    }

    public void CompleteCall(bool result, Exception exception)
    {
        // Passing null for exception means no error occurred
        _ex = exception;

        // Cache result
        _result = result;

        lock (_lock)
        {
            _completed = true;
            if (_event != null)
                _event.Set();
        }

        if (_callback != null)
        {
            // Invoke callback...
            _callback(this);
        }
    }

    public bool EndInvoke()
    {
        // Operation is done...if an exception occured, throw it
        if (_ex != null) throw _ex;

        return _result;
    }
}

I then created an "authentication manager" that implements the "begin" and "end" methods I'd need in my module:

internal sealed class AuthenticationManager
{
    const string BasicAuthProtocol = "basic";
    HttpContext _context = null;

    public AuthenticationManager(HttpContext currentContext)
    {
        _context = currentContext;
    }

    public bool AuthenticateUser()
    {
        return InternalAuthenticateUser();
    }

    public IAsyncResult BeginAuthenticateUser(AsyncCallback callback,
                                              Object state)
    {
        // Create IAsyncResult object identifying the 
        // asynchronous operation.
        AsyncAuthResult ar = new AsyncAuthResult(callback, state);

        // Create a new thread to perform the operation...note we can't
        // use a thread from the thread pool as this defeats the purpose
        // of the asynchronous implementation (we're trying to leave
        // *those* threads alone for ASP.NET to use to service incoming
        // requests).
        Thread worker = new Thread(this.InternalAuthenticateUserAsync);
        worker.Start(ar);

        return ar;  // Return the IAsyncResult to the caller
    }

    public bool EndAuthenticateUser(IAsyncResult asyncResult)
    {
        // We know that it's really an AsyncAuthResult object
        AsyncAuthResult ar = asyncResult as AsyncAuthResult;
        return ar.EndInvoke();
    }

    private void InternalAuthenticateUserAsync(Object asyncResult)
    {
        // We know that it's really an AsyncAuthResult object
        AsyncAuthResult ar = asyncResult as AsyncAuthResult;

        // Perform the operation..if successful set the result. If
        // not, record the exception.
        try
        {
            bool authorized = this.InternalAuthenticateUser();
            ar.CompleteCall(authorized, null);
        }
        catch (Exception ex)
        {
            ar.CompleteCall(false, ex);
        }
    }

    private bool InternalAuthenticateUser()
    {
        // Authorize the user (using HttpContext for Authorization
        // header access) and return success/failure.
    }
}

The module itself then performs the typical asynchronous event connections. In the "begin" authentication event, I create an instance of my authentication manager, call its "begin" method (which spins up a new thread...more in a moment on that), and waits for authentication to complete. Upon completion, it calls the authentication manager's "end" method to retrieve success/failure and proceeds from there:

public class AsyncAuthModule : IHttpModule
{
    #region Fields

    // We *must* cache the context. When the "end" method is
    // invoked, a CLR thread will be executing (versus an IIS
    // thread), so there is no "current" context available
    // for the thread to use. That is, HttpContext.Current
    // will return null. We can cache the context in this manner
    // even though three different threads will eventually
    // use this object since the runtime suspends this module
    // invocation entirely and restarts it in the "end" 
    // method.
    HttpContext _context = null;

    // We'll cache this as well so we can call its "end"
    // method.
    AuthenticationManager _authMgr = null;

    #endregion

    #region IHttpModule Members

    public void Dispose()
    {
    }

    public void Init(HttpApplication application)
    {
        // Hook authentication
        application.AddOnAuthenticateRequestAsync(
            new BeginEventHandler(BeginAuthenticateRequestHandlerExecute),
            new EndEventHandler(EndAuthenticateRequestHandlerExecute)
        );
    }

    #endregion

    #region Event handlers

    IAsyncResult BeginAuthenticateRequestHandlerExecute(Object source,
        EventArgs e, AsyncCallback cb, Object state)
    {
        // Pull the current context
        HttpApplication application = source as HttpApplication;
        _context = application.Context;

        // Create an instance of the authentication manager and invoke
        // its asynchronous method.
        _authMgr = new AuthenticationManager(_context);
        return _authMgr.BeginAuthenticateUser(cb, state);
    }

    void EndAuthenticateRequestHandlerExecute(IAsyncResult ar)
    {
        // Collect authentication/authorziation result...if true, the
        // user is good to go. If false, the context will contain the
        // appropriate response code and headers.
        bool authorized = _authMgr.EndAuthenticateUser(ar); // can throw...

        // If the context contains anything but "OK" we know
        // to terminate the request. The appropriate headers
        // will already be set up by the authentication manager
        // object.
        if (!authorized ||
            _context.Response.StatusCode != (Int32)HttpStatusCode.OK)
        {
            // End the request
            _context.Response.End();
        }
    }

    #endregion
}

As far as I can tell, this is the only example of a complete asynchronous HttpModule that fully implements IAsyncResult for a custom processing workflow that you can search (at least that I could find). I'm not sure why it's so hard to find others, but hopefully this helps someone else. Laughing With this example, and with Jeff's you should be able to get a working asynchronous HttpModule up and running.

One "gotcha" I'd mention is don't fall into the trap I fell into, which is using the wrong callback. That is, ASP.NET provides you with an instance of AsyncCallback as a parameter to the module's "begin" method. That callback is the one your processing should invoke so that ASP.NET knows your workflow is complete. It then will call the module's "end" method for you. Initially I created a new instance of AsyncCallback and passed that in, bypassing ASP.NET entirely. That resulted in the server hanging the page since I broke the HTTP pipeline. (In my best Homer Simpson voice, "Doh!"). In code, the following is correct:

return _authMgr.BeginAuthenticateUser(cb, state);

The following is most assuredly NOT correct:

return _authMgr.BeginAuthenticateUser(
        new AsyncCallback(this.EndAuthenticateRequestHandlerExecute), state);

Now, as for threading. A good question to ask is "why spin up your own thread and not use the thread pool?" The reason is asynchronous modules (and handlers/pages as well) all are assigned CLR threads from the AppDomain's thread pool when IIS hands off the request. If we were to consume one of the AppDomain's pooled threads, we're defeating the purpose of the asynchronous implementation entirely. This means you should not only avoid queueing a thread pool task but also should not use Delegate.BeginInvoke (a trick I often use to gain asynchronous behavior in other apps). Spinning up a new thread is the only alternative you have when you create your own custom asynchronous module/handler/page workflow objects. I've not cracked open other CLR implementations (like FileStream.BeginWrite), but hopefully if I did I'd find those aren't using the AppDomain's thread pool as well.


Tags: , , , ,
Categories: .NET General | ASP.NET | RESTful Services

9 Comments
Actions: E-mail | Permalink | Comment RSSRSS comment feed