當 PHP 需執行耗時任務(如發郵件、生成報表、視頻轉碼)時,不希望用戶等待頁面加載完成。本文詳解利用 HTTP 協議特性,讓瀏覽器「以為請求結束」實際 PHP 繼續後台執行的技術原理與代碼模式。
ob_get_length() 獲取已輸出長度,ob_flush() + flush() 強制發送到瀏覽器。<?php
// 1. 輸出給用戶的即時反饋
echo '任務已接收,正在後台處理...';
// 2. 斷開連接關鍵代碼
$size = ob_get_length();
header("Content-Length: $size");
header("Connection: Close");
ob_flush();
flush();
// 3. 可選:fastcgi_finish_request() (PHP-FPM 環境下更可靠)
if (function_exists('fastcgi_finish_request')) {
fastcgi_finish_request();
}
// 4. 後台長時間任務
error_log("[" . date("Y-m-d H:i:s") . "] 背景任務開始");
sleep(5); // 模擬耗時操作
error_log("[" . date("Y-m-d H:i:s") . "] 背景任務結束");
// 這裡的 echo 瀏覽器不會收到
?>
| 方法 | 適用環境 | 可靠性 | 備註 |
|---|---|---|---|
| Content-Length + Connection: Close | 所有 SAPI (Apache mod_php, Nginx+PHP-FPM, CLI) | 中等 | 需確保無其他輸出緩衝層干擾 |
| fastcgi_finish_request() | PHP-FPM | 高 | 官方推薦,直接關閉 FastCGI 連接 |
| ignore_user_abort(true) + set_time_limit(0) | 所有 | 高 | 防止用戶關閉頁面導致腳本終止,配合上述使用 |
fastcgi_finish_request() 作為首選(PHP-FPM 環境)。ignore_user_abort(true) 確保任務不因客戶端斷開而中止。ob_start() 開啟緩衝,確保 ob_get_length() 準確。header() / setcookie() 之前,或開啟 output_buffering。mod_deflate/gzip 會改變實際傳輸長度,導致 Content-Length 不匹配 —— 關閉壓縮或使用 fastcgi_finish_request() 繞過。proxy_buffering / fastcgi_buffering 可能延遲發送 —— 設置 proxy_buffering off 或 fastcgi_buffering off。掌握 HTTP 協議層面的連接控制,是實現「即時響應 + 後台異步」用戶體驗的關鍵技能。