| 问题 | file_get_contents | cURL 方案 |
|---|---|---|
| 超时控制 | 仅支持 stream_context 全局秒级 | 连接/读取分离毫秒级 CURLOPT_CONNECTTIMEOUT_MS |
| 重定向跟随 | 不跟随 301/302 | CURLOPT_FOLLOWLOCATION 可限制层数 |
| SSL 验证 | 默认不验证,不安全 | 强制验证 CURLOPT_SSL_VERIFYPEER |
| 错误诊断 | 仅返回 false | curl_errno()/curl_error() 精确码 |
| 性能 | 阻塞流 | 支持多句柄并发 curl_multi |
| 协议支持 | HTTP/HTTPS/FTP | HTTP/HTTPS/FTP/SMTP/IMAP/... |
<?php
/**
* cURL 版 file_get_contents
* @param string $url
* @param array $options 额外 curl_setopt 数组
* @return string|false 失败返回 false
*/
function curl_file_get_contents(string $url, array $options = []): string|false
{
$ch = curl_init($url);
// 默认安全配置
$defaults = [
CURLOPT_RETURNTRANSFER => true, // 返回字符串而非输出
CURLOPT_FOLLOWLOCATION => true, // 跟随重定向
CURLOPT_MAXREDIRS => 5, // 最大重定向次数
CURLOPT_CONNECTTIMEOUT_MS => 3000, // 连接超时 3 秒
CURLOPT_TIMEOUT_MS => 10000, // 总超时 10 秒
CURLOPT_SSL_VERIFYPEER => true, // 验证 SSL 证书
CURLOPT_SSL_VERIFYHOST => 2, // 验证域名匹配
CURLOPT_USERAGENT => 'PHP-cURL/' . PHP_VERSION,
CURLOPT_ENCODING => 'gzip, deflate', // 自动解压
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
];
curl_setopt_array($ch, $defaults);
if ($options) curl_setopt_array($ch, $options); // 允许覆盖
$content = curl_exec($ch);
$errno = curl_errno($ch);
$error = curl_error($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($errno) {
// 可选:触发错误日志
error_log("cURL [$errno] $error for $url");
return false;
}
if ($httpCode >= 400) {
error_log("HTTP $httpCode for $url");
return false;
}
return $content;
}
function curl_request(
string $url,
string $method = 'GET',
array $data = [],
array $headers = [],
array $options = []
): string|false {
$ch = curl_init($url);
$defaults = [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 5,
CURLOPT_CONNECTTIMEOUT_MS => 3000,
CURLOPT_TIMEOUT_MS => 15000,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_USERAGENT => 'PHP-cURL/' . PHP_VERSION,
CURLOPT_ENCODING => '',
];
// 方法处理
if (strtoupper($method) === 'POST') {
$defaults[CURLOPT_POST] = true;
if (isset($options[CURLOPT_POSTFIELDS])) {
// 已手动设置
} elseif (isset($headers['Content-Type']) && strpos($headers['Content-Type'], 'json') !== false) {
$defaults[CURLOPT_POSTFIELDS] = json_encode($data, JSON_UNESCAPED_UNICODE);
} else {
$defaults[CURLOPT_POSTFIELDS] = http_build_query($data);
}
} elseif ($data) {
// GET 附加查询串
$url .= (strpos($url, '?') ? '&' : '?') . http_build_query($data);
curl_setopt($ch, CURLOPT_URL, $url);
}
// Header 处理
if ($headers) {
$headerArr = [];
foreach ($headers as $k => $v) {
$headerArr[] = "$k: $v";
}
$defaults[CURLOPT_HTTPHEADER] = $headerArr;
}
curl_setopt_array($ch, $defaults);
if ($options) curl_setopt_array($ch, $options);
$content = curl_exec($ch);
$errno = curl_errno($ch);
curl_close($ch);
if ($errno) return false;
return $content;
}
// 简单 GET
$html = curl_file_get_contents('https://api.example.com/users/1');
// 带 Header GET
$html = curl_request('https://api.example.com/profile', 'GET', [], [
'Authorization' => 'Bearer ' . $token,
'Accept' => 'application/json',
]);
// POST JSON
$result = curl_request('https://api.example.com/orders', 'POST', [
'product_id' => 123,
'quantity' => 2,
], [
'Content-Type' => 'application/json',
'Authorization' => 'Bearer ' . $token,
]);
// 文件上传 (multipart)
$result = curl_request('https://api.example.com/upload', 'POST', [
'file' => new CURLFile('/path/to/image.jpg', 'image/jpeg', 'avatar.jpg'),
], [], [
CURLOPT_HTTPHEADER => ['Expect:'], // 取消 100-continue
]);
function curl_multi_request(array $urls, int $maxConcurrent = 10): array
{
$mh = curl_multi_init();
$handles = [];
$results = [];
// 初始化句柄
foreach ($urls as $i => $url) {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_TIMEOUT_MS => 10000,
CURLOPT_SSL_VERIFYPEER => true,
]);
curl_multi_add_handle($mh, $ch);
$handles[$i] = $ch;
}
$running = null;
do {
curl_multi_exec($mh, $running);
// 限制并发:简单实现,生产用更复杂的队列
while ($running > $maxConcurrent) {
usleep(10000);
curl_multi_exec($mh, $running);
}
curl_multi_select($mh, 0.1);
} while ($running);
// 收集结果
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_cleanup($mh);
return $results;
}
彻底弃用 file_get_contents 做 HTTP 请求。上述 curl_file_get_contents() 兼容原函数签名,仅内部替换为 cURL,即可获得:可控超时、重定向跟随、SSL 安全、错误诊断、并发能力。新项目直接使用 curl_request() 或引入 Guzzle 享受 PSR-18 标准客户端。