* Accept-Encoding に gzip を付けてないクライアントをリダイレクト
この記事の直リンクURL: Permlink | この記事が属するカテゴリ: [Apache] [http] [sonic64.com]
アクセスログを見ていたら、503 Service Temporarily Unavailable が出ていることに気づいた。503 はサーバ側の都合でサービスができないことを意味する HTTP ステータスコード。要するに、503 が出ている間は当サイトにアクセスできなかったということだ。
ログによると、最近アクセスが多かったようで一日20GB を超える転送量が続いていた模様。中でも全記事全文入りの RSS である cl-full.xml の転送量が90%を占めていた。おそらくこれのせいでさくらインターネットの転送量制限を超えてしまい、503 となっていたのだろう。
cl-full.xml は過去の全記事全文入りで 4MB を超えるサイズだから、サイト全体の転送量が増えても仕方がないかもしれないけど、ちょっと多い感じがする。だれかの役に立つかもしれないから公開しているのでどんどん使ってもらって構わないのだが、サイトのサービスの妨げになるのは困る。仕方がないので制限をかけることにした。
リクエストに Accept-Encoding: gzip がある場合は今まで通り gzip 圧縮した cl-full.xml を返す。
リダイレクトせずに cl.xml の中身を返すようにすることもできるけど、「君はこっちのコンテンツを使ってね」というリダイレクトの意図が伝わりにくいので使わない。でも、それだったら Vary を付ける方がいいかなあ。
以下のように、 Accept-Encoding: gzip つきならそのままアクセス許可。
以下のように、Accept-Encoding に gzip を付けてないクライアントは 302 を返して cl.xml へリダイレクト。
まず、Accept-Encoding に gzip なしのクライアントがリクエストしてくるとする。
302 を返して cl.xml へリダイレクト。
302 を受けたクライアントは cl.xml にリクエスト。
もちろん Accept-Encoding: gzip などはリクエストにないが、cl.xml はそういったリクエストの場合は圧縮していないコンテンツを返すだけなので、無事レスポンスが返される。
これでよしと。
ログによると、最近アクセスが多かったようで一日20GB を超える転送量が続いていた模様。中でも全記事全文入りの RSS である cl-full.xml の転送量が90%を占めていた。おそらくこれのせいでさくらインターネットの転送量制限を超えてしまい、503 となっていたのだろう。
cl-full.xml は過去の全記事全文入りで 4MB を超えるサイズだから、サイト全体の転送量が増えても仕方がないかもしれないけど、ちょっと多い感じがする。だれかの役に立つかもしれないから公開しているのでどんどん使ってもらって構わないのだが、サイトのサービスの妨げになるのは困る。仕方がないので制限をかけることにした。
- 制限をかける
2005-09-15 の「mod_rewrite でリクエストに応じて gzip 圧縮ファイルを返す」では、mod_gzip を 使えない当サイトの環境でも Accept-Encoding: gzip を送ってきているクライアントには gzip 圧縮したデータを返すようにした。今回はそれを一歩進めて、リクエストされたファイルが cl-full.xml でかつ Accept-Encoding: gzip がない場合、HTTP レスポンスコード 302 Moved Temporarily を返して、数十キロバイト程度でサイズの小さい cl.xml へリダイレクトする。リクエストに Accept-Encoding: gzip がある場合は今まで通り gzip 圧縮した cl-full.xml を返す。
- Accept-Encoding に gzip を付けてないクライアントをリダイレクトする mod_rewrite の RewiteRule
単純にリダイレクトしてるだけ。RewriteEngine on
RewriteCond %{REQUEST_FILENAME} cl-full\.xml$
RewriteCond %{HTTP:Accept-Encoding} !gzip
RewriteRule .+ http://sonic64.com/cl.xml [L,R]
リダイレクトせずに cl.xml の中身を返すようにすることもできるけど、「君はこっちのコンテンツを使ってね」というリダイレクトの意図が伝わりにくいので使わない。でも、それだったら Vary を付ける方がいいかなあ。
- HTTP トランザクションの中身を見て確認
2005-04-16 で書いた「Live Http headers - HTTP ヘッダ表示ツール」で HTTP トランザクションの中身を表示して確認する。以下のように、 Accept-Encoding: gzip つきならそのままアクセス許可。
http://sonic64.com/cl-full.xml
GET /cl-full.xml HTTP/1.1
Host: sonic64.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; ja-JP; rv:1.7.12) Gecko/20050919 Firefox/1.0.7
Accept: text/xml,application/xml,application/xhtml+xml,text/html; q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Accept-Language: ja,en-us;q=0.7,en;q=0.3
Accept-Encoding: gzip,deflate
Accept-Charset: Shift_JIS,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Referer: http://sonic64.com/
Pragma: no-cache
Cache-Control: no-cache
HTTP/1.x 200 OK
Date: Mon, 20 Feb 2006 04:43:35 GMT
Server: Apache/1.3.34 (Unix)
Last-Modified: Mon, 20 Feb 2006 02:33:38 GMT
Etag: "339758-fab0f-43ebfb82"
Accept-Ranges: bytes
Content-Length: 1026831
Keep-Alive: timeout=3, max=8
Connection: Keep-Alive
Content-Type: application/xml
Content-Encoding: gzip
以下のように、Accept-Encoding に gzip を付けてないクライアントは 302 を返して cl.xml へリダイレクト。
まず、Accept-Encoding に gzip なしのクライアントがリクエストしてくるとする。
GET /cl-full.xml HTTP/1.1
Host: sonic64.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; ja-JP; rv:1.7.12) Gecko/20050919 Firefox/1.0.7
Accept: text/xml,application/xml,application/xhtml+xml,text/html; q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Accept-Language: ja,en-us;q=0.7,en;q=0.3
Accept-Charset: Shift_JIS,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Referer: http://sonic64.com/
Pragma: no-cache
Cache-Control: no-cache
302 を返して cl.xml へリダイレクト。
HTTP/1.x 302 Found
Date: Mon, 20 Feb 2006 04:46:02 GMT
Server: Apache/1.3.34 (Unix)
Location: http://sonic64.com/cl.xml
Keep-Alive: timeout=3, max=7
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: text/html; charset=iso-8859-1
302 を受けたクライアントは cl.xml にリクエスト。
GET /cl.xml HTTP/1.1
Host: sonic64.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; ja-JP; rv:1.7.12) Gecko/20050919 Firefox/1.0.7
Accept: text/xml,application/xml,application/xhtml+xml,text/html; q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Accept-Language: ja,en-us;q=0.7,en;q=0.3
Accept-Encoding: gzip,deflate
Accept-Charset: Shift_JIS,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
If-Modified-Since: Mon, 20 Feb 2006 02:33:30 GMT
If-None-Match: "339172-24c4-43ebfb7a"
もちろん Accept-Encoding: gzip などはリクエストにないが、cl.xml はそういったリクエストの場合は圧縮していないコンテンツを返すだけなので、無事レスポンスが返される。
HTTP/1.x 304 Not Modified
Date: Mon, 20 Feb 2006 04:46:02 GMT
Server: Apache/1.3.34 (Unix)
Connection: Keep-Alive, Keep-Alive
Keep-Alive: timeout=3, max=6
Etag: "339172-24c4-43ebfb7a"
これでよしと。
諸般の事情で、HTTP レスポンスの Server ヘッダなどの情報を最小限にしなければならなくなったので方法をメモ。
Apache Core Features Apache コア機能
http://httpd.apache.org/docs/1.3/mod/core.html
2つとも基本的なことだけど、こういう事こそメモしておくと役に立つことが多い。
- HTTP レスポンスヘッダの Server ヘッダの情報を最小限にする
httpd.conf の ServerTokens を ProductOnly にすることで、HTTP レスポンスヘッダに含まれるのは Server: Apache だけになる。ServerTokens ProductOnly
Apache Core Features Apache コア機能
http://httpd.apache.org/docs/1.3/mod/core.html
ServerTokens ディレクティブ
構文: ServerTokens Minimal|ProductOnly|OS|Full
デフォルト: ServerTokens Full
コンテキスト: サーバ設定ファイル
ステータス: core
互換性: ServerTokens は Apache 1.3 以降で利用可能です。 また、ProductOnly キーワードは Apache 1.3.12 以降で利用可能です。
このディレクティブは、クライアントに送り返す Server レスポンスヘッダ内に、サーバの一般的な OS 種別や、コンパイルされて組み込まれているモジュールの情報を 含めるかどうかを指定します。
ServerTokens Prod[uctOnly]
サーバは (例えば): Server: Apache といったように送ります。
ServerTokens Min[imal]
サーバは (例えば): Server: Apache/1.3.0 といったように送ります。
ServerTokens OS
サーバは (例えば): Server: Apache/1.3.0 (Unix) といったように送ります。
ServerTokens Full (もしくは未指定)
サーバは (例えば): Server: Apache/1.3.0 (Unix) PHP/3.0 MyMod/1.2 といったように送ります。
この設定はサーバ全体に適用され、 バーチャルホスト上で有効にしたり無効にしたりはできません。
- ServerSignature を Off にして Apache が生成したドキュメント中の情報も抑制
ServerSignature もあわせて Off に設定し、DirectoryIndex などに情報が出ないようにした。私の使っているマシンでは E-Mail がデフォルトで設定されていた。.htaccess などでも設定可能とのことだが、サーバ全体で表示をオフにしたいのでやはり httpd.conf で設定。ServerSignature Off
ServerSignature ディレクティブ
構文: ServerSignature On|Off|EMail
デフォルト: ServerSignature Off
コンテキスト: サーバ設定ファイル、バーチャルホスト、ディレクトリ、.htaccess
ステータス: core
互換性: ServerSignature は Apache 1.3 以降で利用可能です。
ServerSignature ディレクティブは、サーバが生成するドキュメント (エラーメッセージ、mod_proxy における FTP のディレクトリリスト、 mod_info の出力、等々) の最下行に付与するフッタの設定を行ないます。 そのような、フッタ行を有効にしたい理由としては、 プロキシが複数連なっている場合に、ユーザはどのサーバが返した エラーメッセージかを知る手段がほとんど無いからです。
デフォルトである Off に設定をすると、エラーの際の行が抑制されます。 (そして、Apache-1.2 以前と互換の動作をします) On に設定した場合は、単にドキュメントの中に、 サーバのバージョン、稼動中のバーチャルホストの ServerName の書かれた行を追加し、 EMail にした場合はさらに参照されたドキュメントに対する ServerAdmin を指す "mailto:" が追加されます。
2つとも基本的なことだけど、こういう事こそメモしておくと役に立つことが多い。
http リクエストの処理にかかった時間をロギングする方法のメモ。
集計や分析、パフォーマンス劣化の監視などで活用するため、http サーバ側でリクエストを処理したあとレスポンスを返すまでどれだけ時間がかかったかを記録したい。
所要時間などの値は http サーバ上で動くアプリケーション側でロギングする仕組みを作るのが普通。ただ、とりあえず記録さえできればいいときは、http サーバが提供するロギング機能を使ってアクセスログに書かせるのが手っ取り早い。
mod_log_config - Apache HTTP サーバ
http://httpd.apache.org/docs/2.2/ja/mod/mod_log_config.html# ...
Internet Information Service (IIS) 6.0 ヘルプ
以下は出力されるログのサンプル。
末尾の 40 が time-taken の値。単位はミリ秒。
IIS 5.0 マニュアル
これだけ。
集計や分析、パフォーマンス劣化の監視などで活用するため、http サーバ側でリクエストを処理したあとレスポンスを返すまでどれだけ時間がかかったかを記録したい。
所要時間などの値は http サーバ上で動くアプリケーション側でロギングする仕組みを作るのが普通。ただ、とりあえず記録さえできればいいときは、http サーバが提供するロギング機能を使ってアクセスログに書かせるのが手っ取り早い。
- Apache 2系
LogFormat ディレクティブ、CustomLog ディレクティブの書式指定文字列で %D を使う。mod_log_config - Apache HTTP サーバ
http://httpd.apache.org/docs/2.2/ja/mod/mod_log_config.html# ...
%D リクエストを処理するのにかかった時間、ミリ秒単位
- Apache 1.3系
標準では出力できない? モジュールなどが必要? 今回は Apache 1.3系は調査対象外なので詳細は調べていない。%T で秒単位ながらもロギングできるらしいが未確認。もっとも、秒単位だとあまりうれしくない。何もないよりはいいけど。- Internet Information Service (IIS) 6.0
インターネット インフォメーション サービス (IIS) マネージャ の「Web サイトのプロパティ」の「Web サイト」タブの「ログの記録を有効にする」の「詳細設定」タブの「所要時間 ( time-taken )」チェックボックスをオンにする。Internet Information Service (IIS) 6.0 ヘルプ
[所要時間]
処理が完了するまでに要した時間を記録するときに選択します。ODBC ログを除くすべてのログ形式では、次の手順に従い、所要時間がミリ秒単位でログが記録されます。HTTP.sys (カーネルモード ドライバ) が、要求の解析を開始する前に最初のバイトを受信すると、クライアント要求のタイムスタンプが初期化されます。IIS で最後の送信が完了すると、クライアント要求のタイムスタンプが停止します。ネットワーク上での時間は所要時間に反映されません。また、サイトへの最初の要求は、他の類似した要求、または同一の要求に比べて、所要時間がわずかに長くなります。これは、HTTP.sys により最初の要求時にログ ファイルが開かれるからです。
以下は出力されるログのサンプル。
#Fields: date time s-sitename s-ip cs-method cs-uri-stem cs-uri-query s-port cs-username c-ip cs-version cs(User-Agent) cs(Referer) sc-status sc-substatus sc-win32-status sc-bytes cs-bytes time-taken
2005-12-27 09:14:59 W3SVC1 127.0.0.1 GET /iisstart.htm - 80 - 127.0.0.1 HTTP/1.1 Mozilla/4.0+(compatible;+MSIE+6.0;+Windows+NT+5.2;+SV1;+.NET+CLR+1.1.4322) - 200 0 0 1842 251 40
末尾の 40 が time-taken の値。単位はミリ秒。
- Internet Information Service (IIS) 5.0
IIS 5 でも IIS 6 と同じ手順で設定可能。ただ、マニュアルの記述が非常に少ない。IIS 5.0 マニュアル
[所要時間] アクションに要した時間。
これだけ。
* gzip/bzip2 は環境変数 GZIP/BZIP2 で圧縮レベルのデフォルト値を指定できる
この記事の直リンクURL: Permlink | この記事が属するカテゴリ: [unix] [Apache] [シェルスクリプト]
2005-12-04 に書いた「gzip 圧縮されたファイルを最強の圧縮レベルで再圧縮」について、いくつか指摘を頂いた。ありがとうございます。本当に、当サイトにも早くコメント欄を付けるべきだなあ・・・。
まずは掲示板 (wiki でしたっけ?) の「イナモデ」に書かれていた指摘。
http://inamode6.tokuhirom.dnsalias.org/show/1827
今回の圧縮対象はテキストのログなので、確かに bzip2 の方が圧縮率は高くなるでしょうね。でも、以下の2つの理由で bzip2 は使いませんでした。
1. さくらインターネットが採用しているログ圧縮やローテーションの仕組みがわからないので、その仕組みによっては bzip2 で圧縮したファイルをさらに gzip 圧縮するとういう動きをするかもしれなかった。gzip や bzip2 コマンドならば、圧縮済みファイルだったら何もしないという動きをすると思いますが、さくらインターネットが採用している仕組みがわからなかったので現状維持で gzip としました。
2. さくらインターネットでは webalizer でアクセスログからレポートを生成しているが、webalizer が bzip2 で圧縮されたログを読めるかどうかわからないので不安だった。たぶん大丈夫なんでしょうけど、そこまで冒険することもないかなあと。
あ、疑問の趣旨は、logrotate や rotatelogs を使ってるとして、そこから呼びだされている gzip や zlib の圧縮レベルを指定してるのはどこなのかな、というものです。それで、logrotate や rotatelogs は圧縮レベルを指定できるオプションを持ってるのかな、というのが元の文の意味です。わかりにくかったですね。
gzip コマンド自体が圧縮レベルを指定できるオプションを持ってるのは知ってたので、とりあえずレベル最強で圧縮し直すという単純な解決法をとったわけですしね。
そういえば、その昔に linux の logrotate を使ってた頃は、何もしなくても gzip -9 と同等の圧縮レベルが指定されてたような気がします。気のせいかもしれないけど・・・。
http://freebsd.g.hatena.ne.jp/TransFreeBSD/20051205/p2
これは知りませんでした。確かに man ページに書いてありました。「よく使ってるコマンドでも man を読むと発見がある」という好例ですね。勉強になりました。
Manpage of GZIP
http://www.linux.or.jp/JM/html/GNU_gzip/man1/gzip.1.html
bzip2 コマンドも同じように環境変数 BZIP2 と BZIP でオプションのデフォルト値を指定できるようですね。
Manpage of bzip2
http://www.linux.or.jp/JM/html/bzip2/man1/bzip2.1.html
ただ、やっぱりさくらインターネットのログ切り出しと圧縮の仕組みがわからないので、2005-12-04 の再圧縮スクリプトを cron で実行という方法を取ろうと思います。運営側で圧縮レベルを上げるという対処をしてくれるかどうかもわからないですしね。とりあえず後でサポートに要望を出しておこうとは思ってます。
そうそう、さくらインターネットのレンタルサーバは FreeBSD でした。uname -a したら以下のように出力されました。
まずは掲示板 (wiki でしたっけ?) の「イナモデ」に書かれていた指摘。
http://inamode6.tokuhirom.dnsalias.org/show/1827
わざわざ再圧縮するんであればbzip2使えばgzip -9より圧縮できるだろう。
今回の圧縮対象はテキストのログなので、確かに bzip2 の方が圧縮率は高くなるでしょうね。でも、以下の2つの理由で bzip2 は使いませんでした。
1. さくらインターネットが採用しているログ圧縮やローテーションの仕組みがわからないので、その仕組みによっては bzip2 で圧縮したファイルをさらに gzip 圧縮するとういう動きをするかもしれなかった。gzip や bzip2 コマンドならば、圧縮済みファイルだったら何もしないという動きをすると思いますが、さくらインターネットが採用している仕組みがわからなかったので現状維持で gzip としました。
2. さくらインターネットでは webalizer でアクセスログからレポートを生成しているが、webalizer が bzip2 で圧縮されたログを読めるかどうかわからないので不安だった。たぶん大丈夫なんでしょうけど、そこまで冒険することもないかなあと。
> ところで、この圧縮率の差が生まれる原因って、logrotate か rotatelogs あたりのオプションにあるのかな? それとも独自のシェルスクリプトか何かのオプションが原因なのかな。
man 1 gzipに「The default compression level is -6 (that is, biased towards high compression at expense of speed). 」と書いてあるって。
あ、疑問の趣旨は、logrotate や rotatelogs を使ってるとして、そこから呼びだされている gzip や zlib の圧縮レベルを指定してるのはどこなのかな、というものです。それで、logrotate や rotatelogs は圧縮レベルを指定できるオプションを持ってるのかな、というのが元の文の意味です。わかりにくかったですね。
gzip コマンド自体が圧縮レベルを指定できるオプションを持ってるのは知ってたので、とりあえずレベル最強で圧縮し直すという単純な解決法をとったわけですしね。
そういえば、その昔に linux の logrotate を使ってた頃は、何もしなくても gzip -9 と同等の圧縮レベルが指定されてたような気がします。気のせいかもしれないけど・・・。
- gzip/bzip2 コマンドは環境変数 GZIP/BZIP2 でオプションのデフォルト値を指定できる
freebsdグループ - TransFreeBSDの日記 - gzipの圧縮レベルを変えるにはhttp://freebsd.g.hatena.ne.jp/TransFreeBSD/20051205/p2
私も、newsyslogとかにオプションとかあるかもと思って、ちょっと探したけどなかった。で、gzipは環境変数「GZIP」にオプションを設定できるらしい。
On-line Manual of ”gzip”
というわけで、newsyslogとかlogrotateとかがこの環境変数を消していなければ、crontabに設定すればわたるはず。
あと、さくらインターネットの専用サーバはFreeBSDが選べますが、レンタルサーバのOSは何だろう。FreeBSDならbzip2の方がお得だと思います。
これは知りませんでした。確かに man ページに書いてありました。「よく使ってるコマンドでも man を読むと発見がある」という好例ですね。勉強になりました。
Manpage of GZIP
http://www.linux.or.jp/JM/html/GNU_gzip/man1/gzip.1.html
環境変数
環境変数 GZIP に gzip のデフォルトのオプションセットを入れることができる。これらのオプションは最初に解釈されるので、明示的なコマンドラインパラメータで上書きすることができる。例を示す。
sh の場合: GZIP="-8v --name"; export GZIP
csh の場合: setenv GZIP "-8v --name"
MSDOS の場合: set GZIP=-8v --name
bzip2 コマンドも同じように環境変数 BZIP2 と BZIP でオプションのデフォルト値を指定できるようですね。
Manpage of bzip2
http://www.linux.or.jp/JM/html/bzip2/man1/bzip2.1.html
bzip2 は環境変数 BZIP2, BZIP からこの順番で引き数を読み込み、コマンドラインから読み込まれた引き数よりも先に処理する。これはデフォルトの引き数を与える便利な方法である。
ただ、やっぱりさくらインターネットのログ切り出しと圧縮の仕組みがわからないので、2005-12-04 の再圧縮スクリプトを cron で実行という方法を取ろうと思います。運営側で圧縮レベルを上げるという対処をしてくれるかどうかもわからないですしね。とりあえず後でサポートに要望を出しておこうとは思ってます。
そうそう、さくらインターネットのレンタルサーバは FreeBSD でした。uname -a したら以下のように出力されました。
$ uname -a
FreeBSD www****.sakura.ne.jp 4.10-RELEASE-p16 FreeBSD 4.10-RELEASE-p16 #0: Thu Aug 4 11:51:09 JST 2005 root@******.sakura.ne.jp:/usr/src/sys/compile/SAKURA8S i386
当サイト http://sonic64.com/ は、さくらインターネットのレンタルサーバーサービスを利用している。レンタルなので、ディスク容量が足りなくなっても自宅のクライアント PC と違って簡単に増設することはできない。ディスク容量は貴重だ。ディスクの1バイトは血の一滴。
で、さくらインターネットでディスクの無駄遣いを見つけた。さくらインターネットでは各ユーザーに apache のアクセスログが提供されていて、最新のログ以外は gzip で自動的に圧縮される。そのときの gzip のレベルが -9 になっていないので、ディスクスペースが無駄になっている。
非常に細かいんだけど、gzip の圧縮レベルがデフォルトの -6 と -9 では、圧縮後のファイルサイズで一日あたり100KB くらいの違いが出てくる。ログは1年分保存されるので、100KB * 365 = 36.5MB の差がある。これはちょっと無視できない。
というわけで、gzip 圧縮されたファイルを最強の圧縮レベルで再圧縮するスクリプトを書いた。
やってることはものすごく単純。単に2日前の圧縮ファイルを find で探し出して展開、その後最高の圧縮レベルを指定した gzip で再圧縮してるだけ。直近1日以内のファイルはまだ gzip されてないので除外。2日以上前のファイルは、このコマンドで処理済みなので除外。
これを cron で一日一回実行するために crontab に仕込んでおく。一応 nice コマンドでプロセスの優先度を最低にしておく。
ここまでやって思ったんだけど、これってさくらインターネットのサポートに要望として出した方がいいかな。そうすれば全ユーザーが幸せになれるな。よし、あとでサポートにメールしておこう。
ところで、この圧縮率の差が生まれる原因って、logrotate か rotatelogs あたりのオプションにあるのかな? それとも独自のシェルスクリプトか何かのオプションが原因なのかな。
追記。
2005-12-07 に『「gzip 圧縮されたファイルを最強の圧縮レベルで再圧縮」への指摘とその反応』を書いた。
で、さくらインターネットでディスクの無駄遣いを見つけた。さくらインターネットでは各ユーザーに apache のアクセスログが提供されていて、最新のログ以外は gzip で自動的に圧縮される。そのときの gzip のレベルが -9 になっていないので、ディスクスペースが無駄になっている。
非常に細かいんだけど、gzip の圧縮レベルがデフォルトの -6 と -9 では、圧縮後のファイルサイズで一日あたり100KB くらいの違いが出てくる。ログは1年分保存されるので、100KB * 365 = 36.5MB の差がある。これはちょっと無視できない。
というわけで、gzip 圧縮されたファイルを最強の圧縮レベルで再圧縮するスクリプトを書いた。
#!/bin/sh
find $HOME/log -type f -maxdepth 1 -mtime +1 -mtime -2 -regex .*.gz |xargs gunzip
find $HOME/log -type f -maxdepth 1 -mtime +1 -mtime -2 ! -regex .*.gz |xargs gzip -9
やってることはものすごく単純。単に2日前の圧縮ファイルを find で探し出して展開、その後最高の圧縮レベルを指定した gzip で再圧縮してるだけ。直近1日以内のファイルはまだ gzip されてないので除外。2日以上前のファイルは、このコマンドで処理済みなので除外。
これを cron で一日一回実行するために crontab に仕込んでおく。一応 nice コマンドでプロセスの優先度を最低にしておく。
# recompress with max compression level
5 6 * * * nice -19 $HOME/script/recompress_log.sh
ここまでやって思ったんだけど、これってさくらインターネットのサポートに要望として出した方がいいかな。そうすれば全ユーザーが幸せになれるな。よし、あとでサポートにメールしておこう。
ところで、この圧縮率の差が生まれる原因って、logrotate か rotatelogs あたりのオプションにあるのかな? それとも独自のシェルスクリプトか何かのオプションが原因なのかな。
追記。
2005-12-07 に『「gzip 圧縮されたファイルを最強の圧縮レベルで再圧縮」への指摘とその反応』を書いた。
* sonic64.com の転送量の80%が RSS
この記事の直リンクURL: Permlink | この記事が属するカテゴリ: [sonic64.com] [Apache] [ネットワーク] [RSS]
当サイト Landscape が infoseek isweb から移転して sonic64.com を使うようになってから、1か月ほど経った。というわけでアクセスログを見てみる。
月間総転送量は 63557488KB。約60GB、一日あたり2GBというところか。なんだか減ってる気がする。旧サイトを置いていた infoseek isweb では、多いときで一日5GB、少ないときで1GBくらいだったと思う。平均すると一日あたり 3GB から 4GB くらいだったかな。移転後に転送量が減ってる理由は、移転に伴って検索エンジンの順位が下がったりとか、あとは 2005-09-15 の「mod_rewrite でリクエストに応じて gzip 圧縮ファイルを返す」などの効果のためだろう。
解析の結果、転送量のほとんどが http://sonic64.com/cl-full.xml に集中してることがわかった。60GB の転送量のうち、50816191KB、すなわち 50GB 程度がこの RSS の転送のために使われていた。cl-full.xml は「すべての記事全文を含む RSS」 なので、サイズが大きいのは仕方がないが、それが sonic64.com の転送量の約80%を占めているとは、ちょっと意外だった。20%80%のパレートの法則を超えるような状態だ。
ちなみに、トップページ http://sonic64.com/ の月間転送量は 471425KB で、471MB。全体に占める割合はたったの 0.74%。まあ、こんなものでしょ。
月間60GBという転送量は、私の見積もりの範囲内なのでまったく問題ない。必要ならどんどんアクセスしてほしい。全体の転送量の80%が RSS というのはちょっと意外だったのだけど。infoseek からの移転先としてさくらインターネットを選んだ理由の一つは、転送量制限の値が高めで余裕があるからだ。
cl-full.xml は今日現在 4MB 弱のサイズがあるが、Accept-Encoding: gzip をつけて送れば900KB くらいに圧縮されたデータがサーバから返される。そのために 2005-09-15 の「mod_rewrite でリクエストに応じて gzip 圧縮ファイルを返す」などの仕組みを取り入れたんだけど、それが使われてなかったとしたら残念だ。無駄は嫌いなんだ。無駄、無駄、と。昨日のアクセスログを grep して調べてみる。
上記のようにして 200 OK で完結したリクエストのログをざーっと眺めてみる。うん、以下の Mozilla Thunderbird は圧縮された cl-full.xml を取ってきてるね。返された cl-full.xml のサイズが 942606 バイトになってる。
一方、 Accept-Encoding: gzip をリクエストにつけていないため、圧縮してないデータを返されたクライアントも結構あった。とくに、ロボットや Web 型 RSS リーダーのクローラーにこの傾向が強いように感じる。
以下、UserAgent 名だけ列挙。
Bloglines はちゃんと Accept-Encoding: gzip をつけてリクエストしてる。さすがだね。
そうだ。Accept-Encoding: gzip を送ってきてないクライアントは、HTTP 302 Moved Temporarily で RSS 広告社の Trend Match の RSS http://rss.rssad.jp/rss/qArzgZHGLg5Z/rss_0002 にリダイレクトするようにしよう。Trend Match から 返される RSS は gzip 圧縮がかかってないから、問答無用で圧縮したファイルを返すよりもずっといい。
2005-10-12 の「RSS広告社の広告プログラム Trend Match に参加」で書いたように、http://rss.rssad.jp/rss/qArzgZHGLg5Z/rss_0002 にアクセすると、Internal Server Error になってしまっていた。その後、2005-10-26 の「RSS広告社の Trend Match の RSS サイズ制限が緩和された?」で、エラーにならずにアクセスできるようになったのを確認した。正式対応のアナウンスはまだ無いが、いまのところ問題がない。そのうち cl-full.xml への全リクエストをリダイレクトしようかと思っていた。でもやっぱりいきなり全リクエストをリダイレクトするのは不安なので、まず一部だけでテストしたいところ。Accept-Encoding: gzip を送ってきてないリクエストにそのテスターになってもらうことにしよう。
さっそく mod_rewrite の RewriteRule を書こう。
できた。これを .htaccess に設定して、Firefox と LiveHttpHeaders の HTTP リクエストリプレイ機能で、Accept-Encoding: gzip があるリクエストと、ないリクエストの両方でテスト。よし、OK だ。これで全員が幸せになれるね。Win-Win-Win だ。
- Landscape - エンジニアのメモ 2005年10月のアクセス
総ページビューとか、人気のあったページとかはまあどうでもいいんだけど、いちおうメモ。ページビューは372969。約37万。Webalizer によるページビューなので、今までとはカウント方法が異なっている可能性はある。月間総転送量は 63557488KB。約60GB、一日あたり2GBというところか。なんだか減ってる気がする。旧サイトを置いていた infoseek isweb では、多いときで一日5GB、少ないときで1GBくらいだったと思う。平均すると一日あたり 3GB から 4GB くらいだったかな。移転後に転送量が減ってる理由は、移転に伴って検索エンジンの順位が下がったりとか、あとは 2005-09-15 の「mod_rewrite でリクエストに応じて gzip 圧縮ファイルを返す」などの効果のためだろう。
- sonic64.com の転送量の80%が RSS
興味深かったのが、転送量の内訳。infoseek ではこういう統計は提供されなかったし、httpd の生のアクセスログも提供されなかったので調べられなかったけど、今は生ログがもらえるので解析できた。解析の結果、転送量のほとんどが http://sonic64.com/cl-full.xml に集中してることがわかった。60GB の転送量のうち、50816191KB、すなわち 50GB 程度がこの RSS の転送のために使われていた。cl-full.xml は「すべての記事全文を含む RSS」 なので、サイズが大きいのは仕方がないが、それが sonic64.com の転送量の約80%を占めているとは、ちょっと意外だった。20%80%のパレートの法則を超えるような状態だ。
ちなみに、トップページ http://sonic64.com/ の月間転送量は 471425KB で、471MB。全体に占める割合はたったの 0.74%。まあ、こんなものでしょ。
月間60GBという転送量は、私の見積もりの範囲内なのでまったく問題ない。必要ならどんどんアクセスしてほしい。全体の転送量の80%が RSS というのはちょっと意外だったのだけど。infoseek からの移転先としてさくらインターネットを選んだ理由の一つは、転送量制限の値が高めで余裕があるからだ。
- HTTP リクエストヘッダ Accept-Encoding: gzip をちゃんと送ってる?
気になるのは、http://sonic64.com/cl-full.xml へアクセスするときに、HTTP リクエストヘッダ Accept-Encoding: gzip を送ってないクライアントが多いんじゃないかということ。cl-full.xml は今日現在 4MB 弱のサイズがあるが、Accept-Encoding: gzip をつけて送れば900KB くらいに圧縮されたデータがサーバから返される。そのために 2005-09-15 の「mod_rewrite でリクエストに応じて gzip 圧縮ファイルを返す」などの仕組みを取り入れたんだけど、それが使われてなかったとしたら残念だ。無駄は嫌いなんだ。無駄、無駄、と。昨日のアクセスログを grep して調べてみる。
grep 'GET /cl-full.xml' ~/log/access_log_`date +%Y%m%d` |grep ' 200 '
上記のようにして 200 OK で完結したリクエストのログをざーっと眺めてみる。うん、以下の Mozilla Thunderbird は圧縮された cl-full.xml を取ってきてるね。返された cl-full.xml のサイズが 942606 バイトになってる。
"GET /cl-full.xml HTTP/1.1" 200 942606 "-" "Mozilla/5.0 (Windows; U; Windows NT 5.1; ja-JP; rv:1.7.10) Gecko/20050716 Thunderbird/1.0.6"
一方、 Accept-Encoding: gzip をリクエストにつけていないため、圧縮してないデータを返されたクライアントも結構あった。とくに、ロボットや Web 型 RSS リーダーのクローラーにこの傾向が強いように感じる。
"GET /cl-full.xml HTTP/1.1" 200 4147427 "-" "Mozilla/4.0 (compatible; Google Desktop)"
以下、UserAgent 名だけ列挙。
InfoSeek RssReader/0.1"
FEEDBRINGER/0.1 (http://feedbringer.net/; 10 subscribers)"
Google Desktop
blogWatcher_crawler/0.2
Accelatech RSSCrawler/0.4
HepCat: http://www.witha.jp/
Hatena RSS/0.2 (http://r.hatena.ne.jp; 7 subscribers)
cococ/1.04
gooRSSreader2/2.0-build 20050818 (based on glucose)
Headline-Reader [t] (http://www.infomaker.jp/)
Bloglines はちゃんと Accept-Encoding: gzip をつけてリクエストしてる。さすがだね。
- Accept-Encoding: gzip なしのリクエストは mod_rewrite でリダイレクト
今のところ転送量にはまだ余裕がある。Accept-Encoding: gzip なしのリクエストを、圧縮しなきゃダメと 403 Forbidden で追い返したり、問答無用で圧縮したファイルを返すようなことはしたくない。でも、今時の HTTP クライアントなら Accept-Encoding: gzip くらい送ってきて欲しい。一般的なクライアント型 RSS リーダーだと、ファイアウォールソフトや proxy によって Accept-Encoding: gzip を削除されてしまうことがあるようだけど、ロボット系はそんなこともないだろうしなあ。そうだ。Accept-Encoding: gzip を送ってきてないクライアントは、HTTP 302 Moved Temporarily で RSS 広告社の Trend Match の RSS http://rss.rssad.jp/rss/qArzgZHGLg5Z/rss_0002 にリダイレクトするようにしよう。Trend Match から 返される RSS は gzip 圧縮がかかってないから、問答無用で圧縮したファイルを返すよりもずっといい。
2005-10-12 の「RSS広告社の広告プログラム Trend Match に参加」で書いたように、http://rss.rssad.jp/rss/qArzgZHGLg5Z/rss_0002 にアクセすると、Internal Server Error になってしまっていた。その後、2005-10-26 の「RSS広告社の Trend Match の RSS サイズ制限が緩和された?」で、エラーにならずにアクセスできるようになったのを確認した。正式対応のアナウンスはまだ無いが、いまのところ問題がない。そのうち cl-full.xml への全リクエストをリダイレクトしようかと思っていた。でもやっぱりいきなり全リクエストをリダイレクトするのは不安なので、まず一部だけでテストしたいところ。Accept-Encoding: gzip を送ってきてないリクエストにそのテスターになってもらうことにしよう。
さっそく mod_rewrite の RewriteRule を書こう。
RewriteCond %{REQUEST_URI} cl-full\.xml(\.gz)?$
RewriteCond %{HTTP:Accept-Encoding} !gzip
RewriteRule .+ http://rss.rssad.jp/rss/qArzgZHGLg5Z/rss_0002 [L,R]
できた。これを .htaccess に設定して、Firefox と LiveHttpHeaders の HTTP リクエストリプレイ機能で、Accept-Encoding: gzip があるリクエストと、ないリクエストの両方でテスト。よし、OK だ。これで全員が幸せになれるね。Win-Win-Win だ。
2005-09-14 の「Content Negotiation でリクエストに応じて gzip 圧縮ファイルを返す」の続き。
cl.xml にリクエストされたときは Content Negotiation が働かず、拡張子が一切ない cl にアクセスされたときは Content Negotiation が働くという挙動だった。これでは使いにくいので、mod_rewrite を使って圧縮ファイルを返すようにする。
RewriteRule の各行の説明。
%{HTTP:Accept-Encoding} gzip は、クライアントの HTTP リクエストヘッダ中に Accept-Encoding が存在し、かつ文字列 gzip が含まれているかどうかをチェックしている。単なる正規表現によるチェックなので sonic64gzip などという文字列でもマッチしちゃうけど、まあいいでしょ。ちなみに、Accpet-Encodind: sonic4gzip で実際にリクエストして RewriteLog を見たら matched になってた。
RewriteCond %{REQUEST_FILENAME} !\.gz$ は、最初から .gz ファイルをアクセスされた場合は Rewrite しないための判定。これをやらないと、file.html.gz にアクセスがあった場合に file.html.gz.gz を探しに行ってしまう。もっとも、次の RewriteCond %{REQUEST_FILENAME}\.gz -s での gz ファイル存在チェックで弾かれるから、やらなくてもとりあえずは動く。あ、でもこれだと png や .jpg もチェック対象になるよなあ。やっぱり .html や .xml だけを対象として明示した方がいいかなあ。
RewriteCond %{REQUEST_FILENAME}\.gz -s は、.gz ファイルが存在するかどうかをチェックしている。.gz ファイルが存在しない場合は Rewrite せずにもとのリクエストのファイルを返す。
RewriteLog の出力内容。
mod_rewrite で任意の HTTP レスポンスヘッダを生成させることってできるのかな? 環境変数の値を設定することはできるけど、直接ヘッダに追加する方法は見つけられなかった。あ、HEADER ディレクティブを使えばいいのか。以下のような感じかな?
ただ、これだといつも Vary を送ってしまうので、レスポンスに Content-Encoding: gzip があるときだけ Vary を返すようにしたいけど、環境変数の値に応じて条件分岐できるのは Apache 2.0 系だけ? ドキュメントを読んでみる。
mod_headers - Apache HTTP サーバ Header ディレクティブ
http://httpd.apache.org/docs/2.1/mod/mod_headers.html#header
Apache module mod_headers
http://httpd.apache.org/docs/1.3/mod/mod_headers.html
1.3 系のドキュメントには env= という構文が使えるとは書いてないもんなあ。どちらにせよ、mod_header のモジュールがステータス: Extension じゃあ使えない可能性が高い。
そもそも Vary ヘッダの付加って 必須? 推奨? HTTP1.1 を規定している RFC 2616 ではどうなってる?
ハイパーテキスト転送プロトコル -- HTTP/1.1
http://www.studyinghttp.net/cgi-bin/rfc.cgi?2616#Sec14.44
「含まなければならない」ではなく、「含むべきである」なので必須ではないということか。じゃあ、とりあえずは Vary なしでいいか。
追記。
Internet Explorer がクライアントとき、HTTP レスポンスで Vary ヘッダを返してしまうと、IE は HTTP リクエストヘッダ If-Modified-Since を送信してこないという問題があるようだ。その結果、あらゆるレスポンスを 200 OK で返さざるを得なくなり、304 Not Modified によるレスポンスを返せなくなる。Vary を送らなければこの問題は回避できるようなので、Content-Encoding しか返さない方が総合的に見れば問題が少ないかも。
cl.xml にリクエストがあったとき、Accept-Encoding に gzip があり、かつ cl.xml.gz がサーバに存在していたら Content-Encoding: gzip で cl.xml.gz の中身を返すようにしたい
cl.xml にリクエストされたときは Content Negotiation が働かず、拡張子が一切ない cl にアクセスされたときは Content Negotiation が働くという挙動だった。これでは使いにくいので、mod_rewrite を使って圧縮ファイルを返すようにする。
- mod_rewrite を利用した Content Negotioation の代替の .htaccess サンプル
以下の RewriteRule を書いた。とりあえず希望の動作は実現できた。RewriteEngine on
RewriteCond %{HTTP:Accept-Encoding} gzip
RewriteCond %{REQUEST_FILENAME} !\.gz$
RewriteCond %{REQUEST_FILENAME}\.gz -s
RewriteRule .+ %{REQUEST_URI}.gz
RewriteRule の各行の説明。
%{HTTP:Accept-Encoding} gzip は、クライアントの HTTP リクエストヘッダ中に Accept-Encoding が存在し、かつ文字列 gzip が含まれているかどうかをチェックしている。単なる正規表現によるチェックなので sonic64gzip などという文字列でもマッチしちゃうけど、まあいいでしょ。ちなみに、Accpet-Encodind: sonic4gzip で実際にリクエストして RewriteLog を見たら matched になってた。
(4) RewriteCond: input='sonic64gzip' pattern='gzip' => matched
RewriteCond %{REQUEST_FILENAME} !\.gz$ は、最初から .gz ファイルをアクセスされた場合は Rewrite しないための判定。これをやらないと、file.html.gz にアクセスがあった場合に file.html.gz.gz を探しに行ってしまう。もっとも、次の RewriteCond %{REQUEST_FILENAME}\.gz -s での gz ファイル存在チェックで弾かれるから、やらなくてもとりあえずは動く。あ、でもこれだと png や .jpg もチェック対象になるよなあ。やっぱり .html や .xml だけを対象として明示した方がいいかなあ。
RewriteCond %{REQUEST_FILENAME}\.gz -s は、.gz ファイルが存在するかどうかをチェックしている。.gz ファイルが存在しない場合は Rewrite せずにもとのリクエストのファイルを返す。
- HTTP トランザクションの中身
ブラウザでリクエストした結果。http://sonic64.com/cl.xml
GET /cl.xml HTTP/1.1
Host: sonic64.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; ja-JP; rv:1.7.10) Gecko/20050717 Firefox/1.0.6
Accept: text/xml,application/xml,application/xhtml+xml, text/html;q=0.9,text/plain;q=0.8, image/png,*/*;q=0.5
Accept-Language: ja,en-us;q=0.7,en;q=0.3
Accept-Encoding: gzip,deflate
Accept-Charset: Shift_JIS,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
HTTP/1.x 200 OK
Date: Wed, 14 Sep 2005 22:51:21 GMT
Server: Apache/1.3.33 (Unix)
Last-Modified: Tue, 13 Sep 2005 23:08:14 GMT
Etag: "3391fb-2ecc-43275bde"
Accept-Ranges: bytes
Content-Length: 11980
Keep-Alive: timeout=3, max=8
Connection: Keep-Alive
Content-Type: application/xml
Content-Encoding: gzip
RewriteLog の出力内容。
10.9.7.2 - - [14/Sep/2005:23:35:52 +0900] [192.168.0.38/sid#804b97c][rid#80941ac/initial] (3) [per-dir /home/hiroaki/public_html/test/] strip per-dir prefix: /home/hiroaki/public_html/test/cl.xml -> cl.xml
10.9.7.2 - - [14/Sep/2005:23:35:52 +0900] [192.168.0.38/sid#804b97c][rid#80941ac/initial] (3) [per-dir /home/hiroaki/public_html/test/] applying pattern '.+' to uri 'cl.xml'
10.9.7.2 - - [14/Sep/2005:23:35:52 +0900] [192.168.0.38/sid#804b97c][rid#80941ac/initial] (4) RewriteCond: input='gzip' pattern='gzip' => matched
10.9.7.2 - - [14/Sep/2005:23:35:52 +0900] [192.168.0.38/sid#804b97c][rid#80941ac/initial] (4) RewriteCond: input='/home/hiroaki/public_html/test/cl.xml' pattern='!\.gz$' => matched
10.9.7.2 - - [14/Sep/2005:23:35:52 +0900] [192.168.0.38/sid#804b97c][rid#80941ac/initial] (4) RewriteCond: input='/home/hiroaki/public_html/test/cl.xml.gz' pattern='-s' => matched
10.9.7.2 - - [14/Sep/2005:23:35:52 +0900] [192.168.0.38/sid#804b97c][rid#80941ac/initial] (2) [per-dir /home/hiroaki/public_html/test/] rewrite cl.xml -> /~hiroaki/test/cl.xml.gz
10.9.7.2 - - [14/Sep/2005:23:35:52 +0900] [192.168.0.38/sid#804b97c][rid#80941ac/initial] (1) [per-dir /home/hiroaki/public_html/test/] internal redirect with /~hiroaki/test/cl.xml.gz [INTERNAL REDIRECT]
10.9.7.2 - - [14/Sep/2005:23:35:52 +0900] [192.168.0.38/sid#804b97c][rid#8095f1c/initial/redir#1] (3) [per-dir /home/hiroaki/public_html/test/] strip per-dir prefix: /home/hiroaki/public_html/test/cl.xml.gz -> cl.xml.gz
10.9.7.2 - - [14/Sep/2005:23:35:52 +0900] [192.168.0.38/sid#804b97c][rid#8095f1c/initial/redir#1] (3) [per-dir /home/hiroaki/public_html/test/] applying pattern '.+' to uri 'cl.xml.gz'
10.9.7.2 - - [14/Sep/2005:23:35:52 +0900] [192.168.0.38/sid#804b97c][rid#8095f1c/initial/redir#1] (4) RewriteCond: input='gzip' pattern='gzip' => matched
10.9.7.2 - - [14/Sep/2005:23:35:52 +0900] [192.168.0.38/sid#804b97c][rid#8095f1c/initial/redir#1] (4) RewriteCond: input='/home/hiroaki/public_html/test/cl.xml.gz' pattern='!\.gz$' => not-matched
10.9.7.2 - - [14/Sep/2005:23:35:52 +0900] [192.168.0.38/sid#804b97c][rid#8095f1c/initial/redir#1] (1) [per-dir /home/hiroaki/public_html/test/] pass through /home/hiroaki/public_html/test/cl.xml.gz
- Vary ヘッダを出力した方がより明示的
HTTP レスポンスヘッダに Vary: Accept-Encoding を追加するとより理想的な動きになる。Vary ヘッダは 2004-02-06 「http の Vary レスポンスヘッダの意味と使用例」で書いた。mod_rewrite で任意の HTTP レスポンスヘッダを生成させることってできるのかな? 環境変数の値を設定することはできるけど、直接ヘッダに追加する方法は見つけられなかった。あ、HEADER ディレクティブを使えばいいのか。以下のような感じかな?
Header append Vary Accept-Encoding
ただ、これだといつも Vary を送ってしまうので、レスポンスに Content-Encoding: gzip があるときだけ Vary を返すようにしたいけど、環境変数の値に応じて条件分岐できるのは Apache 2.0 系だけ? ドキュメントを読んでみる。
mod_headers - Apache HTTP サーバ Header ディレクティブ
http://httpd.apache.org/docs/2.1/mod/mod_headers.html#header
構文: Header [condition] set|append|add|unset|echo header [value] [early|env=[!]variable]
Apache module mod_headers
http://httpd.apache.org/docs/1.3/mod/mod_headers.html
Syntax: Header set|append|add header value
1.3 系のドキュメントには env= という構文が使えるとは書いてないもんなあ。どちらにせよ、mod_header のモジュールがステータス: Extension じゃあ使えない可能性が高い。
そもそも Vary ヘッダの付加って 必須? 推奨? HTTP1.1 を規定している RFC 2616 ではどうなってる?
ハイパーテキスト転送プロトコル -- HTTP/1.1
http://www.studyinghttp.net/cgi-bin/rfc.cgi?2616#Sec14.44
HTTP/1.1 サーバは、サーバ駆動型ネゴシエーションを受けるあらゆるキャッシュ可能なレスポンスに Vary ヘッダフィールド値を含むべきである。そうする事で、キャッシュはそのリソースへの将来のリクエストを適切に解釈する事ができ、ユーザエージェントにそのリソースへのネゴシエーションの存在について知らせる事ができる。サーバは、サーバ駆動型ネゴシエーションを受けるキャッシュ不可能なレスポンスにも、ユーザエージェントにそのレスポンス時には変化してしまうレスポンスのについての有益な情報を提供するであろうから、Vary ヘッダフィールド値を含む事ができる。
「含まなければならない」ではなく、「含むべきである」なので必須ではないということか。じゃあ、とりあえずは Vary なしでいいか。
追記。
Internet Explorer がクライアントとき、HTTP レスポンスで Vary ヘッダを返してしまうと、IE は HTTP リクエストヘッダ If-Modified-Since を送信してこないという問題があるようだ。その結果、あらゆるレスポンスを 200 OK で返さざるを得なくなり、304 Not Modified によるレスポンスを返せなくなる。Vary を送らなければこの問題は回避できるようなので、Content-Encoding しか返さない方が総合的に見れば問題が少ないかも。
* Content Negotiation でリクエストに応じて gzip 圧縮ファイルを返す
HTTP の Content Negotiation 機能を使うと、ユーザの環境に応じて適切なファイルを返すことができる。
これを利用すると、以下を実現できる。
HTTP リクエストヘッダ Accept-Encoding: gzip を送ってきているクライアントにはあらかじめ gzip で圧縮しておいたファイルを返し、Accept-Encoding: gzip を送ってきていないクライアントには gzip 圧縮していない生のファイルを返す。
動的に生成するページを圧縮したいなら、mod_gzip や mod_deflate のフィルタを使うのがよいだろう。しかし、静的ページならば、あらかじめ圧縮したファイルを返す方式でも良い。毎回圧縮をしないので、CPU 資源を節約することにもつながる。
また、mod_gzip や mod_deflate が使えないサーバでも圧縮を利用できるというメリットがある。現在 sonic64.com はさくらインターネットのホスティングサービスを利用している。残念なことに私の利用しているコースでは mod_gzip が使えない。Content Negotiation 以外の方法でコンテンツを圧縮して返そうと思ったら、すべてを CGI にしてコード中で圧縮するか、proxy を挟むくらいしか方法がない。
コンテントネゴシエーション - Apache HTTP サーバ
http://httpd.apache.org/docs/2.1/content-negotiation.html
MultiViews が有効になると、クライアントのリクエストに応じてサーバが最適なファイルを選択して返すようになる。
私の使っているサーバは httpd.conf で MuitiViews が有効になっているようなので試してみる。
まず、生のファイルと、圧縮して拡張子 .gz を付加したファイルを用意する。
ブラウザからリクエスト。
あれ? うまく Content-Negotiation されてないな。なんで? リクエストするファイル名を変えてみるか。cl.xml じゃなくて、cl をリクエストしてみよう。
今度はうまくいった。Content-Location: cl.xml.gz と Vary: negotiate,accept-encoding が返されている。cl でリクエストすると Content-Negotiation が働き、cl.xml でリクエストしたときは Content-Negotiation がなされない。なんでだろう?
コンテントネゴシエーション - Apache HTTP サーバ
http://httpd.apache.org/docs/2.1/ja/content-negotiation.html
上記ページの説明を見る限り、foo.MIME-TYPE.ENCODING のファイルは foo.MIME-TYPE でアクセスできるはずなんだけどなあ。 とりあえずなんで cl でないとダメなのかは後で調べてみよう。
これはとくに Content-Negtiation とは関係ない。以下のように、cl.xml.gz を直接リクエストしたときも Content-Encoding: gzip がサーバから返されている。
Content-Encoding: gzip を返しているのは mod_mime だ。Apache のデフォルトでこの機能が働くように httpd.conf が記述されている。httpd.conf の以下の部分が該当箇所。もし設定されていなければ、.htaccess で設定する。
ん? 今気づいたけど、拡張子 tgz も AddEncodding してるの? それだと tgz という拡張子を付けたファイルの場合、クライアント側で gzip を展開してしまうので gzip されたファイルを保存したいとユーザが考えたときなどに問題になるんじゃないのかな? Google で AddEncoding x-gzip gz tgz を検索するとヒット。
Re: .tar.gz -> .tgz
http://search.fml.org/mlarchives/fml-help/200107/msg397.html
あ、やっぱり。.tar.gz という拡張子なら Content-Encoding が付かないので問題ないが、.tgz にすると Content-Encoding が付いてしまうので展開さてしまうと。まあ tgz なんて拡張子を使わないようにするか、AddEncoding から tgz を消せば良いだけだから実害は少ないかな。
cl.xml にリクエストがあったとき、Accept-Encoding に gzip があり、かつ cl.xml.gz がサーバに存在していたら Content-Encoding: gzip で cl.xml.gz の中身を返すようにしたいだけなんだけどなあ。cl.xml を CGI にでもすれば簡単にできるけど、それじゃあサーバに余計な負荷をかけてしまう。しょうがない、mod_rewrite を使うか。明日やってみよう。
これを利用すると、以下を実現できる。
HTTP リクエストヘッダ Accept-Encoding: gzip を送ってきているクライアントにはあらかじめ gzip で圧縮しておいたファイルを返し、Accept-Encoding: gzip を送ってきていないクライアントには gzip 圧縮していない生のファイルを返す。
- Content Negotiation を利用して圧縮ファイルを返す手法と mod_gzip/mod_deflate の違い
クライアントが送ってくるリクエストヘッダに応じて、圧縮したデータまたは生のデータを返す。一見 mod_gzip や mod_deflate がやっていることと同じように見えるが、違う。最大の違いは動的にファイルを圧縮するのではなく、最初から圧縮済みのファイルを置いておくことにある。動的に生成するページを圧縮したいなら、mod_gzip や mod_deflate のフィルタを使うのがよいだろう。しかし、静的ページならば、あらかじめ圧縮したファイルを返す方式でも良い。毎回圧縮をしないので、CPU 資源を節約することにもつながる。
また、mod_gzip や mod_deflate が使えないサーバでも圧縮を利用できるというメリットがある。現在 sonic64.com はさくらインターネットのホスティングサービスを利用している。残念なことに私の利用しているコースでは mod_gzip が使えない。Content Negotiation 以外の方法でコンテンツを圧縮して返そうと思ったら、すべてを CGI にしてコード中で圧縮するか、proxy を挟むくらいしか方法がない。
- Apache で Content Negotiation
Apache だと MultiViews を使う。コンテントネゴシエーション - Apache HTTP サーバ
http://httpd.apache.org/docs/2.1/content-negotiation.html
Multiviews
MultiViews はディレクトリ毎のオプションで、 httpd.confファイルの <Directory>, <Location>, <Files> セクション中や、(AllowOverride が適切な値に 設定されていると) .htaccess ファイルで Options ディレクティブによって設定することができます。 Options All は MultiViews をセットしないことに注意してください。明示的に その名前を書く必要があります。
MultiViews が有効になると、クライアントのリクエストに応じてサーバが最適なファイルを選択して返すようになる。
私の使っているサーバは httpd.conf で MuitiViews が有効になっているようなので試してみる。
まず、生のファイルと、圧縮して拡張子 .gz を付加したファイルを用意する。
[21:27:32][hiroaki@sonic64 ~/public_html/test]$ ls -l
-rw----r-- 1 hiroaki users 39751 Sep 13 21:18 cl.xml
-rw----r-- 1 hiroaki users 9785 Sep 13 21:18 cl.xml.gz
ブラウザからリクエスト。
http://sonic64.com/test/cl.xml
GET /test/cl.xml HTTP/1.1
Host: sonic64.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; ja-JP; rv:1.7.10) Gecko/20050717 Firefox/1.0.6
Accept: text/xml, application/xml,application/xhtml+xml, text/html;q=0.9,text/plain;q=0.8, image/png,*/*;q=0.5
Accept-Language: ja,en-us;q=0.7,en;q=0.3
Accept-Encoding: gzip,deflate
Accept-Charset: Shift_JIS,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
HTTP/1.x 200 OK
Date: Tue, 13 Sep 2005 12:29:40 GMT
Server: Apache/1.3.33 (Unix)
Last-Modified: Tue, 13 Sep 2005 12:18:00 GMT
Etag: "3449c4-9b47-4326c378"
Accept-Ranges: bytes
Content-Length: 39751
Keep-Alive: timeout=3, max=8
Connection: Keep-Alive
Content-Type: application/xml
あれ? うまく Content-Negotiation されてないな。なんで? リクエストするファイル名を変えてみるか。cl.xml じゃなくて、cl をリクエストしてみよう。
http://sonic64.com/test/cl
GET /test/cl HTTP/1.1
Host: sonic64.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; ja-JP; rv:1.7.10) Gecko/20050717 Firefox/1.0.6
Accept: text/xml, application/xml,application/xhtml+xml, text/html;q=0.9,text/plain;q=0.8, image/png,*/*;q=0.5
Accept-Language: ja,en-us;q=0.7,en;q=0.3
Accept-Encoding: gzip,deflate
Accept-Charset: Shift_JIS,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
HTTP/1.x 200 OK
Date: Tue, 13 Sep 2005 12:35:24 GMT
Server: Apache/1.3.33 (Unix)
Content-Location: cl.xml.gz
Vary: negotiate,accept-encoding
TCN: choice
Last-Modified: Tue, 13 Sep 2005 12:18:02 GMT
Etag: "3449c8-2639-4326c37a;4326c74b"
Accept-Ranges: bytes
Content-Length: 9785
Keep-Alive: timeout=3, max=8
Connection: Keep-Alive
Content-Type: application/xml
Content-Encoding: gzip
今度はうまくいった。Content-Location: cl.xml.gz と Vary: negotiate,accept-encoding が返されている。cl でリクエストすると Content-Negotiation が働き、cl.xml でリクエストしたときは Content-Negotiation がなされない。なんでだろう?
コンテントネゴシエーション - Apache HTTP サーバ
http://httpd.apache.org/docs/2.1/ja/content-negotiation.html
リンクに MIME タイプを使い続けたい (例えば foo.html)時は、言語拡張子は (エンコーディング拡張子もあればそれも含めて) MIME タイプ拡張子の右側になければなりません (例えば foo.html.en)。
上記ページの説明を見る限り、foo.MIME-TYPE.ENCODING のファイルは foo.MIME-TYPE でアクセスできるはずなんだけどなあ。 とりあえずなんで cl でないとダメなのかは後で調べてみよう。
- mod_mime が 拡張子 gz のファイルを Content-Encoding: gzip にしている
うまく Content-Negotiation がなされて gz ファイルが返されたとき、Content-Encoding: gzip も同時にサーバから返されていて、いわゆる「透過的な圧縮」になっている。mod_gzip や mod_deflate を使ったときと同じような、HTTP 転送データそのものを圧縮している状態だ。これはとくに Content-Negtiation とは関係ない。以下のように、cl.xml.gz を直接リクエストしたときも Content-Encoding: gzip がサーバから返されている。
http://sonic64.com/test/cl.xml.gz
GET /test/cl.xml.gz HTTP/1.1
Host: sonic64.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; ja-JP; rv:1.7.10) Gecko/20050717 Firefox/1.0.6
Accept: text/xml, application/xml,application/xhtml+xml, text/html;q=0.9,text/plain;q=0.8, image/png,*/*;q=0.5
Accept-Language: ja,en-us;q=0.7,en;q=0.3
Accept-Encoding: gzip,deflate
Accept-Charset: Shift_JIS,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
HTTP/1.x 200 OK
Date: Tue, 13 Sep 2005 12:42:35 GMT
Server: Apache/1.3.33 (Unix)
Last-Modified: Tue, 13 Sep 2005 12:18:02 GMT
Etag: "3449c8-2639-4326c37a"
Accept-Ranges: bytes
Content-Length: 9785
Keep-Alive: timeout=3, max=8
Connection: Keep-Alive
Content-Type: application/xml
Content-Encoding: gzip
Content-Encoding: gzip を返しているのは mod_mime だ。Apache のデフォルトでこの機能が働くように httpd.conf が記述されている。httpd.conf の以下の部分が該当箇所。もし設定されていなければ、.htaccess で設定する。
<IfModule mod_mime.c>
#
# AddEncoding allows you to have certain browsers (Mosaic/X 2.1+) uncompress
# information on the fly. Note: Not all browsers support this.
# Despite the name similarity, the following Add* directives have nothing
# to do with the FancyIndexing customization directives above.
#
AddEncoding x-compress Z
AddEncoding x-gzip gz tgz
ん? 今気づいたけど、拡張子 tgz も AddEncodding してるの? それだと tgz という拡張子を付けたファイルの場合、クライアント側で gzip を展開してしまうので gzip されたファイルを保存したいとユーザが考えたときなどに問題になるんじゃないのかな? Google で AddEncoding x-gzip gz tgz を検索するとヒット。
Re: .tar.gz -> .tgz
http://search.fml.org/mlarchives/fml-help/200107/msg397.html
gz が展開されてしまう、という問題ですよね?
あ、やっぱり。.tar.gz という拡張子なら Content-Encoding が付かないので問題ないが、.tgz にすると Content-Encoding が付いてしまうので展開さてしまうと。まあ tgz なんて拡張子を使わないようにするか、AddEncoding から tgz を消せば良いだけだから実害は少ないかな。
- cl.xml へのリクエストを cl.xml.gz にネゴシエーションしたい
うーん、なんだか今日はやりたいことができなかったなあ。cl.xml にリクエストがあったとき、Accept-Encoding に gzip があり、かつ cl.xml.gz がサーバに存在していたら Content-Encoding: gzip で cl.xml.gz の中身を返すようにしたいだけなんだけどなあ。cl.xml を CGI にでもすれば簡単にできるけど、それじゃあサーバに余計な負荷をかけてしまう。しょうがない、mod_rewrite を使うか。明日やってみよう。
.htaccess に読み込み権限がないと、403 Forbidden になる。
画像のパーミッションは 644、ホームディレクトリのパーミッションは 755、ウェブ公開用のディレクトリのパーミッションは 711 を指定している。
サーバ管理者に何か制限をかけられてしまったのかと思い質問メールを送ったところ、以下のような回答が返ってきた。
確認するとまさにそう。suExec なサーバなので、CGI ファイルのパーミッションをまとめて 700 に変更したときに .htaccess が混入していたようだ。いつもならエラーログを見てすぐに気づくところだが、このサーバはエラーログをユーザに提供していないのでわからなかった。access_log は見られるんだけどね。
.htaccess を読めないから読めるようにしてね、というそのままなエラーメッセージだ。
- 今日の失敗 .htaccess 編
とあるサーバで、あらゆるファイル、あらゆる CGI が 403 Forbidden を返すようになってしまった。試しにダミーの jpeg ファイルを置いてブラウザからアクセスしても、403 Forbidden となってしまう。おかしい。画像のパーミッションは 644、ホームディレクトリのパーミッションは 755、ウェブ公開用のディレクトリのパーミッションは 711 を指定している。
サーバ管理者に何か制限をかけられてしまったのかと思い質問メールを送ったところ、以下のような回答が返ってきた。
あなたが設置した .htaccess のパーミッションが 700 になっています。
確認するとまさにそう。suExec なサーバなので、CGI ファイルのパーミッションをまとめて 700 に変更したときに .htaccess が混入していたようだ。いつもならエラーログを見てすぐに気づくところだが、このサーバはエラーログをユーザに提供していないのでわからなかった。access_log は見られるんだけどね。
- .htaccess に読み込み権が無いとき error_log にどう出力されるのか
Apache をインストールした私個人のマシンで上記環境を再現し、エラーログにどんなメッセージが出力されるのかを確認してみた。error_log には以下の行が出力されていた。[Thu Oct 20 21:26:39 2004] [crit] [client 192.168.1.106] (13)Permission denied: /home/hiroaki/public_html/.htaccess pcfg_openfile: unable to check htaccess file, ensure it is readable
.htaccess を読めないから読めるようにしてね、というそのままなエラーメッセージだ。
apache には 環境変数を表示する CGI printenv.cgi が付いてくるが、レンタルサーバなどでは残念ながら削除されてしまっていることが多い。printenv.cgi は環境変数を表示しつつ .htaccess や cgi の調整や動作確認を行いたいときに重宝するのでメモ。
以下を printenv.cgi などといったファイル名で保存し、適切なパーミッションを与えてブラウザからアクセス。
実行結果。HTTP_ACCEPT の行は長すぎるので、ペースト時に改行を入れた。
PHP が使えるんだったら <?php phpinfo(); ?> を見た方が早いかも。
以下を printenv.cgi などといったファイル名で保存し、適切なパーミッションを与えてブラウザからアクセス。
#!/usr/local/bin/perl
##
## printenv -- demo CGI program which just prints its environment
##
print "Content-type: text/plain\n\n";
foreach $var (sort(keys(%ENV))) {
$val = $ENV{$var};
$val =~ s|\n|\\n|g;
$val =~ s|"|\\"|g;
print "${var}=\"${val}\"\n";
}
実行結果。HTTP_ACCEPT の行は長すぎるので、ペースト時に改行を入れた。
DOCUMENT_ROOT=/usr/local/etc/httpd/htdocs
GATEWAY_INTERFACE=CGI/1.1
HTTP_ACCEPT=text/xml,application/xml,application/xhtml+xml,
text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
HTTP_ACCEPT_CHARSET=Shift_JIS,utf-8;q=0.7,*;q=0.7
HTTP_ACCEPT_ENCODING=gzip,deflate
HTTP_ACCEPT_LANGUAGE=ja,en-us;q=0.7,en;q=0.3
HTTP_CONNECTION=keep-alive
HTTP_HOST=www.example.com
HTTP_KEEP_ALIVE=300
HTTP_USER_AGENT=Mozilla/5.0 (Windows; U; Windows NT 5.0; rv:1.7.3) Gecko/20040913 Firefox/0.10
PATH=/bin:/usr/bin:/usr/ucb:/usr/bsd:/usr/local/bin
QUERY_STRING=
REMOTE_ADDR=192.168.0.1
REMOTE_PORT=6209
REQUEST_METHOD=GET
REQUEST_URI=/cgi-bin/printenv.cgi
SCRIPT_FILENAME=/usr/local/etc/httpd/cgi-bin/printenv.cgi
SCRIPT_NAME=/cgi-bin/i16/printenv.cgi
SERVER_ADDR=192.168.0.2
SERVER_ADMIN=webmaster@example.com
SERVER_NAME=www.example.com
SERVER_PORT=80
SERVER_PROTOCOL=HTTP/1.1
SERVER_SIGNATURE=
SERVER_SOFTWARE=Apache/1.3.31 (Unix)
USER=#-1
PHP が使えるんだったら <?php phpinfo(); ?> を見た方が早いかも。
先日 2004-09-08 の 「ErrorDocument を利用した mod_rewrite の代替」に続き、mod_rewrite を使えない環境でのパス変換の話。
モジュール一覧 - Apache HTTP サーバ
http://httpd.apache.org/docs-2.1/ja/mod/
役に立ちそうなのは、mod_actions かなあ。Apache CORE ではないけど。
mod_actions - Apache HTTP サーバ Action ディレクティブ
http://httpd.apache.org/docs-2.1/ja/mod/mod_actions.html#act ...
core - Apache HTTP サーバ ForceType ディレクティブ
http://httpd.apache.org/docs-2.1/ja/mod/core.html#forcetype
もちろん FilesMatch は Files と ~ でもいい。
これで正規表現にマッチした場合は text/html として取り扱われて read.cgi に処理が振り分けられる。
- ErrorDocument 法はエラーログに出力されてしまうことが悩みの種
ErrorDocument を使うことでリダイレクトというかパス変換は実現できたのだが、やはりエラーログにエラーが山ほど書かれるのは精神衛生上良くない。今まで「何かあったら access_log と error_log をすぐ確認すること。」と口を酸っぱくして教えてきたのに、肝心のログがノイズだらけでは原因追及もままならない。access_log には 200 OK のステータスコードで記録されてるのに、error_log には しっかりと 404 が記録されるという矛盾もあるしね。- そこで Action ですよ
ErrorDocument を使ったのは、とりあえず Google で調べたらすぐ見つかったから。でも、もっと良い方法があるかもしれない。Apache のマニュアルを読んでみよう。幸いなことに日本語訳されたマニュアルもあるし。モジュール一覧 - Apache HTTP サーバ
http://httpd.apache.org/docs-2.1/ja/mod/
役に立ちそうなのは、mod_actions かなあ。Apache CORE ではないけど。
mod_actions
メディアタイプやリクエストメソッドに応じて CGI スクリプトを実行する機能を提供
mod_actions - Apache HTTP サーバ Action ディレクティブ
http://httpd.apache.org/docs-2.1/ja/mod/mod_actions.html#act ...
このディレクティブは action-type がリクエストされたときに cgi-script が実行されるという動作を追加します。cgi-script は ScriptAlias や AddHandler によって CGI スクリプトに設定されたリソースへの URL-path です。 Action-type には handler か MIME コンテントタイプを指定できます。リクエストされたドキュメントの URL とファイルのパスは標準 CGI 環境変数 PATH_INFO と PATH_TRANSLATED を使って伝えられます。 特定のリクエストに対して使用されるハンドラへは、 REDIRECT_HANDLER 変数を使って渡せます。
- ForceType で MIME タイプを強制する
なるほど。.html がリクエストされたら特定のスクリプトに振り分けられるわけだ。ただ、私の場合はリクエストに拡張子がない。拡張子がない場合でも text/html として扱ってやれれば良いんだけど・・・。あ、この ForceType を組み合わせればいいかな。core - Apache HTTP サーバ ForceType ディレクティブ
http://httpd.apache.org/docs-2.1/ja/mod/core.html#forcetype
ForceType ディレクティブこれを使えば、あらゆるリクエストを任意の MIME タイプに固定できる。
説明: すべてのマッチするファイルが指定の MIME コンテントタイプで送られるようにする
構文: ForceType MIME-type|None
コンテキスト: ディレクトリ, .htaccess
上書き: FileInfo
ステータス: Core
モジュール: core
互換性: Apache 2.0 で core に移動
.htaccess や <Directory> セクション、 <Location> セクション、 <Files> セクションに 書かれた場合、このディレクティブはそこにあるすべてのファイルが MIME-type で指定されたコンテントタイプとして扱われるようにします。たとえば、 GIF ファイルばかりのディレクトリがあって、すべてのファイルを .gif で終わらせたくはないときに、以下のものを使用します:
- .htaccess サンプル
<FilesMatch "^[0-9]{5}$">
ForceType text/html
Action text/html /cgi-bin/read.cgi
</FilesMatch>
もちろん FilesMatch は Files と ~ でもいい。
これで正規表現にマッチした場合は text/html として取り扱われて read.cgi に処理が振り分けられる。
- mod_actions 様々
私が使っているサーバでは上記設定で問題なくパス変換できた。しかも ErrorDocument を使った方法と違って、エラーログに 404 が延々と記録されることもない。mod_actions が使えるならこれがいいな。http://example.com/00765 といったアドレスで CGI にアクセスさせたい。つまり、ドメインの後に5桁の文書番号を入力するだけで希望の文書を閲覧できる CGI を呼び出すようにしたい。CGI のパスは http://example.com/cgi-bin/read.cgi?id=00765 なのだが、ケータイ用の短縮アドレスなので URL は短くしたい。
core - Apache HTTP サーバ ErrorDocument ディレクティブ
http://httpd.apache.org/docs-2.1/ja/mod/core.html#errordocum ...
こうすると、http://example.com/00765 にアクセスされると /cgi-bin/read.cgi の中身を返すようになる。文書番号は環境変数 REDIRECT_URL や REQUEST_URI にセットされるので、リクエストされた文書番号も取得できる。正規表現を使っているので、Files と ~ を使うよりも FilesMatch を使う方が明示的でより良いかな。
一つは、アクセスがあるたび Apache の error_log にエラーとして記録されてしまうこと。これは避けようがないので、ログのサイズ肥大に注意。httpd.conf でログを書かないようにすることはできるが、そもそも httpd.conf を修正できるなら .htaccess に ErrorDocument を書くなんてことをせずに、素直に mod_rewrite を使った方が良い。
二つめは、明示的に HTTP ステータスコードを上書きしないと404でステータスコードがが返ってしまうこと。この欠点があるため、リダイレクト先は CGI でないと使いにくいだろう。CGI ならば自分で 200 OK などのステータスコードを生成できるし、環境変数 REDIRECT_URL や REQUEST_URI にセットされた値に応じた処理を実装しやすいからだ。
参考
htmlでアクセスしたい
http://tdiary-users.sourceforge.jp/cgi-bin/wiki.cgi?html%A4% ...
- mod_rewrite を使えない環境なので ErrorDocument を使ってみる
Apache の mod_rewrite が使えれば楽勝なのだが、今回の案件では使えない。仕方がないので、ErrorDocument を利用したリダイレクト法を使うことにする。リダイレクトというよりパス変換と呼ぶ方が正確かな。300系の HTTP ステータスを発行するワケじゃないしね。- ErrorDocument 法の利点
ErrorDocument 法の利点は、利用できる可能性が非常に高い点にある。ErrorDocument は Apache Core に入っているディレクティブであり、かつ多くのコンテキストで指定できる。.htaccess で指定できるのは使い勝手が良い。.htaccess はパフォーマンスを落とすって? 速いマシンを使えばいい。core - Apache HTTP サーバ ErrorDocument ディレクティブ
http://httpd.apache.org/docs-2.1/ja/mod/core.html#errordocum ...
ErrorDocument ディレクティブ
説明: エラーが発生したときにサーバがクライアントに送るもの
構文: ErrorDocument error-code document
コンテキスト: サーバ設定ファイル, バーチャルホスト, ディレクトリ, .htaccess
上書き: FileInfo
ステータス: Core
モジュール: core
問題やエラーが発生したときの動作として、 Apache には以下の四つのうち一つの動作を設定することができます。
Apache 標準の簡単なエラーメッセージを表示
自分で指定したメッセージを表示
問題やエラーの処理をする為に、自サーバ内の URL-path へリダイレクト
問題やエラーの処理をする為に、外部の URL へリダイレクト
最初のものがデフォルトの動作で、2 番目から 4 番目は、 ErrorDocumentディレクティブにより、 HTTP のレスポンスコードと、メッセージか URL を指定することで設定します。 Apache が問題もしくはエラーに関する追加情報を提供することがあります。
- 設定方法
.htaccess に以下の記述を追加する。<Files ~ "^[0-9]{5}$">
ErrorDocument 404 /cgi-bin/read.cgi
</Files>
こうすると、http://example.com/00765 にアクセスされると /cgi-bin/read.cgi の中身を返すようになる。文書番号は環境変数 REDIRECT_URL や REQUEST_URI にセットされるので、リクエストされた文書番号も取得できる。正規表現を使っているので、Files と ~ を使うよりも FilesMatch を使う方が明示的でより良いかな。
- ErrorDocument を使ったリダイレクト (パス変換) の注意点
この方法を使う場合の注意点は2つ。一つは、アクセスがあるたび Apache の error_log にエラーとして記録されてしまうこと。これは避けようがないので、ログのサイズ肥大に注意。httpd.conf でログを書かないようにすることはできるが、そもそも httpd.conf を修正できるなら .htaccess に ErrorDocument を書くなんてことをせずに、素直に mod_rewrite を使った方が良い。
二つめは、明示的に HTTP ステータスコードを上書きしないと404でステータスコードがが返ってしまうこと。この欠点があるため、リダイレクト先は CGI でないと使いにくいだろう。CGI ならば自分で 200 OK などのステータスコードを生成できるし、環境変数 REDIRECT_URL や REQUEST_URI にセットされた値に応じた処理を実装しやすいからだ。
参考
htmlでアクセスしたい
http://tdiary-users.sourceforge.jp/cgi-bin/wiki.cgi?html%A4% ...
- 2004-09-13 追記
2004-09-13 に、「Action を利用した mod_rewrite の代替」という記事を書いた。- .htaccess で mod_rewrite
.htaccess に RewriteRule を設定する場合、第一引数はファイル名だけを記述する。ディレクトリを含んだ正規表現を第一引数に与えても動作しない。例) /home/hiroaki/public_html/log2 にあるファイルを /home/hiroaki/public_html/log の URL でアクセスさせる
RewriteEngine on
RewriteRule .* /~hiroaki/log/$1
今日の失敗は、「.htaccess に設定するときは、mod_rewrite の第一引数にはファイル名だけを記述する」というルールに気が付かずに10分無駄にしたこと。
- 参考
Apache URL Rewriting Guidehttp://japache.infoscience.co.jp/rewriteguide/
- 追記 mod_rewrite の代替
mod_rewrite を使えない場合に、なんとかして rewrite っぽい動作をさせる方法。もっとも、rewrite 先が CGI でないとダメだけど。2004-09-08 「ErrorDocument を利用した mod_rewrite の代替」
2004-09-13 「Action を利用した mod_rewrite の代替」
/usr/share/analog/images にある画像を表示させたいので、/home/tails/public_html/log/images からシンボリックリンクを張ったが表示されないという相談を受けた。ブラウザでアクセスしてみると、403 forbidden が返ってきているようだ。エラーログを確認すると、Symbolic link not allowed とあった。
私自身も /home/hiroaki/public_html/log/images から /usr/share/analog/images にシンボリックリンクを張っているが、問題なくアクセスできている。なぜ私の方は問題なく使えてるんだろう? httpd.conf に特別な記述をして私のシンボリックリンクを特別に許可しているか、逆に問題となる記述をこのユーザが加えてしまっているのではないかと考え、httpd.conf と .htaccess を確認したが、とくにめぼしいものはなかった。
うーん、何でだろう、ともう一度 シンボリックリンクを ls -al したところ、違いが見つかった。
私のシンボリックリンクはオーナーが root になっているが、問題のあるユーザのシンボリックリンクはオーナーがそのユーザ自身になっている。
場当たり的な対処だが、シンボリックリンクを chown して解決した。
Web サーバーとしてのチューニング
http://www.asi.co.jp/info/unix/webserver.html
セキュリティ的には SymLinksIfOwnerMatch を使うのが良いが、パフォーマンスの点で不利とのこと。でも、オーナーが root のシンボリックリンクって、なんか不安だなあ・・・。
[Fri Jan 9 19:23:25 2004] [error] [client 10.5.25.1] Symbolic link not allowed: /home/tails/public_html/log/images/
私自身も /home/hiroaki/public_html/log/images から /usr/share/analog/images にシンボリックリンクを張っているが、問題なくアクセスできている。なぜ私の方は問題なく使えてるんだろう? httpd.conf に特別な記述をして私のシンボリックリンクを特別に許可しているか、逆に問題となる記述をこのユーザが加えてしまっているのではないかと考え、httpd.conf と .htaccess を確認したが、とくにめぼしいものはなかった。
うーん、何でだろう、ともう一度 シンボリックリンクを ls -al したところ、違いが見つかった。
私のシンボリックリンクはオーナーが root になっているが、問題のあるユーザのシンボリックリンクはオーナーがそのユーザ自身になっている。
[hiroaki@pro images]$ ls -al /home/hiroaki/public_html/log/images
lrwxrwxrwx 1 root root 24 Apr 19 2003 /home/hiroaki/public_html/log/images -> /usr/share/analog/images/
[hiroaki@pro images]$ ls -al /home/tails/public_html/log/images
lrwxrwxrwx 1 tails tails 24 Jan 9 16:38 /home/tails/public_html/log/images -> /usr/share/analog/images/
場当たり的な対処だが、シンボリックリンクを chown して解決した。
[root@pro /root]# chown root /home/tails/public_html/log/images
- パフォーマンスやセキュリティ的にシンボリックリンクってどうなんだろう?
Google で apache シンボリックリンク SymLinksIfOwnerMatch を検索したところ、山ほどヒット。さすが シェアトップだけのことはある。Web サーバーとしてのチューニング
http://www.asi.co.jp/info/unix/webserver.html
2.4. Option の FollowSymLinks と SymLinksIfOwnerMatch
シンボリックリンク先の参照を許可する FollowSymLinks は、シンボリック先を参照するのは安全性に問題があるので利用しない方が良いと考えます。しかし、ファイルやディレクトリーがシンボリックされているかどうかチェックするためにそれぞれのファイルやディレクトリーに対して lstat 関数を実行します。さらに、lstat の結果はキャッシュされないのでリクエストのたびに発生します。これは、パフォーマンスを考えると非常に問題になります。また、シンボリックリンクファイルの持ち主とリンク先ファイル/ディレクトリの持ち主が同じだった場合にリンク先を参照することを許可する SymLinksIfOwnerMatch は、設定されるとパフォーマンスに影響します。でも、どうしてもセキュリティーチェックが必要なら以下のような設定をお勧めします。
DocumentRoot /home/httpd/html
<Directory />
Options FollowSymLinks
</Directory>
<Directory /home/httpd/html>
Options -FollowSymLinks +SymLinksIfOwnerMatch
</Directory>
このように設定することで、少なくとも DocumentRoot までの余分なパスのチェックが要らなくなります。Alias や RewriteRule で設定したパスに対しても同様な処理をする必要があります。最高のパフォーマンスを引き出すには、全ての箇所に FollowSymLinks を設定し、SymLinksIfOwnerMatch を絶対に設定しないことです。
セキュリティ的には SymLinksIfOwnerMatch を使うのが良いが、パフォーマンスの点で不利とのこと。でも、オーナーが root のシンボリックリンクって、なんか不安だなあ・・・。
$ /usr/local/apache/bin/httpd -l
[hiroaki@pro ~]$ /usr/local/apache/bin/httpd -l
Compiled-in modules:
http_core.c
mod_env.c
mod_log_config.c
mod_mime.c
mod_negotiation.c
mod_status.c
mod_include.c
mod_autoindex.c
mod_dir.c
mod_cgi.c
mod_asis.c
mod_imap.c
mod_actions.c
mod_userdir.c
mod_alias.c
mod_access.c
mod_auth.c
mod_so.c
mod_setenvif.c
suexec: disabled; invalid wrapper /usr/local/apache/bin/suexec
ab は複数ファイルを POST するよう設定するインターフェイスを持っていない。
ただ、Request Header の Content-Type の指定と、Content-Body をファイルから読み込ませて POST させることはできる。ということは、 Content-Type: を multipart/form-data にして、別のアプリケーションなどであらかじめ生成しておいた body を POST させればいいんじゃないかと思う。
http://www.atmarkit.co.jp/flinux/rensai/apache15/apache15b.h ...
ただ、Request Header の Content-Type の指定と、Content-Body をファイルから読み込ませて POST させることはできる。ということは、 Content-Type: を multipart/form-data にして、別のアプリケーションなどであらかじめ生成しておいた body を POST させればいいんじゃないかと思う。
- 例
/usr/local/apache/bin/ab -n 5 -c 10 http://10.3.31.90/cgi-bin/printstdin.pl -p mutipart_form_data.txt -T "Content-Type: form/multipart"
- 参考
連載:ApacheによるWebサーバ構築 Apacheパフォーマンス・チューニングのポイント(2/2)http://www.atmarkit.co.jp/flinux/rensai/apache15/apache15b.h ...
mod_auth_cookie、mod_ticket のどちらも apache のモジュール。apache レベルで 特定のトークンを持っているかどうかをチェックし、アクセスの可否を決定できるようにする。
ライブラリ側にあるとうれしい機能だな。以前作ったサイトは、これで置き換えられるかもしれない。もっとも、あのときは cookie を使わないようにするという仕様と、学習のためにあえてすべてをアプリ側で実装するようにしたんだった。
ライブラリ側にあるとうれしい機能だな。以前作ったサイトは、これで置き換えられるかもしれない。もっとも、あのときは cookie を使わないようにするという仕様と、学習のためにあえてすべてをアプリ側で実装するようにしたんだった。
httpd.conf の <IfModule mod_alias.c> に以下の行を追加する。
.htaccess に以下の記述を追加。
ScriptAliasMatch /~(..*)/cgi-bin/(.*) /home/$1/public_html/cgi-bin/$2
- httpd.conf に以下の行を追加する。sonic ではこの方法を採用。
<Directory /home/*/public_html/cgi-bin>
AddHandler cgi-script .cgi
AllowOverride FileInfo AuthConfig Limit
Options ExecCGI
</Directory>
- httpd.conf の <Directory /home/*/public_html> で
AllowOverride に Options を追加し、.htaccess で ExecCGI できるようにする。.htaccess に以下の記述を追加。
<Limit GET POST>
AddHandler cgi-script .cgi
Options ExecCGI
</Limit>
ScriptAlias を指定したディレクトリのサブディレクトリにcgi ファイルを置くという方法は問題ないのかについて調査した。
結論。問題ないようだ。環境は apache 1.3.26。
google で探してみたが、この件についての公式なドキュメントは見つけることができなかった。先輩に質問したところ、「親ディレクトリの設定を引き継ぐ」ので、cgi として実行されるとのこと。
[Newbie 2759] Re: CGIエラー
http://mm.apache.or.jp/pipermail/newbie/2002-July/002752.htm ...
においても、動いているとの報告があった。PATH_INFO も正常にセットされる。
結論。問題ないようだ。環境は apache 1.3.26。
google で探してみたが、この件についての公式なドキュメントは見つけることができなかった。先輩に質問したところ、「親ディレクトリの設定を引き継ぐ」ので、cgi として実行されるとのこと。
[Newbie 2759] Re: CGIエラー
http://mm.apache.or.jp/pipermail/newbie/2002-July/002752.htm ...
においても、動いているとの報告があった。PATH_INFO も正常にセットされる。
PHP 4.2.1 で正規表現を使ったとき、以下のエラーを出して apache が落ちる。
正規表現で全角スペースを含む文字列を trim する際にこの問題が出た。
結局、長くなるおそれのある文字列は標準の trim() のみ行う、ということにした。
正規表現で置換するとき、検索対象となる文字列があまりに長い場合にこの現象が起きるようだ。以下、問題を再現できるコード。長いけど。
$ tail -f /var/log/httpd/error_log
[Tue Dec 3 20:58:50 2002] [notice] child pid 6053 exit signal Segmentation fault (11)
正規表現で全角スペースを含む文字列を trim する際にこの問題が出た。
結局、長くなるおそれのある文字列は標準の trim() のみ行う、ということにした。
正規表現で置換するとき、検索対象となる文字列があまりに長い場合にこの現象が起きるようだ。以下、問題を再現できるコード。長いけど。
<?php
$base_str = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">';
$Zspace = '(?:\xA1\xA1)'; // 全角スペース
$ascii = '[\x00-\x7F]'; # 1バイト EUC-JP文字
$twoBytes = '(?:[\x8E\xA1-\xFE][\xA1-\xFE])'; # 2バイト EUC-JP文字
$threeBytes = '(?:\x8F[\xA1-\xFE][\xA1-\xFE])'; # 3バイト EUC-JP文字
$character = "(?:$ascii|$twoBytes|$threeBytes)"; # EUC-JP文字
$count = 1000;
$str = '';
for ($i = 0; $i < $count; $i++) {
$str .= $base_str;
}
// $str が EUC-JP の場合
$str = preg_replace("/^($character*?)(?:\s|$Zspace)+$/", "$1", $str);
print htmlspecialchars($str);
?>