mod_deflate и отдача подготовленного контента в Apache

Небольшая заметка на тему экономии трафика, mod_deflate и всего такого.

Ковыряю последние дни XMPP Server Scanner, тот генерирует на выходе пачку HTML-страничек (ну и XML-список впридачу). Глядя на то, как на списке в 500 серверов получил в итоге HTML’ку на 600 КБайт, начал задумываться о том, что неплохо было бы сжимать это все в процессе передачи – планируется обработать большой список и там счет будет уже на мегабайты. У сканера есть штатная функция создания gzip-файлов для HTML’а; начал разбираться, как это можно использовать.

В файле, занимающемся созданием всех этих файлов, есть небольшая инструкция на эту тему:

The files are static and can be very verbose so, you might want to cache and compress them.

You can use dynamic compression or static compression, the dynamic compression compreses the page on every request, the static one compress the page on the first request and stores it to serve it from that moment.

Since the webpage content doesn't change, you might prefer static compression to lower the CPU load.

Just in case your webserver doesn't support static compression, I have added the compress option. This option, generates gzipped files, so you can serve them specifing the enconding as gzip.

Then you have to configure your server.

On Apache, static compresion can be achieved combining mod_deflate and mod_cache. Or, if they aren't available, generating the gzipped page and configuring Apache like:

AddEncoding x-gzip .gz
    <IfModule mod_rewrite.c>
        RewriteEngine On
        RewriteCond %{HTTP:Accept-Encoding} gzip
        RewriteCond %{REQUEST_FILENAME}.gz -f
        RewriteRule ^(.+).html$ $1.$2.gz [L]
    </IfModule>

Собственно, что это все значит – более понятным языком.

Динамическая компрессия получается с помощью одного только mod_deflate. Загружаем модуль в Апач; в конфиге модуля дефолтно перечислены всякие там html/css/js/xml – все, теперь эти файлы будут прозрачно сжиматься перед передачей и разжиматься на стороне клиента. Но это будет делаться КАЖДЫЙ запрос (с поправкой на то, что клиент может получить ответ, что контент не модифицировался и не запрашивать содержимое вообще). Т.е., трафик экономим, но каждый раз напрягаем проц. В моем случае это уже работало – модуль был загружен, так что по факту из 600 КБ HTML’ки оставалось только 30. Но если сканер генерирует gzip’ы – надо это использовать.

Статическая компрессия подразумевает, что контент один раз сжимается и дальше отдается только сжатый. Тут варианты: как упомянуто в инструкции выше, можно использовать mod_deflate (который уже есть) + mod_cache (который и будет хранить сжатый контент какое-то время, не дергая каждый раз mod_deflate). Либо другой вариант – использовать предварительное сжатие в самом сканере, для чего в него и была добавлена автором специальная опция. Мне такой вариант был более интересен: опция сканера – лишь опция, а добавление mod_cache – лишний ненужный компонент у веб-сервера.

Добавил кусок конфига в секцию отдачи страничек из каталога сканера, но сходу оно не заработало – предлагало скачать сжатый вариант. В конфиге выше ошибка, кстати – $2 не обозначен в условии RewriteRule.

В итоге, как выяснилось, споткнулся на двойной компрессии – мои уже сжатые файлы сжимались повторно в mod_deflate. На “той” стороне первый слой прозрачно распаковывался, а вот на выходе-то все равно gzip – и уже его браузер предлагал скачать. До того, как это понял (собственно, к пониманию подтолкнула найденная в сети подобная ситуация), потратил довольно много времени. Итоговый вариант конфига:

<Directory "/home/xss/servers/">
DirectoryIndex servers.html.gz servers.html
# Offloading of servers list serving
# if we have gzipped version of page - send it (so, no need to use mod_deflate and compress pages on every request)
# if no - send ordinary page
AddEncoding x-gzip .gz
RewriteEngine On
RewriteCond %{HTTP:Accept-Encoding} gzip
RewriteCond %{REQUEST_FILENAME}.gz -s
RewriteRule ^(.+).html$ $1.html.gz [L]
# this is the key: don't compress already compressed pages
RewriteRule ".gz$" "-" [T=text/html,E=no-gzip:1]
<FilesMatch "(\.gz)$">
Header append Content-Encoding gzip
</FilesMatch>
Order allow,deny
Allow from all
</Directory>

Основные моменты:

  • В качестве индексной страницы отдаем как gzip, так и обычную. Не будет gzip’а – будет fallback на несжатую.
  • Далее идет ряд проверок: если клиент заявляет поддержку gzip-сжатия и если у нас есть gz-версия запрошенного файла – значит, вместо обычного файла отдаем gz. Соответственно, если в каталоге не будет gzip-файлов – возвращаемся к отдаче обычных – и все равно через mod_deflate с экономией трафика, но ценой возросшей нагрузки на процессор.
  • Ключевой момент: для gz-файлов в каталоге отключаем сжатие в mod_deflate и ставим соответствующий хедер о том, что это уже gzip, но внутри это все тот же text/html.

Одна мысль про “mod_deflate и отдача подготовленного контента в Apache”

Добавить комментарий