フィルタリング機能徹底解剖

出典
Software Design 2007年 09月号
テスト環境
CentOS5 テスト用最小インストール手順参照。
apacheバージョン
2.2系

2.0系のフィルタリングモジュール

シンプルフィルタ

  • mod_include
  • mod_ssl
  • mod_deflate
  • mod_ext_filter

などなど。。。

mod_deflateの組み込み

  • 圧縮フィルタ。テキストデータの圧縮によるトラフィック軽減。ブラウザがHTTP1.1準拠の必要あり(準拠していない場合は使用しない設定が必要。)
  • なお、mod_deflateに併せてmod_headersも組み込む必要がある。理由はここ

ソースからビルドして組み込み

今回は動的に組み込んでみる。

# yum install gcc zlib-devel
# wget http://ftp.kddilabs.jp/infosystems/apache/httpd/httpd-2.2.4.tar.gz
# tar zxvf httpd-2.2.4.tar.gz
# cd httpd-2.2.4
# ./configure
# make
# make install
# /usr/local/apache2/bin/apxs -a -i -c modules/filters/mod_deflate.c -lz
# /usr/local/apache2/bin/apxs -a -i -c modules/metadata/mod_headers.c
# /usr/local/apache2/bin/apachectl -t

-tでsyntax check。

パッケージの場合

CentOS5の場合、標準で動的に組み込まれている。

# yum install httpd
# httpd -M 2>&1 | egrep "header|deflate"
deflate_module (shared)
headers_module (shared)

設定

まずモジュールのロード設定を確認。以下の行がhttpd.confにあること。

LoadModule deflate_module modules/mod_deflate.so
LoadModule headers_module modules/mod_headers.so

httpd.confを編集。以下の点を踏まえて設定する。

  • モジュールがインストールされている場合のみ有効化(IfModuleディレクティブ)
  • 圧縮時は通常のログとは別にログを出力。
  • 圧縮対象を指定する。パターンは以下。
    • 特定のディレクトリでは無条件に圧縮
    • 特定のコンテンツをMIMEタイプで指定して圧縮
    • 対象となるブラウザ、ファイルを指定して圧縮
特定のディレクトリでは無条件に圧縮
<IfModule deflate_module>

    DeflateCompressionLevel 5

    DeflateFilterNote Input  instream
    DeflateFilterNote Output outstream
    DeflateFilterNote Ratio  ratio
    LogFormat '"%r" %{outstream}n/%{instream}n (%{ratio}n%%) %{User-agent}i' deflate
    CustomLog logs/deflate_log deflate

    <Directory /var/www/html/deflate>
       SetOutputFilter DEFLATE
    </Directory>

</IfModule>

テキストデータを置いてアクセスしてみる。

"GET /deflate/index.html HTTP/1.1" 2004/5044 (39%)
Mozilla/5.0 (Macintosh; U; Intel Mac OS X; ja-jp) AppleWebKit/419.3 (KHTML, like Gecko) Safari/419.3

圧縮されているのが分かる。ちなみに別のディレクトリに同じファイルを置いてアクセスしてみる。

"GET /nondeflate/index.html HTTP/1.1" -/- (-%)
Mozilla/5.0 (Macintosh; U; Intel Mac OS X; ja-jp) AppleWebKit/419.3 (KHTML, like Gecko) Safari/419.3

こちらは圧縮されていないのがわかる。

  • よくわからないのは、最初に10MBのテキストデータ(#文字列だけで埋めた)を用意したのだけど圧縮率0になった(圧縮してない場合のログとは異なるので圧縮対象にはなってる様子)。この辺どういうロジックなのかな?
  • DeflateCompressionLevel、DeflateFilterNoteディレクティブは<Directory>〜</Directory>の中では使えないみたい。圧縮率設定はともかく、ログ設定はこの書き方だと圧縮対象外のコンテンツにアクセスした場合でもログが吐かれちゃうのはちょっと。。。ただしログ出力はオーバーヘッドがでかいみたいなので普通は使わないのかも。
特定のコンテンツをMIMEタイプで指定して圧縮
<IfModule deflate_module>

    DeflateCompressionLevel 5

    DeflateFilterNote Input  instream
    DeflateFilterNote Output outstream
    DeflateFilterNote Ratio  ratio
    LogFormat '"%r" %{outstream}n/%{instream}n (%{ratio}n%%) %{User-agent}i' deflate
    CustomLog logs/deflate_log deflate

    <Directory /var/www/html/deflate>
        AddOutputFilter DEFLATE .html .htm
        AddOutputFilterByType DEFLATE text/html
    </Directory>

</IfModule>

同じ内容で拡張子を変えてファイルを用意してアクセス。

"GET /deflate/index.html HTTP/1.1" 2004/5044 (39%)
 Mozilla/5.0 (Macintosh; U; Intel Mac OS X; ja-JP-mac; rv:1.8.1.6) Gecko/20070725 Firefox/2.0.0.6

*.html の場合は圧縮されている。

"GET /deflate/index.txt HTTP/1.1" -/- (-%)
 Mozilla/5.0 (Macintosh; U; Intel Mac OS X; ja-JP-mac; rv:1.8.1.6) Gecko/20070725 Firefox/2.0.0.6

*.txt の場合は圧縮されない。

  • AddOutputFilterByTypeは2.1系以降では非推奨。
  • ならAddOutputFilterだけでいいんでないの?まあ拡張子だけでは図れない柔軟性はあるのかもだけど。
  • 2.2系ではもちょっとスマートに設定できるので後述。
対象となるブラウザ、ファイルを指定して圧縮
<IfModule deflate_module>

    DeflateCompressionLevel 5

    DeflateFilterNote Input  instream
    DeflateFilterNote Output outstream
    DeflateFilterNote Ratio  ratio
    LogFormat '"%r" %{outstream}n/%{instream}n (%{ratio}n%%) %{User-agent}i' deflate
    CustomLog logs/deflate_log deflate

    <Location />
        SetOutputFilter DEFLATE
        BrowserMatch ^Mozilla/4¥.0[678] no-gzip
        BrowserMatch ^Mozilla/4 gzip-only-text/html
        BrowserMatch ¥bMSIE !no-gzip !gzip-only-text/html
        SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png)$ no-gzip dont-vary
        Header append Vary User-Agent env=!dont-vary
    </Location>

</IfModule>
  • <Location />〜</Location>は全部のコンテンツを指定する場合の簡単な設定。<Location>と<Directory>は混合すると混乱するので本来は<Directory>で指定したほうが良い。
  • Mozilla4.0Xは圧縮系に問題ありなのでOFF。それ以外のMozilla4.Xはtext/htmlのみ圧縮に対応しているのでgzip-only-text/htmlを指定。MSIEもMozilla4を名乗るのだけど圧縮対応しているので逆に設定を打ち消す設定を追加。ちなみにMozilla5以降(FirefoxとかNetscape6以降とかsafariとか)や最近のIEなどでは基本的に圧縮対応しているらしい。

Safariでアクセスしてみる。

"GET /deflate/index.html HTTP/1.1" 2004/5044 (39%)
 Mozilla/5.0 (Macintosh; U; Intel Mac OS X; ja-jp) AppleWebKit/419.3 (KHTML, like Gecko) Safari/419.3
"GET /deflate/gazou.gif HTTP/1.1" -/- (-%)
 Mozilla/5.0 (Macintosh; U; Intel Mac OS X; ja-jp) AppleWebKit/419.3 (KHTML, like Gecko) Safari/419.3

htmlファイルは圧縮されてgifファイルは圧縮されていない。
wgetでアクセスしてみる。

"GET /deflate/index.html HTTP/1.0" -/- (-%) Wget/1.10.2
"GET /deflate/gazou.gif HTTP/1.0" -/- (-%) Wget/1.10.2

htmlファイルもgifファイルも圧縮されていない。

  • 書籍内の記事では
            BrowserMatch ^Mozilla/4¥.0[678] no-gzip
            BrowserMatch ^Mozilla/4 gzip-only-text/html
            BrowserMatch ^Mozilla/5 gzip-only-text/html
    
    となっているけど、圧縮対応しているMozilla5でtext/htmlだけにしてるのはなんでなんだろ?
  • apacheのドキュメントでは、
            BrowserMatch ^Mozilla/4 gzip-only-text/html
            BrowserMatch ^Mozilla/4¥.0[678] no-gzip
    
    となっているのだけどこれだと、Mozilla4.0Xの場合最初の評価で判断されないのかな?まあ全行評価するのだったら順番関係ない気もするけど、それだと逆に上の例でMSIEの場合にMozilla/4の設定打ち消しもなんか変な気がする。
  • dont-varyという表記はマニュアルのどこにも載ってない。mod_deflate モジュールを使うと自動的にVaryヘッダをついて、Accept-Encoding リクエストを投げてくるブラウザに対してだけプロキシがキャッシュしたコンテンツを返してね、ということをするみたいなのだけど、要は画像ファイルに関しては圧縮もしないしvaryヘッダもつけないんでキニシナイっていう理解でよいのかな。

2.2系のフィルタリングモジュール

スマートフィルタリング。mod_filterを使った、動的なフィルタリング。

mod_filter

  • apache2.2系のフィルタ。
  • apache2.0系のフィルタは、フィルタ内で条件指定するので、フィルタ不要なデータでも結局フィルタを通る。
  • mod_filterは、リクエストヘッダ/レスポンスヘッダ/環境変数などを条件にしてフィルタ適用の有無を決める。

mod_pgheader

  • <BODY></BODY>タグを置き換えてヘッダ/フッタをテンプレート的に使うモジュール。
  • スマートフィルタの例としてやってみる。

mod_pgheaderのインストール

パッケージを使っている場合、mod_pgheaderはサードパーティのapacheモジュールなので、CentOS5のapacheには組み込まれていない。よってソースを落としてきてapxsコマンドでインストールする必要がある。CentOSのパッケージについては以下注意。

  • CentOS5のhttpdパッケージを使う場合、apxsコマンドはhttpdパッケージではなくてhttpd-develパッケージに含まれているのでhttpd-develを先にインストールすること。
    # yum install httpd-devel
    
  • CentOS5のhttpdパッケージではhttpd -Mしてもmod_filterが出てこない。バイナリ自体はちゃんと動的に組み込まれているのだけど、単にhttpd.confでロードされていない(コメントアウトされたエントリすらない)だけなので以下の記述を追加してapachectl restartしておくこと。
    LoadModule filter_module modules/mod_filter.so
    

apxsコマンドでインストール。

# cd /usr/local/src
# wget http://nchc.dl.sourceforge.net/sourceforge/pgheader/mod_pgheader.tgz
# tar zxvf mod_pgheader.tgz
# cd mod_pgheader
# /(apxsコマンドへのパス)/apxs -a -i -c mod_pgheader.c

mod_pgheaderの設定

予めヘッダ用ファイル、フッタ用ファイル、ボディ用ファイルを用意する。

  • /var/www/_head.mkp
    <body>
    <h1>ヘッダー</h1>
    <hr>
    
  • /var/www/_head.mkp
    <hr>
    フッダーですよ
    </body>
    
  • /var/www/html/index.html
    <html>
    <head><title>pgheaderのテスト</title></head>
    <body>
    ここがボディ部分のコンテンツになります。
    </body>
    </html>
    

普通にアクセスするとこんな感じで表示される。

ここがボディ部分のコンテンツになります。

ではpgheaderを使うようにhttpd.confを修正する。

<IfModule deflate_module>
    <Directory /var/www/html>
    FilterDeclare  Header_Footer CONTENT_SET
    FilterProvider Header_Footer PGHEADER resp=Content-Type $text/html
    FilterChain Header_Footer
    AddBodyHead /var/www/_head.mkp /*
    AddBodyFoot /var/www/_foot.mkp /*
    </Directory>
</IfModule>

AddBody〜は引数2つ必要。記事間違ってる。これでブラウザからアクセスしてみるとこんな感じで表示される。

ヘッダー
--------------------------------------------------
ここがボディ部分のコンテンツになります。 
--------------------------------------------------
フッダーですよ
  • 画像ファイルにアクセスした場合やtext/plainなファイルにアクセスした場合にはpgheaderで加工されていないものがそのまま表示される。
  • 上記のようにindex.htmlみたいなインデックスファイルの場合、http://.../index.html 間で指定しなくても http://.../ でアクセスしても表示されるがこの場合はフィルタの対象にならない様子。

mod_filterのフォーマット

必ず以下の順番で指定すること。

FilterDeclare
定義するフィルタ名の宣言。

FilterDeclate フィルタ名 タイプ
フィルタ名は任意。指定できるタイプがよくわからない。include/util_filter.hのAP_FTYPE_*で定義されているものが使えるみたいなんだけど。
/**
 * Filters have different types/classifications. These are used to group
 * and sort the filters to properly sequence their operation.
 *
 * The types have a particular sort order, which allows us to insert them
 * into the filter chain in a determistic order. Within a particular grouping,
 * the ordering is equivalent to the order of calls to ap_add_*_filter().
 */
typedef enum {
    /** These filters are used to alter the content that is passed through
     *  them. Examples are SSI or PHP. */
    AP_FTYPE_RESOURCE     = 10,
    /** These filters are used to alter the content as a whole, but after all
     *  AP_FTYPE_RESOURCE filters are executed.  These filters should not
     *  change the content-type.  An example is deflate.  */
    AP_FTYPE_CONTENT_SET  = 20,
    /** These filters are used to handle the protocol between server and
     *  client.  Examples are HTTP and POP. */
    AP_FTYPE_PROTOCOL     = 30,
    /** These filters implement transport encodings (e.g., chunking). */
    AP_FTYPE_TRANSCODE    = 40,
    /** These filters will alter the content, but in ways that are
     *  more strongly associated with the connection.  Examples are
     *  splitting an HTTP connection into multiple requests and
     *  buffering HTTP responses across multiple requests.
     *
     *  It is important to note that these types of filters are not
     *  allowed in a sub-request. A sub-request's output can certainly
     *  be filtered by ::AP_FTYPE_RESOURCE filters, but all of the "final
     *  processing" is determined by the main request. */
    AP_FTYPE_CONNECTION  = 50,
    /** These filters don't alter the content.  They are responsible for
     *  sending/receiving data to/from the client. */
    AP_FTYPE_NETWORK     = 60
} ap_filter_type;
FilterProvider
加工する処理(プロバイダ)を指定。
FilterProvider フィルタ名 プロバイダ識別子 [req|resp|env]=割り当てるキ—? マッチする表現
日本語にするとオプション引数がわかりにくい。[req|resp|env]はそれぞれリクエストヘッダ、レスポンスヘッダ、環境変数。これらの特定のキーが条件にマッチしたら、プロバイダ識別子で指定したフィルタが適用されるフラグが立つみたいな。上のmod_pgheaderの例参照。
FilterChain
フィルタの実行
FilterChain フィルタ名
最終的にこれを書かないと実行されない。

スマートフィルタを使ってみる

mod_bw(トラフィック制御モジュール)と併せて、上で指定したmod_deflateを2.2系のスマートフィルタで書き直す。

mod_bwのインストール

サートパーティなのでapxs

# wget http://www.ivn.cl/apache/files/source/mod_bw-0.8.tgz
# tar zxvf mod_bw-0.8.tgz 
# cd mod_bw
# apxs -a -i -c mod_bw.c

httpd.confを修正。mod_bwについては別途検証するのでここでは細かく触れない。

<IfModule deflate_module>
<IfModule bw_module>

    BandWidthModule On
    ForceBandWidthModule Off
    BandWidth all 1000
    FilterDeclare BW CONTENT_SET
    FilterProvider BW MOD_BW resp=Content-Type $application/zip
    
    DeflateCompressionLevel 5
    DeflateFilterNote Input  instream
    DeflateFilterNote Output outstream
    DeflateFilterNote Ratio  ratio
    LogFormat '"%r" %{outstream}n/%{instream}n (%{ratio}n%%) %{User-agent}i' deflate
    CustomLog logs/deflate_log deflate
    FilterDeclare Comp CONTENT_SET
    FilterProvider Comp DEFLATE resp=Content-Type /^text/
    
    FilterChain Comp BW
 
</IfModule>
</IfModule>

FilterChainで指定した順番にフィルタが適用されるところがミソ。ちなみに

FilterChain フィルタ1 フィルタ2 ...

と書く代わりに

FilterChain フィルタ1
FilterChain +フィルタ2

と書いても良い。よくわからんのが、上記のように書くと、zipファイルもdeflateされちゃう。 この辺スマートフィルタの使い方次第なんだろうけど、複数のフィルタがあった場合に、それぞれの条件ごとに別々に適用するなら、

FilterDeclare A
FilterProvider A
FilterChain A
FilterDeclare B
FilterProvider B
FilterChain B

と書くべきなのかな?


その他


参考