使用 file_get_contents 抓取遠端 API 時,若對方回應緩慢或斷線,PHP 會拋出 Fatal Error: Maximum execution time exceeded 或長時間阻塞導致後續程式無法執行。特別是跨國請求(如中國大陸訪問海外伺服器),stream_context_create(['http'=>['timeout'=>10]]) 往往失效。
<?php
$url = 'https://api.example.com/data';
$ctx = stream_context_create([
'http' => [
'timeout' => 10, // 總超時(秒)
'method' => 'GET',
'header' => "User-Agent: PHP Bot\r\n",
]
]);
$result = @file_get_contents($url, 0, $ctx);
if ($result !== false) {
var_dump($result);
} else {
echo "請求失敗或超時";
}
?>
實測結論: 同機房/本機測試有效;跨國/高延遲環境下 timeout 參數常被忽略,請求仍會阻塞至 PHP max_execution_time。
<?php
function zhoz_get_contents($url, $connectTimeout = 5, $totalTimeout = 10) {
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => false,
CURLOPT_CONNECTTIMEOUT => $connectTimeout, // 連線建立超時
CURLOPT_TIMEOUT => $totalTimeout, // 整體執行超時
CURLOPT_USERAGENT => 'Mozilla/5.0 (compatible; PHP Bot)',
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 3,
CURLOPT_SSL_VERIFYPEER => false, // 僅測試
]);
$content = curl_exec($ch);
$errno = curl_errno($ch);
$error = curl_error($ch);
curl_close($ch);
if ($errno) {
// 可拋出異常或記錄日誌
error_log("cURL [$errno] $error for $url");
return false;
}
return $content;
}
// 使用範例
try {
echo date('Y-m-d H:i:s') . "\n";
$buffer = zhoz_get_contents('https://slow-api.example.com/data');
echo date('Y-m-d H:i:s') . "\n";
if (empty($buffer)) {
echo "回應為空\n";
} else {
echo "成功取得資料 (" . strlen($buffer) . " bytes)\n";
}
} catch (Exception $e) {
echo "異常: " . $e->getMessage() . "\n";
}
?>
<?php
function vita_get_url_content($url) {
// 1. 優先 cURL(功能完整、超時精確、支援 HTTPS/Proxy)
if (function_exists('curl_init')) {
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_TIMEOUT => 10,
CURLOPT_USERAGENT => 'Mozilla/5.0 (compatible; PHP Bot)',
CURLOPT_SSL_VERIFYPEER => false,
]);
$content = curl_exec($ch);
curl_close($ch);
return $content;
}
// 2. 次之 file_get_contents + Context(需 allow_url_fopen=On)
if (function_exists('file_get_contents') && ini_get('allow_url_fopen')) {
$ctx = stream_context_create(['http' => ['timeout' => 10]]);
return @file_get_contents($url, 0, $ctx);
}
// 3. 理論上不會到這裡,保留 fsockopen 兜底
trigger_error('No HTTP client available (cURL/fopen)', E_USER_WARNING);
return false;
}
?>
php-curl 並啟用 extension=curlCURLOPT_CONNECTTIMEOUT(連線階段)與 CURLOPT_TIMEOUT(總執行)分離CURLOPT_SSL_VERIFYPEER 並配置 CURLOPT_CAINFO 指向 cacert.pemUser-Agent,避免被防火牆攔截curl_errno()/curl_error() 進行錯誤處理,避免靜默失敗curl_multi 非同步批次請求<?php
function multiRequest($urls, $options = []) {
$handles = [];
$mh = curl_multi_init();
foreach ($urls as $i => $url) {
$ch = curl_init($url);
curl_setopt_array($ch, array_merge([
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_TIMEOUT => 10,
], $options));
curl_multi_add_handle($mh, $ch);
$handles[$i] = $ch;
}
$running = null;
do {
curl_multi_exec($mh, $running);
curl_multi_select($mh);
} while ($running > 0);
$results = [];
foreach ($handles as $i => $ch) {
$results[$i] = [
'content' => curl_multi_getcontent($ch),
'http_code' => curl_getinfo($ch, CURLINFO_HTTP_CODE),
'error' => curl_error($ch),
];
curl_multi_remove_handle($mh, $ch);
curl_close($ch);
}
curl_multi_close($mh);
return $results;
}
?>