Custom IIS module conflicting with gzip
As an experiment I’ve been toying with an idea of creating an IIS managed module to modify CSS files on fly. The backstory is that all out web applications keep a shared and common version number which we append to each JS, CSS and image reference (in HTML) and I wanted to modify the actual content of the CSS to also append the version number to image references in CSS. So, for instance:
span.warning { background-image: url(warning-icon.png) }
should become:
span.warning { background-image: url(warning-icon.png?123开发者_StackOverflow社区) }
Now, I know that there are many different approaches to this problem (and some are perhaps better), but I was wondering if anybody can answer my question related to the one I’ve been playing with.
So far I've learned that a managed HTTP module can't directly modify the response stream (I think) and that a correct way is to append an output filter steam that does the work. I've written the following test module:
public class HttpCSSModule : IHttpModule
{
public void Init(HttpApplication httpApplication)
{
httpApplication.BeginRequest += new EventHandler(
(s, e) => AttachFilter((HttpApplication)s));
}
private void AttachFilter(HttpApplication httpApplication)
{
HttpRequest httpRequest = httpApplication.Context.Request;
HttpResponse httpResponse = httpApplication.Context.Response;
if (httpRequest.Path.EndsWith(".css", StringComparison.CurrentCultureIgnoreCase))
{
if (!string.IsNullOrEmpty(httpRequest.Url.Query))
{
httpResponse.Filter = new CSSResponseStreamFilter(
httpResponse.Filter, httpRequest.Url.Query);
}
}
}
public void Dispose()
{
}
private class CSSResponseStreamFilter : Stream
{
private Stream inner;
private string version;
private MemoryStream responseBuffer = new MemoryStream();
public CSSResponseStreamFilter(Stream inner, string version)
{
this.inner = inner;
this.version = version;
}
public override void Close()
{
if (responseBuffer.Length != 0)
{
string stylesheet = Encoding.ASCII.GetString(
responseBuffer.GetBuffer(), 0, (int)responseBuffer.Length);
// crude, just testing
string versionedStylesheet = stylesheet.
Replace(".png", ".png" + version).
Replace(".jpg", ".jpg" + version).
Replace(".gif", ".gif" + version);
byte[] outputBytes = Encoding.ASCII.GetBytes(versionedStylesheet);
innerStream.Write(outputBytes, 0, outputBytes.Length);
}
innerStream.Close();
}
public override void Write(byte[] buffer, int offset, int count)
{
responseBuffer.Write(buffer, offset, count);
}
// other Stream members
}
}
The module works, but not always and there are things I don't understand. The biggest problem is that the module does not work when the static file compression is turned on. When the the static file compression is turned is on, the first request to a CSS files serves the file as usually, but presumably IIS keeps the gzipped version around and in any subsequence request my custom stream is passed the gzipped stream. I have not found a way to detect it, but there is probably a deeper problem in that I don't really perhaps understand how IIS modules are supposed to work. It seems wrong that my module should make any assumption on another module, or perhaps at least, one should be able to define the order in which they handle request in the IIS config. However, this does not seem to make sense because each module can register to handle any events in the request lifecycle.
So if I had to reformulate my thoughts to more refined questions, the questions would be:
How can I make sure my post processing / module is called after the file is read and server by the StaticFileModule module but before the StaticCompressionModule? And does the question even make sense? Based on observations above, it seems a bit contradictory.
There is configuration for static file compression to include particular file or mime types, I forget which one. If you want to use static compression it needs to only be on files that are actually static. The fact that you would be rewriting them with an HttpModule means they are no longer static. So, you could use dynamic compression for those file types, and as you might expect, you will want to make sure your dynamic compression will include the appropriate mime types or file extensions.
精彩评论