なうびるどいんぐ

脳みそ常時-3dB

正しく圧縮、素早く送信、mod_deflateの設定方法

      2018/04/12    HimaJyun

このブログでは、帯域の削減とページ表示を高速化する目的でgzip圧縮して転送しています。

ただ、「Apache deflate 設定」とかで調べると「ん?、それ必要なの?/間違ってない?」と思う様な設定例が沢山ヒットします。

「自分の設定が完璧だ!」と主張するつもりはありませんが、少しでも正しい(と思える)情報を掲載したいので。

スポンサーリンク

圧縮して転送すると?

圧縮して転送するのはページの表示速度を向上する目的でもほぼ全方位からおススメされている方法です。

圧縮にCPUリソースを利用しますが、上り帯域の節約や転送に掛かる時間が短縮される事を考えるとむしろプラスです。(一般的に、ネットワークを含むI/O操作はCPUを遊ばせます)

この時代にmod_deflateが使えない鯖の方が少ないと思うので、出来る事ならばやっておいた方が良いでしょう。

良くある間違った例

以下の様な例が良くヒットしますが、間違ってますよ。

全部圧縮してる

<IfModule mod_deflate.c>
    <IfModule mod_filter.c>
        SetOutputFilter DEFLATE
        BrowserMatch ^Mozilla/4 gzip-only-text/html
        BrowserMatch ^Mozilla/4\.0[678] no-gzip
        BrowserMatch \bMSI[E] !no-gzip !gzip-only-text/html
        AddOutputFilterByType DEFLATE なんとか/かんとか
        AddOutputFilterByType DEFLATE かくかく/しかじか
    </IfModule>
</IfModule>

割と分かりづらいミス。

何が間違ってるかと言うと、SetOutputFilter DEFLATEを設定すると全てのコンテンツが圧縮されます。

つまり、後ろにあるAddOutputFilterByType無駄!!imageだろうがzipだろうがなんでもかんでも全部圧縮しちゃいます。

ただしこっちは正しい

以下のパターンはとても多いですね、このやり方は正しいです。

<IfModule mod_deflate.c>
    <IfModule mod_filter.c>
        SetOutputFilter DEFLATE
        BrowserMatch ^Mozilla/4 gzip-only-text/html
        BrowserMatch ^Mozilla/4\.0[678] no-gzip
        BrowserMatch \bMSI[E] !no-gzip !gzip-only-text/html
        SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png)$ no-gzip dont-vary
    </IfModule>
</IfModule>

全部圧縮する設定にした後、画像を圧縮の対象外にしているので大丈夫です。

application/x-httpd-php……?

圧縮の設定にapplication/x-httpd-phpを含めている例を見掛けます。

しかし、コンテンツが圧縮のフィルタを通るのは「最終的な出力」に対してです。

すなわち、PHPなどで処理した結果に対してフィルタが適用されるのです。

そして、PHPはデフォルトで「text/html」として結果を出力します、application/x-httpd-phpの指定は何の意味も持ちません。

AddOutputFilterByTypeは古い

先程の例でも上げた様に、MIMEを指定して圧縮するにはAddOutputFilterByTypeを利用するのですが、このAddOutputFilterByTypeはApache2.1以降で非推奨(deprecated)になり、2.3.7からはcore機能から「mod_filter」の機能へと移行されました。

なので「mod_filter+AddOutputFilterByType」を使えば良いのですが、どうせmod_filterを使うのですから、「FilterProvider」を利用した方が良いと思います。

FilterDeclare Compression CONTENT_SET
FilterProvider Compression DEFLATE Content-Type $text/html
FilterProvider Compression DEFLATE Content-Type $mime/type
FilterChain Compression

と言った設定がヒットします。ピュアな心で何も疑う事無く、心ウキウキで設定して再起動してみると

AH00526: Syntax error on line 5 of /etc/apache2/mods-enabled/deflate.conf:
FilterProvider takes three arguments, filter-name provider-name match-expression
Action 'configtest' failed.

と言う訳で再度調べ直すとGitHubでそれっぽい記事を発見、

どうやら、2.2→2.4の過程で構文が変わった模様……

FilterProvider COMPRESS DEFLATE resp=Content-Type $text/htmlではなくFilterProvider COMPRESS DEFLATE "%{CONTENT_TYPE} = 'text/html'"

コッチの方が新しい設定方法

この鯖での設定例

このサーバでは以下の様に設定してみました。

<IfModule mod_deflate.c>
    DeflateCompressionLevel 1
    <IfModule mod_filter.c>
        FilterDeclare COMPRESS
        FilterProvider COMPRESS DEFLATE "%{CONTENT_TYPE} =~ m#^text/#i"
        FilterProvider COMPRESS DEFLATE "%{CONTENT_TYPE} =~ m#^application/(atom\+xml|javascript|json|rss\+xml|xml|xhtml\+xml)#i"
        FilterProvider COMPRESS DEFLATE "%{CONTENT_TYPE} =~ m#^image/(svg\+xml|vnd\.microsoft\.icon)#i"
        FilterChain COMPRESS
        FilterProtocol COMPRESS DEFLATE change=yes;byteranges=no
        #BrowserMatch ^Mozilla/4 gzip-only-text/html
        #BrowserMatch ^Mozilla/4\.0[678] no-gzip
        #BrowserMatch \bMSI[E] !no-gzip !gzip-only-text/html
        #Header append Vary Accept-Encoding env=!dont-vary
    </IfModule>
</IfModule>

コメントアウトの部分は必要ないですが、なぜ排除したのかも説明したいので記載しています。
1つずつ解説して行きますね。

圧縮レベルは1で充分

DeflateCompressionLevel 1は圧縮レベルを1で圧縮する事を意味します。ちなみに、(環境にも依りますが)デフォルトは6で圧縮されていました

圧縮レベルは1~9の範囲で、数字が大きいほど高圧縮です。「高圧縮の方が良いでしょ?」と思われがちですが、圧縮レベルを上げると当然ながらCPUの負荷(圧縮に必要な時間)が増えます。

でもって、圧縮レベルを1~9にした場合でもそう大差ありません。せいぜい数%ほど変わるかな?、くらいです。

実際に1~9まで試してみました。計測対象はこのブログのトップページで、「33101 byte」です。

レベル 圧縮後 削減 圧縮率
1 10374 byte 22727 byte 68.6%
2 10102 byte 22999 byte 69.4%
3 9963 byte 23138 byte 69.9%
4 9459 byte 23642 byte 71.4%
5 9330 byte 23771 byte 71.8%
6 9272 byte 23829 byte 71.9%
7 9272 byte 23829 byte 71.9%
8 9261 byte 23840 byte 72.0%
9 9256 byte 23845 byte 72.0%

ご覧の通り、殆ど変わりませんね。しかも、レベルが上がるほど圧縮率の伸びが悪くなって行ってます。

1と9の差はたったの4%。CPU負荷を上げて圧縮するくらいならば、画像の色数なり枚数なりを減らした方が良いです。

この様に、上げても殆ど無意味なため圧縮レベルは1、欲張ってもせいぜい2か3まででしょう。

圧縮するMIMEタイプを指定

当初は良くある例で上げたような全圧縮設定にしてから、拡張子指定で画像を圧縮の対象から除外する設定を利用していたのですが、PHPで画像を動的に生産するとその中身が何だろうが勝手に圧縮しやがりました。

それでは困ると言うか、二度手間なのでMIMEタイプ指定で圧縮する方法を選びました。

FilterDeclare COMPRESS
FilterProvider COMPRESS DEFLATE "%{CONTENT_TYPE} =~ m#^text/#i"
FilterProvider COMPRESS DEFLATE "%{CONTENT_TYPE} =~ m#^application/(atom\+xml|javascript|json|rss\+xml|xml|xhtml\+xml)#i"
FilterProvider COMPRESS DEFLATE "%{CONTENT_TYPE} =~ m#^image/(svg\+xml|vnd\.microsoft\.icon)#i"
FilterChain COMPRESS
FilterProtocol COMPRESS DEFLATE change=yes;byteranges=no

元の記事では.icoがimage/x-iconでしたが、この鯖ではimage/vnd.microsoft.iconなのでそれに合わせて書き換えました。

あと、見るからに正規表現なので一部の文字をエスケープしています。

text/(html|css|plain|xml)ではなくtext/と指定する事でMIMEタイプがtext/から始まる物(HTML,CSS……すなわち平文のコンテンツ)は全て圧縮対象になります。

元記事ではFilterProtocol COMPRESS DEFLATE change=yes;byteranges=noと言った指定があるのですが、この指定を(最低でもchangeは)入れないと静的コンテンツなのにETagやキャッシュが効かなかったりします。

せっかく304でキャッシュが有効なのに効かないからと毎回gzipして転送していたのでは本末転倒ですからね。

拡張子指定で除外する方法とMIME指定、どちらを使うかは完全に好みなのでお好きな方を採用して下さい。

一部の画像は圧縮出来る

「画像だから圧縮しない」と言う考え方は短絡的で好ましくありません。画像でも圧縮出来る物もあるので良く考えてから設定しましょう。

SVG、確かに画像ですが中身はXMLです、圧縮できます。ICO、確かに画像ですが、圧縮すると1/10くらいのサイズになります。

今回は含んでいませんが、bmp画像は基本的に無圧縮なので圧縮出来ます。

GZIPの解凍に問題を抱えるブラウザは無視

BrowserMatch ^Mozilla/4 gzip-only-text/html
BrowserMatch ^Mozilla/4\.0[678] no-gzip
BrowserMatch \bMSI[E] !no-gzip !gzip-only-text/html

これはgzipの解凍に問題を抱えるブラウザは圧縮せずに転送する設定ですが、こんな平安時代に和歌を詠みあげてた様なブラウザを未だに使っている人間は居ません。(人間以外なら(悪質なbotとかなら)居るかもね。)

仮に居たとしても、CSS3やHTML5が主流、HTTP/1.1が大前提、場合によってはHTTP/2やSNIを使っているかという今の時代では、もはや表示する事すらままならないでしょう。

個人的には、これらの設定は省いてしまって構わないと思います。設定してたって無駄なマッチングが発生するだけですしね。

Varyは自動的に付与される

Header append Vary Accept-Encoding env=!dont-vary

これは、プロキシなどに対してgzipが解凍出来ないブラウザに圧縮されたキャッシュを送信しない様にするための設定ですが、きょうびgzip解凍出来ないブラウザの方が少ないのでは?

さて、「Varyヘッダは自動的に付与される」とどこかで見かけたのでtelnetで試してみました
まずはgzip対応ブラウザ

GET / HTTP/1.1
Host: jyn.jp
Accept-Encoding: gzip, deflate

HTTP/1.1 200 OK
Server: Apache
Vary: Accept-Encoding
Content-Encoding: gzip
Transfer-Encoding: chunked
Content-Type: text/html

うむ、「Vary: Accept-Encoding」が追加されてますね。

次に、gzip非対応ブラウザ

GET / HTTP/1.1
Host: jyn.jp

HTTP/1.1 200 OK
Server: Apache
Vary: Accept-Encoding
Transfer-Encoding: chunked
Content-Type: text/html

Varyというのは「そのヘッダがあるとコンテンツが変わるよ」、つまり今回の場合「Accept-Encodingの内容に応じてコンテンツが変わるよ」という意味です。

これによってプロキシ側でAccept-Encodingの有無を見てキャッシュを振り分けたりします。

まぁ、設定してもしなくても良いでしょう。

.htaccessでの注意点

僕はサーバのアドミンです、ルートです、スーパーユーザです、confを直接触れます。

しかし、世の中には共有サーバなるモノがあって、.htaccessで設定している方も居るかも知れないので、注意点をいくつか

  1. DeflateCompressionLevelは.htaccessでは使えない
    文字通りです、DeflateCompressionLevelを含んだ.htaccessを有効にすると500 Internal Server Errorになります。
  2. CGI版PHPにはdeflate圧縮できない?
    CGI版PHPの出力を.htaccessで設定したdeflateで圧縮する様に設定しても圧縮が効きません。
    その場合はphp.iniのzlib.output_compressionをOnにすると良いでしょう。
    (.confに直接記載している場合は圧縮が効きます、.htaccessの場合だけ効きません)

設定が終わったら必ず確認を、

「設定終わった、はい終了!!」は愚か者のする事です。

設定が終わったら必ず意図した通りに動くか確認しましょう。

以下の様なツールを使えば簡単に確認できます。
https://www.port80software.com/support/p80tools

 - サーバー