file_get_contents() 讀取遠端 URL 時出現 Warning: file_get_contents(...): failed to open stream: HTTP request failed! 是常見問題。本文系統梳理可能原因,並提供分級解決方案。
Warning: file_get_contents(https://example.com/file.rar):
failed to open stream: HTTP request failed!
in /path/to/script.php on line 10
表示 HTTP 請求過程中發生錯誤,導致無法取得回應內容。後續通常伴隨 404 Not Found、403 Forbidden、500 Internal Server Error、Connection timeout 等具體 HTTP 狀態碼。
| 類別 | 具體原因 | 檢查方法 |
|---|---|---|
| 伺服器端 | 檔案不存在 (404) | 瀏覽器直接開啟 URL 確認 |
| 權限不足 (403) / 需登入 (401) | 檢查 Referer、User-Agent、Cookie、Token | |
| 伺服器錯誤 (5xx) / 限流 (429) | 稍後重試、檢查 API 文件限制 | |
| 網路/連線 | DNS 解析失敗 | dig example.com / nslookup |
| 連線逾時 | 預設 60 秒,大檔/慢線路易觸發 | |
| SSL/TLS 憑證驗證失敗 | 自簽憑證、憑證過期、主機名不符 | |
| 防火牆/代理阻擋 | 伺服器出口 IP 被封、需走 Proxy | |
| PHP 設定 | allow_url_fopen = Off | phpinfo() 或 ini_get('allow_url_fopen') |
default_socket_timeout 過短 | 預設 60 秒,大檔需調大 | |
| OpenSSL 擴充套件未啟用 | HTTPS 請求需 extension=openssl | |
| 請求參數 | User-Agent 被攔截、缺少必要 Header、Redirect 循環 | 模擬瀏覽器 Header、限制重導向次數 |
<?php
$url = 'https://example.com/large.rar';
$context = stream_context_create([
'http' => [
'method' => 'GET',
'timeout' => 300, // 5 分鐘逾時
'header' => implode("\r\n", [
'User-Agent: Mozilla/5.0 (compatible; PHP Bot)',
'Accept: */*',
'Accept-Encoding: gzip, deflate', // 支援壓縮
]),
'follow_location' => 1, // 支援重導向
'max_redirects' => 10,
'ignore_errors' => true, // 即使 4xx/5xx 也取得內容
],
'ssl' => [
'verify_peer' => true,
'verify_peer_name' => true,
// 若對方自簽憑證且信任,可暫時關閉驗證(生產環境慎用)
// 'verify_peer' => false,
// 'verify_peer_name' => false,
],
]);
$content = @file_get_contents($url, false, $context);
if ($content === false) {
$error = error_get_last();
throw new Exception("下載失敗: {$error['message']}");
}
// 取得 HTTP 狀態碼
$meta = stream_get_meta_data($handle ?? fopen('php://memory', 'r'));
// 實際需從 $http_response_header 取得
$statusLine = $http_response_header[0] ?? 'HTTP/1.1 000';
preg_match('/\d{3}/', $statusLine, $matches);
$statusCode = (int)($matches[0] ?? 0);
if ($statusCode >= 400) {
throw new Exception("HTTP 錯誤: $statusCode");
}
file_put_contents('downloaded.rar', $content);
?>
<?php
function downloadWithCurl(string $url, string $savePath, array $options = []): bool {
$ch = curl_init();
$defaultOptions = [
CURLOPT_URL => $url,
CURLOPT_FILE => fopen($savePath, 'w'), // 直接寫入檔案,不佔記憶體
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 300,
CURLOPT_CONNECTTIMEOUT => 30,
CURLOPT_USERAGENT => 'Mozilla/5.0 (compatible; PHP Bot)',
CURLOPT_ENCODING => 'gzip, deflate', // 自動解壓
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_FAILONERROR => false, // 即使 4xx 也寫入內容
];
curl_setopt_array($ch, array_merge($defaultOptions, $options));
$result = curl_exec($ch);
$errno = curl_errno($ch);
$error = curl_error($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
fclose(curl_getinfo($ch, CURLINFO_FILE)); // 關閉檔案指標
curl_close($ch);
if ($errno) {
@unlink($savePath); // 失敗刪除不完整檔案
throw new Exception("cURL 錯誤 ($errno): $error");
}
if ($httpCode >= 400) {
@unlink($savePath);
throw new Exception("HTTP $httpCode");
}
return true;
}
// 使用
try {
downloadWithCurl('https://example.com/file.rar', 'file.rar');
echo "下載完成\n";
} catch (Exception $e) {
echo "失敗: " . $e->getMessage() . "\n";
}
?>
<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
$client = new Client([
'timeout' => 300,
'verify' => true, // SSL 驗證
]);
try {
// 串流下載大檔案,不佔記憶體
$response = $client->request('GET', 'https://example.com/file.rar', [
'sink' => 'file.rar', // 直接存檔
'headers' => ['User-Agent' => 'MyApp/1.0'],
'on_stats' => function ($transferStats) {
// 進度條回呼
if ($transferStats->getTransferSize() > 0) {
$pct = round($transferStats->getBytesTransferred() / $transferStats->getTransferSize() * 100, 1);
echo "\r進度: $pct%";
}
},
]);
echo "\n下載完成,狀態碼: " . $response->getStatusCode() . "\n";
} catch (RequestException $e) {
echo "請求失敗: " . $e->getMessage() . "\n";
if ($e->hasResponse()) {
echo "狀態碼: " . $e->getResponse()->getStatusCode() . "\n";
}
}
?>
file_get_contents、cURL、Guzzle 皆支援二進位,無需特殊處理。Content-Encoding: gzip 但檔案實為 RAR(非 gzip),需停用自動解壓:cURL 設 CURLOPT_ENCODING => '',Guzzle 設 'decode_content' => false。Range 標頭,cURL/Guzzle 均可實作。<?php
// 診斷腳本:在伺服器上執行此檔案
$testUrls = [
'https://httpbin.org/get', // 正常 HTTPS
'https://httpbin.org/status/404', // 404
'https://httpbin.org/status/500', // 500
'https://expired.badssl.com/', // 憑證過期
'https://self-signed.badssl.com/', // 自簽憑證
];
foreach ($testUrls as $url) {
echo "測試: $url\n";
$ctx = stream_context_create(['http' => ['timeout' => 10, 'ignore_errors' => true]]);
$start = microtime(true);
$content = @file_get_contents($url, false, $ctx);
$elapsed = round((microtime(true) - $start) * 1000);
$status = $http_response_header[0] ?? '無回應';
echo " 狀態: $status\n";
echo " 耗時: {$elapsed}ms\n";
echo " 長度: " . ($content !== false ? strlen($content) : '失敗') . "\n";
echo " 錯誤: " . (error_get_last()['message'] ?? '無') . "\n";
echo "\n";
}
?>
allow_url_fopen=On、OpenSSL 啟用、default_socket_timeout 足夠大。stream_context_create() 設定 User-Agent、逾時、忽略錯誤、SSL 選項。CURLOPT_FILE、Guzzle sink),避免記憶體爆炸。遇到「HTTP request failed」不要只看錯誤訊息,善用 $http_response_header、cURL curl_getinfo()、Guzzle 例外物件取得完整 HTTP 狀態碼與回應標頭,才能精準定位問題根因。