2011年8月29日月曜日

phpの出力制御

header()の前には出力はできない(はず)

phpのマニュアルのheader()関数の説明に
<html>
<?php
/* これはエラーとなります。この上に出力があることに注目してください。
* それはheader()のコールより前であるということになります */
header('Location: http://www.example.com/');
?>
とあります。たしかにおぼろげに
Cannot modify header information - headers already sent by ...
こんなワーニングを見た記憶があります。
このワーニングを再現しようと思い上記のコードをテストサーバに配置しアクセスしてみたのですが、ワーニングも出ずにきちんとリダイレクトされてしまいました。なぜ!? 納得がいくまで調べていたら数日が経過してしまいました…

調査

サーバからのレスポンスをヘッダを含めて確認したかったので、いろいろ手段はありますが今回は Burp Suite を使いました。
レスポンスはこのようになっていました。
HTTP/1.1 302 Found
Date: Mon, 29 Aug 2011 **:**:** GMT
Server: Apache/2.2.16 (Ubuntu)
X-Powered-By: PHP/5.3.3-1ubuntu9.5
Location: http://www.example.com/
Vary: Accept-Encoding
Content-Length: 7
Content-Type: text/html

<html>
<html>を先に出力しているのに、きちんとheader()関数が有効で、Location:ヘッダが出力されていました。これは明らかに、

出力バッファリング

ですよね。でも、出力制御関数は一切使っていないのに…となると、答えはもう、php.iniしかありません。
テストした環境(ubuntu server 10.10, php 5.3.3)のphp.iniを見てみると…
output_buffering = 4096
見つけました。この設定があるおかげで、header()の前に出力があってもheader()が有効でした。ためしに、
<html>
<?php
echo str_repeat(" ", 4096);
header('Location: http://www.example.com/');
?>
このように余計な文字列を出力し Content-Length: が4096以上になると、その時点でバッファに溜め込んだ出力がヘッダとともに出力されてしまうので、次の header("Location:"... でワーニングが発生し、リダイレクトもされません。

以上をふまえて注意

header()を使うときは
  • header()とコンテンツの出力の順序に気をつける。これはMVCの分離ができていれば容易にできるはずです。
もしくは
  • output_bufferingの設定を明示的に行う。php.ini以外に.htaccessやhttpd.confでも可能です。
header()がまともに動かなかったり動いたり、出力制御が結果的にややこしくしている原因かもしれません。しかし、phpの出力制御の特性を理解していればトラブルもすぐ解決できることでしょう。

0 件のコメント:

コメントを投稿