「HTTPコンテンツ圧縮はどのレイヤーで行うのがいいか」で書いたりしましたが、高トラフィック環境では、サーバ間の通信量も問題となるため、ApplicationサーバでもHTTP圧縮をかけたほう良さげです。Plackには既にPlack::Middleware::DeflaterというApacheのmod_deflate相当のMiddlewareがあるのですが、ちょいちょい弄っていたりするのでその紹介。
その前に、HTTPコンテンツ圧縮のおさらいです。HTTP圧縮はリクエストのAccept-Encodingにgzipやdeflateがあった場合に、コンテンツをgzip、deflateなどで圧縮し、レスポンスのContent-Encodingヘッダに圧縮に用いた方式を表記することで実現されます。さらに、クライアントサーバ間にキャッシュサーバが存在した場合に、Accept-Encodingを送ってきていないクライアントに対して圧縮済みのコンテンツを返してしまわないよう、Varyヘッダも必要となります。
Vary: Accept-Encoding
キャッシュサーバはVaryヘッダに記されたヘッダの内容ごとにキャッシュを分け、クライアントのリクエストに適したコンテンツを返します。
追記: 小悪魔元女子大生の奥さんが説明いらすと書いてくれた!
仕様通りのHTTP圧縮であればこれで済みますが、実際のところブラウザ側のバグとも折り合いをつけて行かなければなりません。(だれも使っていないだろうけど)IE6ではHTML以外のコンテンツに圧縮をかけた場合、キャッシュが壊れるという問題があったり、(だれも使っていないだろうけど)Netscapeの古いバージョンではAccept-Encoding送ってくるのにそもそも圧縮をしたらうまく表示できないとかいろいろ問題があります。
そこでサーバ側でUser-Agentをみて圧縮をかけるかどうか判断し、VaryヘッダにUser-Agentも追加するのがよくある設定です
Vary: Accept-Encoding, User-Agent
Apacheのmod_deflateのドキュメントに書いてあるものがまさにそんな感じです。ただしIE6のバグについては対応していないようです
# Insert filter
SetOutputFilter DEFLATE
# Netscape 4.x has some problems...
BrowserMatch ^Mozilla/4 gzip-only-text/html
# Netscape 4.06-4.08 have some more problems
BrowserMatch ^Mozilla/4\.0[678] no-gzip
# MSIE masquerades as Netscape, but it is fine
BrowserMatch \bMSIE !no-gzip !gzip-only-text/html
# Don't compress images
SetEnvIfNoCase Request_URI \
\.(?:gif|jpe?g|png)$ no-gzip dont-vary
# Make sure proxies don't deliver the wrong content
Header append Vary User-Agent env=!dont-vary
環境変数no-gzipが有効な場合、圧縮はされず、gzip-only-text/htmlが有効な場合はContent-Typeがtext/htmlの場合にだけdeflateが有効となります。Apacheの設定ではさらにgifやjpegは圧縮をしないようにno-gzipし、最後に画像以外にはVaryにUser-Agentを追加しています
IE6の件も含めると
# IE6はMozilla/4
BrowserMatch ^Mozilla/4 gzip-only-text/html
# Netscapehふるいの
BrowserMatch ^Mozilla/4\.0[678] no-gzip
# IE7,8はMozilla/4だけど問題がない。IE9はMozilla/5らしい
BrowserMatch \bMSIE\s(7|8) !no-gzip !gzip-only-text/html
Header append Vary User-Agent
AddOutputFilterByType DEFLATE text/html text/plain text/css text/xml text/javascript
AddOutputFilterByType DEFLATE application/javascript application/x-javascript
こんな感じに設定するのがいいんじゃないなぁと思っている。画像にもVary: UAつくけど
んで、Plack::Middleware::Deflater
CPANにあるバージョン0.03は圧縮とVary: Accept-Encodingの追加のみの機能しかないので、そこに圧縮をかけるContent-Typeの選択、Vary: User-Agentの付加、no-gzip, gzip-only-text/htmlと同じ動きをする環境変数の導入をしてみた。ソースコードはマージされてない部分があるので、僕のgithubのforkにて。
使う時はこんな感じ
enable sub {
my $app = shift;
sub {
my $env = shift;
my $ua = $env->{HTTP_USER_AGENT} || '';
# Netscape has some problem
$env->{"psgix.compress-only-text/html"} = 1 if $ua =~ m!^Mozilla/4!;
# Netscape 4.06-4.08 have some more problems
$env->{"psgix.no-compress"} = 1 if $ua =~ m!^Mozilla/4\.0[678]!;
# MSIE (7|8) masquerades as Netscape, but it is fine
if ( $ua =~ m!\bMSIE (?:7|8)! ) {
$env->{"psgix.no-compress"} = 0;
$env->{"psgix.compress-only-text/html"} = 0;
}
$app->($env);
}
};
enable "Deflater",
content_type => ['text/css','text/html','text/javascript','application/javascript'],
vary_user_agent => 1;
Plack::Builder::Conditionalsの時に紹介してるけど、
builder {
enable match_if browser(qr!^Mozilla/4!), 'ForceEnv',
"psgix.compress-only-text/html" => 1;
enable match_if browser(qr!^Mozilla/4\.0[678]!), 'ForceEnv',
"psgix.no-compress" => 1;
enable match_if browser(qr!\bMSIE (?:7|8)!), 'ForceEnv',
"psgix.no-compress" => 0,
"psgix.compress-only-text/html" => 0;
enable "Deflater",
content_type => ['text/html','text/css','text/plain','text/javascript','application/javascript'],
vary_user_agent => 1;
$app
}
こんな感じでも書けますね。簡単になったのかどうなのか分からないですが。
このBKくさい設定をデフォルトにしたPlack::Middleware::Deflater::Presetsとかあればいいのかしらne