| 特性 | file_get_contents | cURL (curl_get_contents) |
|---|---|---|
| 支援協定 | HTTP/HTTPS、FTP、本地檔案 | HTTP/HTTPS、FTP、SFTP、LDAP、SMTP 等 20+ 協定 |
| 超時控制 | 依賴 default_socket_timeout、stream_context | 原生 CURLOPT_TIMEOUT、CURLOPT_CONNECTTIMEOUT 毫秒級精準 |
| 錯誤處理 | 回傳 false,需配合 error_get_last() | curl_errno()/curl_error() 詳細錯誤碼與訊息 |
| HTTP 功能 | 基本 GET/POST、Header、Cookie | 完整 REST 動詞、多部分表單、斷點續傳、代理、SSL 驗證、HTTP/2 |
| 效能/穩定度 | 大檔案/慢回應易導致進程阻塞、CPU 100% | 非阻塞選項、多工處理 (curl_multi)、生產級穩定 |
| 本地檔案讀取 | 原生支援、記憶體映射優化 | 不支援 (需 fopen) |
max_execution_time 或 default_socket_timeout (預設 60s)<?php
// 方式 1: stream_context (推薦,精確控制讀取超時)
$ctx = stream_context_create([
'http' => [
'timeout' => 5, // 總超時 5 秒 (含連線+讀取)
'method' => 'GET',
'header' => "User-Agent: MyApp/1.0\r\nAccept: application/json\r\n",
],
'ssl' => [
'verify_peer' => true,
'verify_peer_name' => true,
],
]);
$content = @file_get_contents('https://api.example.com/data', false, $ctx);
if ($content === false) {
$err = error_get_last();
// 記錄 $err['message']
}
// 方式 2: ini_set (影響全域 socket)
ini_set('default_socket_timeout', 5);
// 方式 3: php.ini (永久生效)
; default_socket_timeout = 5
<?php
/**
* 通用 cURL GET 請求
* @param string $url
* @param int $timeout 總超時秒數
* @param int $connectTimeout 連線超時秒數
* @param array $headers 額外 Header ['Key: Value', ...]
* @return array ['code'=>HTTP狀態碼, 'body'=>回應內容, 'error'=>錯誤訊息]
*/
function curl_get($url, $timeout = 10, $connectTimeout = 5, $headers = []) {
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => $timeout,
CURLOPT_CONNECTTIMEOUT => $connectTimeout,
CURLOPT_HTTPHEADER => array_merge([
'User-Agent: MyApp/1.0 (+https://example.com/bot)',
'Accept: application/json, text/html',
], $headers),
CURLOPT_SSL_VERIFYPEER => true, // 生產環境必須驗證
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_FOLLOWLOCATION => true, // 自動跟隨 3xx 重導
CURLOPT_MAXREDIRS => 5,
CURLOPT_ENCODING => 'gzip, deflate', // 自動解壓
CURLOPT_IPRESOLVE => CURL_IPRESOLVE_V4, // 或 V6 / ANY
]);
$body = curl_exec($ch);
$errno = curl_errno($ch);
$error = $errno ? curl_error($ch) : '';
$code = $errno ? 0 : (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return compact('code', 'body', 'error');
}
// 使用範例
$res = curl_get('https://httpbin.org/get', 5, 2);
if ($res['code'] === 200) {
$data = json_decode($res['body'], true);
// 處理資料...
} else {
error_log("API請求失敗: HTTP {$res['code']} - {$res['error']}");
}
?>
curl_multi (批量 API 呼叫)<?php
function curl_multi_get(array $urls, $timeout = 10) {
$mh = curl_multi_init();
$handles = [];
foreach ($urls as $i => $url) {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => $timeout,
CURLOPT_SSL_VERIFYPEER => true,
]);
curl_multi_add_handle($mh, $ch);
$handles[$i] = $ch;
}
$running = null;
do {
curl_multi_exec($mh, $running);
curl_multi_select($mh, 0.1); // 避免 CPU 空轉
} while ($running > 0);
$results = [];
foreach ($handles as $i => $ch) {
$results[$i] = [
'code' => curl_getinfo($ch, CURLINFO_HTTP_CODE),
'body' => curl_multi_getcontent($ch),
'error' => curl_error($ch),
];
curl_multi_remove_handle($mh, $ch);
curl_close($ch);
}
curl_multi_close($mh);
return $results;
}
// 同時請求 5 個 API,總耗時 ≈ 最慢的那一個
$urls = [
'https://api1.example.com',
'https://api2.example.com',
// ...
];
$responses = curl_multi_get($urls, 5);
?>
strace 找出 file_get_contents 卡死進程top 找出 CPU 100% 的 php-cgi PIDstrace -p 觀察系統呼叫select(7, [6], [6], [], {15, 0}) = 1 (out [6], left {15, 0})
poll([{fd=6, events=POLLIN}], 1, 0) = 0 (Timeout)file_get_contents 等待網路回應導致阻塞
需要讀取本地檔案? ──Yes──→ file_get_contents (原生最優)
│
No
│
需要 POST/PUT/DELETE/PATCH? ──Yes──→ cURL
│
No
│
簡單 GET、可容忍偶發阻塞? ──Yes──→ file_get_contents + stream_context 超時
│
No
│
生產環境 / 高併發 / 需精準錯誤處理 / HTTPS 雙向認證 / 代理 / HTTP/2 ──→ cURL (唯一正解)
新專案**強烈建議直接使用 PSR-18 相容的 HTTP Client** (Guzzle 7+, Symfony HttpClient, HTTPlug),優點:
- 介面標準化,易於測試與替換底層驅動
- 內建重試、中介軟、非同步 (Promise)、日誌整合
- 型別提示完整,IDE 友善
// Guzzle 7 範例
use GuzzleHttp\Client;
$client = new Client(['timeout' => 5, 'verify' => true]);
$response = $client->get('https://api.example.com/data');
$data = json_decode($response->getBody()->getContents(), true);