Warning: file_get_contents(URL): failed to open stream: HTTP request failed! HTTP/1.0 500 Read timed out
雖設定 set_time_limit(0),但請求仍在約 60 秒後超時。原因:PHP 內建 HTTP 封裝協議有獨立的預設超時 (default_socket_timeout = 60s),不受腳本執行時間限制。
所有支援 $context 參數的檔案系統函數 (file_get_contents, file_put_contents, readfile, fopen, copy 等) 均可透過統一機制配置超時。
$opts = [
'http' => [
'method' => 'GET', // 或 POST
'timeout' => 120.0, // 浮點數支援毫秒精度
'header' => "User-Agent: MyApp/1.0\r\nAccept: */*\r\n",
'ignore_errors' => true, // 重要:取得錯誤回應內容
],
'ssl' => [
'verify_peer' => true,
'verify_peer_name' => true,
],
];
$ctx = stream_context_create($opts);
// 統一應用於所有函數
$content = file_get_contents($url, false, $ctx);
readfile($url, false, $ctx); // 直接輸出
$fp = fopen($url, 'rb', false, $ctx); // 串流處理
copy($url, $dest, $ctx); // 直接複製
class HttpClient {
private $defaultOptions = [
'http' => [
'timeout' => 30.0,
'connect_timeout' => 10.0, // 需 PHP 7.1+
'ignore_errors' => true,
'follow_location' => 1,
'max_redirects' => 10,
'protocol_version' => 1.1,
],
'ssl' => [
'verify_peer' => true,
'verify_peer_name' => true,
],
];
public function request(string $method, string $url, array $data = [], array $headers = [], array $options = []): ?string {
$opts = $this->mergeOptions($this->defaultOptions, $options);
$opts['http']['method'] = strtoupper($method);
if ($data) {
$opts['http']['content'] = is_array($data) ? http_build_query($data) : $data;
if (!isset($opts['http']['header'])) $opts['http']['header'] = '';
$opts['http']['header'] .= "Content-Type: application/x-www-form-urlencoded\r\n";
}
if ($headers) {
$headerStr = '';
foreach ($headers as $k => $v) {
$headerStr .= "$k: $v\r\n";
}
$opts['http']['header'] = ($opts['http']['header'] ?? '') . $headerStr;
}
$ctx = stream_context_create($opts);
return @file_get_contents($url, false, $ctx);
}
public function get(string $url, array $headers = [], array $options = []): ?string {
return $this->request('GET', $url, [], $headers, $options);
}
public function post(string $url, array $data, array $headers = [], array $options = []): ?string {
return $this->request('POST', $url, $data, $headers, $options);
}
private function mergeOptions(array $base, array $override): array {
$result = $base;
foreach ($override as $wrapper => $params) {
if (!isset($result[$wrapper])) {
$result[$wrapper] = $params;
} elseif (is_array($params)) {
$result[$wrapper] = array_merge($result[$wrapper], $params);
}
}
return $result;
}
}
// 使用範例
$client = new HttpClient();
$response = $client->post('https://api.example.com/data', [
'name' => 'test',
'email' => 'test@gmail.com',
], [], ['http' => ['timeout' => 60]]);
function streamDownload(string $url, string $savePath, array $ctxOptions = []): bool {
$ctx = stream_context_create(array_merge([
'http' => [
'timeout' => 300, // 大檔案長超時
'ignore_errors' => true,
],
], $ctxOptions));
$src = @fopen($url, 'rb', false, $ctx);
if (!$src) return false;
$dest = @fopen($savePath, 'wb');
if (!$dest) {
fclose($src);
return false;
}
$written = stream_copy_to_stream($src, $dest);
fclose($src);
fclose($dest);
return $written > 0;
}
// 直接輸出到瀏覽器 (需配合 Header)
function streamOutput(string $url, string $filename, array $ctxOptions = []): void {
$ctx = stream_context_create([
'http' => ['timeout' => 60, 'ignore_errors' => true] + ($ctxOptions['http'] ?? [])
]);
$fp = @fopen($url, 'rb', false, $ctx);
if (!$fp) { http_response_code(404); exit; }
$meta = stream_get_meta_data($fp);
$headers = $meta['wrapper_data'] ?? [];
$contentType = 'application/octet-stream';
$contentLength = null;
foreach ($headers as $h) {
if (stripos($h, 'Content-Type:') === 0) $contentType = trim(substr($h, 13));
if (stripos($h, 'Content-Length:') === 0) $contentLength = trim(substr($h, 15));
}
header("Content-Type: $contentType");
header("Content-Disposition: attachment; filename=\"$filename\"");
if ($contentLength) header("Content-Length: $contentLength");
header('Cache-Control: no-cache, must-revalidate');
header('Pragma: no-cache');
fpassthru($fp);
fclose($fp);
}
$sslOptions = [
// 生產環境標準配置
'verify_peer' => true,
'verify_peer_name' => true,
'allow_self_signed' => false,
'cafile' => '/etc/ssl/certs/ca-certificates.crt', // 系統 CA 憑證包
'capath' => '/etc/ssl/certs/',
'ciphers' => 'HIGH:!aNULL:!MD5:!3DES', // 強加密套件
'min_proto_version' => TLS_VERSION_1_2, // PHP 7.4+ 禁用 TLS 1.0/1.1
];
// 開發環境臨時跳過驗證 (生產嚴禁)
$devSslOptions = [
'verify_peer' => false,
'verify_peer_name' => false,
];
| 參數 | 作用範圍 | 預設值 | 配置層級 |
|---|---|---|---|
| default_socket_timeout | 所有 socket 讀寫 | 60s | php.ini / ini_set() |
| stream_context 'timeout' | 特定請求總超時 | default_socket_timeout | 單次請求 |
| stream_context 'connect_timeout' | TCP 連線建立階段 | default_socket_timeout | 單次請求 (PHP 7.1+) |
| CURLOPT_TIMEOUT | cURL 總超時 | 0 (無限制) | cURL handle |
| CURLOPT_CONNECTTIMEOUT | cURL 連線階段 | 0 | cURL handle |
// 1. 捕獲警告資訊
$response = @file_get_contents($url, false, $ctx);
if ($response === false) {
$error = error_get_last();
// 記錄:$error['message'], $error['file'], $error['line']
}
// 2. 解析 HTTP 回應碼
$statusLine = $http_response_header[0] ?? '';
preg_match('/HTTP\/\d\.\d\s+(\d{3})/', $statusLine, $m);
$httpCode = $m[1] ?? 0;
// 3. 計時統計
$start = microtime(true);
$response = file_get_contents($url, false, $ctx);
$elapsed = microtime(true) - $start;
// 記錄:$elapsed, strlen($response), $httpCode
雖然 Stream Context 功能強大,但新專案建議採用 PSR-18 客戶端 (Guzzle, Symfony HttpClient, HTTPlug):
理解 PHP 內建 HTTP 封裝協議的超時機制,能快速解決遺留專案的網路可靠性問題,也是面試中「PHP 網程程式設計深度」的常考重點。