最近提交完程式碼後,發現Firephp在其他人的環境下又出問題了,提示:’Headers already sent in …’,與上一次Nginx 緩衝區超出不太一樣。查看Nginx錯誤日誌,並沒有發現錯誤,有同學發現Apache下面也會,懷疑是PHP的問題。但我用的也是Apache,不會有問題!偶然發現有一個頁面不會出現錯誤提示,發現該頁面輸出內容大小在1KB左右,懷疑是PHP的輸出緩衝區超出時,自動發送緩衝區數據,導致後續Firephp透過Http header發送調試信息失敗了並結束php腳本執行。
註解掉模板渲染後的輸出語句,不再提示該錯誤,確定是php的緩衝輸出有問題。比較不同環境下的php.ini,發現output_buffering的值不太一樣,我的值是On,而其他人則是預設值4096。
Php代碼
Output buffering is a mechanism for controlling how much output data (excluding headers and cookies) PHP should keep internally before pushing that data to the client. If your application's output exceeds this setting, PHP will send that data in chunks of roughly the size you specify. Turning on this setting and managing its maximum buffer size can yield some interesting side-effects depending on your application and web server. You may be able to send headers and cookies after you've already sent output through print or echo. You also may see performance benefits if your server is emitting less packets due to buffered output versus PHP streaming the output as it gets it. On production servers, 4096 bytes is a good setting for performance reasons. Note: Output buffering can also be controlled via Output Buffering Control functions. Possible Values: On = Enabled and buffer is unlimited. (Use with caution) Off = Disabled Integer = Enables the buffer and sets its maximum size in bytes. Note: This directive is hardcoded to Off for the CLI SAPI Default Value: Off Development Value: 4096 Production Value: 4096 http://php.net/output-buffering output_buffering = On
根據上面的解釋,當output_buffering為On或4096時,在每次請求裡面,當我們使用echo或print的時候,php實際上並不立即輸出而是先保存到緩衝區裡面去,當達到一定大小(如4KB)或腳本執行結束的時候,再向瀏覽器輸出緩衝區內容並清空。
Http協定傳輸內容時,先傳送回應頭,一旦內容開始輸出後,回應頭就不再可以改變。當output_buffering為On時,PHP會將所有的輸出快取起來,等待請求結束時在向瀏覽器輸出內容,故Firephp在最後時刻更改Http回應頭仍然不會有問題,因為此時仍輸出任何內容;當output_buffering為4096(或其他固定值)時,每次php緩衝區一滿便會向客戶端輸出,此時已輸出內容,回應頭不再可以改變,若嘗試設定header便會提示:'Headers already sent …'。
做好Webserver的緩衝輸出控制,能帶來更好的使用者體驗,如Facebook、新浪的Bigpipe。
本次的問題排查發現,由於大部分頁面的調試資訊較多,導致Http響應頭較大(遠大於4KB,有的達到36KB),所以觸發了自動輸出機制。解決方法:更改output_buffering為On;在程式碼裡面手動ob_start();更改output_buffering為更大值;減少日誌偵錯資訊。