file_get_contents 預設無逾時控制,跨網段請求易導致腳本卡死。本文提供 stream context 與 cURL 兩種逾時解決方案,並整合為通用函式。
問題場景
- 跨機房/跨國請求 (中國↔美國) 延遲高、丟包率高
- 目標服務無回應導致 PHP 腳本長時間阻塞
- 預設 60 秒逾時不符合業務 SLA 需求
方案一:stream_context_create (file_get_contents 原生)
<?php
$url = 'https://slow.example.com/api';
$ctx = stream_context_create([
'http' => [
'timeout' => 10, // 總逾時秒數 (含連線+傳輸)
'method' => 'GET',
'header' => 'User-Agent: PHP Client\r\n',
'follow_location' => 1,
'max_redirects' => 5,
],
'ssl' => [
'verify_peer' => true,
'verify_peer_name' => true,
]
]);
$result = @file_get_contents($url, false, $ctx);
if ($result !== false) {
var_dump($result);
} else {
// 區分錯誤類型
$error = error_get_last();
echo "獲取失敗: " . ($error['message'] ?? '未知錯誤');
}
關鍵參數說明
| 參數 | 說明 | 單位 |
| timeout | 讀取/寫入/連線總逾時 | 秒 |
| method | HTTP 方法 | - |
| header | 自定標頭 (字串或陣列) | - |
| follow_location | 自動追蹤重定向 | 0/1 |
| max_redirects | 最大重定向次數 | 整數 |
| protocol_version | HTTP 版本 (1.0/1.1) | - |
方案二:cURL 精確逾時控制 (推薦生產環境)
function zhozGetContents($url, $second = 5) {
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_HEADER => 0,
CURLOPT_TIMEOUT => $second, // 總執行逾時 (秒)
CURLOPT_CONNECTTIMEOUT => min(5, $second), // 連線逾時 (秒)
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 5,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_USERAGENT => 'Mozilla/5.0 (compatible; PHP Client)',
]);
$content = curl_exec($ch);
$errno = curl_errno($ch);
$error = curl_error($ch);
$info = curl_getinfo($ch);
curl_close($ch);
if ($errno) {
// 區分逾時類型
if ($errno === CURLE_OPERATION_TIMEDOUT) {
throw new Exception("請求逾時 ({$second}s): $url");
} elseif ($errno === CURLE_COULDNT_CONNECT) {
throw new Exception("無法連線: $url");
}
throw new Exception("cURL 錯誤 [$errno]: $error");
}
$httpCode = $info['http_code'];
if ($httpCode >= 400) {
throw new Exception("HTTP 錯誤 $httpCode: $url");
}
return $content;
}
// 使用範例
try {
echo date('Y-m-d H:i:s') . "\n";
$buffer = zhozGetContents('https://zhoz.com/zhoz.php', 10);
echo date('Y-m-d H:i:s') . "\n";
if (empty($buffer)) {
echo "回應為空\n";
} else {
echo "獲取成功,長度: " . strlen($buffer) . "\n";
}
} catch (Exception $e) {
echo "錯誤: " . $e->getMessage() . "\n";
}
毫秒級精確控制 (PHP 5.2.3+ / cURL 7.16.2+)
curl_setopt_array($ch, [
CURLOPT_TIMEOUT_MS => 5000, // 總逾時 5000ms
CURLOPT_CONNECTTIMEOUT_MS => 2000, // 連線逾時 2000ms
]);
整合通用函式:環境自適應
function vitaGetUrlContent($url, $timeout = 10) {
// 策略 1: cURL 可用時優先使用 (功能最完整)
if (function_exists('curl_init')) {
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => $timeout,
CURLOPT_CONNECTTIMEOUT => min(5, $timeout),
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 5,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_USERAGENT => 'Mozilla/5.0 (compatible; PHP Client)',
]);
$content = curl_exec($ch);
if (curl_errno($ch) === 0) {
curl_close($ch);
return $content;
}
curl_close($ch);
}
// 策略 2: file_get_contents + stream context (允許 url fopen 時)
if (ini_get('allow_url_fopen')) {
$ctx = stream_context_create([
'http' => [
'timeout' => $timeout,
'follow_location' => 1,
'max_redirects' => 5,
],
'ssl' => ['verify_peer' => true, 'verify_peer_name' => true],
]);
$content = @file_get_contents($url, false, $ctx);
if ($content !== false) return $content;
}
// 策略 3: 最後嘗試 (無逾時控制,極慢環境慎用)
return @file_get_contents($url);
}
逾時錯誤碼速查表
| 常數 | 值 | 含義 |
| CURLE_OPERATION_TIMEDOUT | 28 | 操作逾時 (總逾時) |
| CURLE_COULDNT_CONNECT | 7 | 無法連線到主機 |
| CURLE_COULDNT_RESOLVE_HOST | 6 | DNS 解析失敗 |
| CURLE_OPERATION_TIMEOUTED | 28 | 舊版別名 (同 TIMEDOUT) |
| CURLE_SSL_CONNECT_ERROR | 35 | SSL 握手失敗 |
| CURLE_RECV_ERROR | 56 | 接收資料失敗 |
最佳實踐建議
- 生產環境強制使用 cURL:功能完整、錯誤可辨識、支援毫秒級控制
- 設定合理逾時:連線 3-5 秒、總計 10-30 秒,視業務 SLA 調整
- 區分逾時類型:連線逾時 vs 傳輸逾時 vs DNS 逾時,採取不同重試策略
- 記錄慢請求:超過閾值記錄日誌,定期分析優化上游服務
- 熔斷機制:連續失敗達標時暫停請求,避免雪崩效應