Chrome以外で真っ白になる

謎の現象に悩まされる

本番サーバでのみ、 Chrome以外のブラウザで真っ白、PHPエラーも検出されない という奇々怪々な現象を体験しました。 curl -i してみたところ、ヘッダデータも本文データもちゃんと全て取得できてる。
一体なぜなんだぜ・・。

FastCGIこんにゃろう!

丸一日調査して判明したのは、 FastCGIのバッファ挙動が特殊 と言うものでした。(厳格な仕様なのでしょうが、気づくのに非常に時間がかかりました)

CakePHP でウェブサイトを構築していたのですが、標準のビューレンダーをOffにし別のフロントPHPにシステムを組み込むという形にしていたため、 dispatch() で CakePHP側のコントロール処理が終わった後にも出力があるのですが、これがFastCGI側でばっさりカットされてしまっていたのでした。

具体的には、 dispatch() の処理の中で Content-length の出力があるのですが、まだ CakePHP の後にもこれから出力があるのにここまでのデータ長で出力してしまうので、この値を超えた以降の部分のデータがカットされてしまうケースがあるようでした。 "あるようでした" というのも、テスト環境ではどのブラウザもすべて表示できるのに本番サーバのみChrome以外で正常に見れない(レスポンス本文データがカットされている)のである。 どうも FastCGIのキャッシュの挙動と Content-Length がらみの問題であるとはわかったのですが、どう対処したらいいものか試行錯誤していました。

Content-Length 吐かなきゃOK

ちょっとアレですが、この方法が一番確実かもしれません。
CakePHPですと、 lib/Cake/Network/CakeResponse.php の 510行目付近の $shouldSetLengthfalse にしてしまえば Content-Length が出力されなくなり、FastCGIやブラウザ側も変にバッファしたりカットしたりしません。 ただ、コアファイルに手入れするのは忍びないので、app/Lib/Network/CakeResponse.php に修正したものを複製しておきましょう。CakePHPが優先的にこちらのファイルを実行してくれるようになります。

...
    protected function _setContentLength() {

        // $shouldSetLength = !isset($this->_headers['Content-Length']) && !in_array($this->_status, range(301, 307));
        $shouldSetLength = false;

        if (isset($this->_headers['Content-Length']) && $this->_headers['Content-Length'] === false) {
            unset($this->_headers['Content-Length']);
...

別法?

実は、本番サーバのサポート担当にも当現象を連絡していたのですが、以下のような設定を行うことで回避できるのでは、という回答をいただいておりました。 ただ、こちらは試してませんので、"これでもうまくいくかも" 程度です。

output_buffering = 0
Header add X-Accel-Buffering no

以上、ご参考まで。

参考にさせていただいたサイト