Пользовательский модуль IIS, конфликтующий с gzip

#iis-7 #httpmodule

#iis-7 #httpmodule

Вопрос:

В качестве эксперимента я поиграл с идеей создания управляемого IIS модуля для изменения CSS-файлов «на лету». Предыстория заключается в том, что все веб-приложения сохраняют общий номер версии, который мы добавляем к каждой ссылке на JS, CSS и изображение (в HTML), и я хотел изменить фактическое содержимое CSS, чтобы также добавлять номер версии к ссылкам на изображения в CSS. Так, например:

 span.warning { background-image: url(warning-icon.png) }
  

должно стать:

 span.warning { background-image: url(warning-icon.png?123) }
  

Теперь я знаю, что существует много разных подходов к этой проблеме (и некоторые, возможно, лучше), но мне было интересно, может ли кто-нибудь ответить на мой вопрос, связанный с тем, с которым я играл.

До сих пор я узнал, что управляемый HTTP-модуль не может напрямую изменять поток ответов (я думаю) и что правильный способ — добавить выходной фильтр steam, который выполняет эту работу. Я написал следующий тестовый модуль:

 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

    }

}
  

Модуль работает, но не всегда, и есть вещи, которые я не понимаю. Самая большая проблема заключается в том, что модуль не работает при включенном статическом сжатии файлов. Когда включено статическое сжатие файла, первый запрос к CSS-файлам обслуживает файл как обычно, но, предположительно, IIS сохраняет архивированную версию, и при любом последующем запросе моему пользовательскому потоку передается архивированный поток. Я не нашел способа обнаружить это, но, вероятно, существует более глубокая проблема в том, что я, возможно, не совсем понимаю, как должны работать модули IIS. Кажется неправильным, что мой модуль должен делать какие-либо предположения о другом модуле, или, возможно, по крайней мере, нужно иметь возможность определять порядок, в котором они обрабатывают запрос в конфигурации IIS. Однако, похоже, это не имеет смысла, поскольку каждый модуль может зарегистрироваться для обработки любых событий в жизненном цикле запроса.

Итак, если бы мне пришлось переформулировать свои мысли в более утонченные вопросы, вопросы были бы:

Как я могу убедиться, что моя постобработка / модуль вызывается после того, как файл прочитан и отправлен на сервер модулем StaticFileModule, но до StaticCompressionModule? И имеет ли вообще смысл этот вопрос? Основываясь на приведенных выше наблюдениях, это кажется немного противоречивым.

Комментарии:

1. У меня такая же проблема, и я часами работаю над ее устранением. : (Не могли бы вы объяснить мне, как вы решили эту проблему, пожалуйста?

Ответ №1:

Существует конфигурация для статического сжатия файлов, включающая определенные типы файлов или mime, я забыл, какой именно. Если вы хотите использовать статическое сжатие, оно должно выполняться только для файлов, которые на самом деле статичны. Тот факт, что вы будете переписывать их с помощью HttpModule, означает, что они больше не являются статическими. Итак, вы могли бы использовать динамическое сжатие для этих типов файлов, и, как и следовало ожидать, вы захотите убедиться, что ваше динамическое сжатие будет включать соответствующие типы mime или расширения файлов.

Комментарии:

1. Вы правы. Это устраняет проблему, и на самом деле это более логично (это больше не статический файл). Это также показывает, что модуль динамического сжатия (compdyn.dll ) и модуль статического сжатия (compstat.dll ) отличаются не только списком типов, которые они обрабатывают. Они должны интегрироваться в конвейер IIS различными способами.