The conception, birth, and first steps of an application named Charlie

Subscribe: Atom or RSS

The Trouble with Filters

by Alister Jones (SomeNewKid)

At this stage in the life of Charlie, I was building the classes that would allow its Plugins to become involved in all the stages of processing an incoming request. In my last entry, I explained the trouble with getting a plugin’s Modules to participate in the first stages of processing. I then turned my attention to getting a plugin’s Filters to participate in the final stages of processing.

ASP.NET allows an application to add a single Filter to the HttpResponse object. As you might expect, this allows the application to filter (change) the response. This is easier said than done.

To start with, the response of a webpage is not the nice string of text that we envisage. Rather, the response is a byte stream. If we want to filter the HTML (for example, to remove any profanity), then we have to convert the byte stream to a string of text, work with that text, and then convert it back to a byte steam again. And that’s the easy part.

The harder part is that we can only attach one filter to the HttpResponse object. Yet, the goal for Charlie is that each separate Plugin is to be given the chance to filter the output. For example, one of the plugins for Charlie will be a Smartypants plugin, which filters the output to provide typographically correct text. Another required filter is my own RootRelativePaths filter. The problem to combine several different filters into a single filter can be solved by having one filter “wrap” another filter. This is the Decorator pattern that Charlie implements in the CombineFilters method of its FilterFactory class.

By a combination of the Factory Pattern and the Decorator Pattern, Charlie was able to give each and every plugin the opportunity to filter the HttpResponse output. Problem solved. Or so it seemed.

Charlie does not handle requests only for HTML webpages. For reasons I will come to when we discuss friendly URLs, Charlie handles all requests to a website, including requests for CSS stylesheets, JavaScript files, JPEG image files, and so on. For this reason, the IWebFilter interface looks like this:

public interface IWebFilter
{
    String[] GetResponseTypes();
    String Process(Object sender, EventArgs e);
}

The GetResponseTypes method allows the Filter to describe what response types it wishes to filter. The Smartypants filter will want to filter the response type of “text/html”. The RootRelativePaths filter will want to filter the response types of “text/html” and “text/css”. The filters will not apply if the response is of some other type. The text-based Smartypants filter cannot filter a response type of “image/jpeg”, for example, so it will not register itself to handle that response type.

That seems all fine and dandy, and it worked swell when the request was for a webpage. But this did not work when the request was for a .css file. If a filter touches a request for a .css file, then the response will report that it contains 4kb, but the response will in fact be empty. Remove the filter, and the response will be fine. It does not matter if the .css request is handled by ASP.NET’s own StaticFileHandler or by a custom CssHandler; touch the response with a filter and it destroys the response. Flushing the response does not help, nor does yelling expletives—and I tried both.

In the end I had to take a pragmatic way out. I cannot see the need to filter the output of a .css stylesheet request, so I simply documented my decision and pressed on.

// *******************************************************
//  GetResponseTypes Method
//
/// <summary>
/// </summary>
/// <returns></returns>
/// <notes>
/// While theoretically we should be able to filter the 
/// response of type "text/css", stylesheets fail to be 
/// returned if they are filtered. So, we do not specify 
/// "text/css" as a handled response type. We cannot 
/// foresee any need to ever use a root-relative path in 
/// an external stylesheet, so we'll just let this slide.
/// </notes>
//
// *******************************************************
public String[] GetResponseTypes()
{
    String[] types = new String[] { 
        "text/html"
    };
    return types;
}

By this stage I had wrestled with the ASP.NET pipeline, and come up with an IWebModule interface and an IWebFilter interface that would allow plugins to hook into the events that occur before and after the IHttpHandler (usually an .aspx webpage) has executed. The next step was to find a way for the plugins to “give” its modules and filters to the PluginManager.

by Alister Jones | Next up: The Plugin Interface

0 comments

----