本文系统介绍 PHP cURL 扩展:基础概念、核心 API、常用选项、GET/POST/JSON/文件上传、并发(curl_multi)、错误与调试、性能与安全最佳实践,并提供可运行示例与命令。
目录
- 基础与启用检查
- 核心 API 流程
- 常用选项与场景
- GET/POST/JSON 示例
- 文件上传与下载
- 并发请求(curl_multi)
- 错误处理与调试
- 性能与安全最佳实践
- 完整示例与运行
- 并发下载示例与重试封装
- 使用
curl_multi并发下载到磁盘 - 简易重试封装(指数退避 + 可重试状态)
- 使用
- FAQ
基础与启用检查
- cURL 是客户端网络请求库,支持 HTTP(S)、FTP 等协议。
- PHP 通过
ext/curl扩展提供封装。检查是否启用:<?php echo function_exists('curl_init') ? "cURL enabled\n" : "cURL disabled\n"; phpinfo(); // 查看 curl 版本与特性 - 在 macOS/Linux 常用包管理安装或启用扩展;在 Docker/容器中使用镜像自带或安装。
核心 API 流程
- 基本流程:
curl_init()→curl_setopt()配置 →curl_exec()执行 →curl_close()释放。 - 返回与资源:
curl_exec()成功返回响应体(当CURLOPT_RETURNTRANSFER为 true),失败返回 false。 - 获取额外信息:
curl_getinfo($ch)返回状态码、时间、大小等指标。
示例:
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => 'https://httpbin.org/get',
CURLOPT_RETURNTRANSFER => true, // 返回字符串而非直接输出
CURLOPT_TIMEOUT => 10, // 总超时秒
]);
$body = curl_exec($ch);
if ($body === false) {
throw new RuntimeException('cURL error: '.curl_error($ch), curl_errno($ch));
}
$info = curl_getinfo($ch);
curl_close($ch);
常用选项与场景
- URL 与方法:
CURLOPT_URL,CURLOPT_CUSTOMREQUEST指定GET/POST/PUT/DELETE等。 - 请求体:
CURLOPT_POST与CURLOPT_POSTFIELDS(数组或字符串);数组会使用application/x-www-form-urlencoded或 multipart(含文件)。- 发送 JSON:设置
POSTFIELDS为json_encode($data);并加 Header。
- Header:
CURLOPT_HTTPHEADER设定 KV;示例:['Content-Type: application/json']。 - SSL:
CURLOPT_SSL_VERIFYPEER与CURLOPT_SSL_VERIFYHOST默认应开启校验。CURLOPT_CAINFO指定 CA 证书;必要时更新 CA 包。
- 重定向:
CURLOPT_FOLLOWLOCATION控制是否跟随;CURLOPT_MAXREDIRS限制次数。 - 代理:
CURLOPT_PROXY、CURLOPT_PROXYUSERPWD设置代理与认证。 - 超时:
CURLOPT_TIMEOUT(总超时)、CURLOPT_CONNECTTIMEOUT(连接)、CURLOPT_LOW_SPEED_TIME/LIMIT(低速阈值)。 - 压缩:
CURLOPT_ENCODING设置gzip,deflate自动解压。 - Keep-Alive:
Connection: keep-alive,并重用 handle;或使用 HTTP/2。
GET/POST/JSON 示例
// GET
$ch = curl_init('https://httpbin.org/get?foo=bar');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$body = curl_exec($ch);
curl_close($ch);
// POST 表单
$ch = curl_init('https://httpbin.org/post');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => [ 'name' => 'alice', 'age' => 20 ],
CURLOPT_RETURNTRANSFER => true,
]);
$body = curl_exec($ch);
curl_close($ch);
// POST JSON
$ch = curl_init('https://httpbin.org/post');
$payload = json_encode(['name' => 'alice', 'age' => 20], JSON_UNESCAPED_UNICODE);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Accept: application/json',
],
CURLOPT_RETURNTRANSFER => true,
]);
$body = curl_exec($ch);
curl_close($ch);
文件上传与下载
- 上传:通过
CURLOPT_POSTFIELDS使用CURLFile指定文件;或数组中使用new CURLFile($path)。 - 下载到文件:使用
CURLOPT_FILE/CURLOPT_WRITEFUNCTION将响应写入句柄。
示例:
// 上传文件
$ch = curl_init('https://httpbin.org/post');
$file = new CURLFile('/path/to/file.png', 'image/png', 'file.png');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => [ 'file' => $file, 'desc' => 'upload test' ],
CURLOPT_RETURNTRANSFER => true,
]);
$body = curl_exec($ch);
curl_close($ch);
// 下载到磁盘
$fp = fopen('out.bin', 'wb');
$ch = curl_init('https://httpbin.org/bytes/1024');
curl_setopt_array($ch, [
CURLOPT_FILE => $fp,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_TIMEOUT => 15,
]);
curl_exec($ch);
if (curl_errno($ch)) { fprintf(STDERR, "error: %s\n", curl_error($ch)); }
curl_close($ch);
fclose($fp);
并发请求(curl_multi)
- 使用
curl_multi_init()管理多个curl句柄并发执行,提升吞吐。 - 关键 API:
curl_multi_add_handle、curl_multi_exec、curl_multi_select、curl_multi_getcontent、curl_multi_remove_handle、curl_multi_close。
示例(并发 GET):
$urls = [
'https://httpbin.org/get?i=1',
'https://httpbin.org/get?i=2',
'https://httpbin.org/get?i=3',
];
$mh = curl_multi_init();
$chs = [];
foreach ($urls as $u) {
$ch = curl_init($u);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_multi_add_handle($mh, $ch);
$chs[] = $ch;
}
$running = null;
do {
$mrc = curl_multi_exec($mh, $running);
if ($mrc > 0) { usleep(1000); }
curl_multi_select($mh, 0.1);
} while ($running > 0);
$results = [];
foreach ($chs as $ch) {
$results[] = curl_multi_getcontent($ch);
curl_multi_remove_handle($mh, $ch);
curl_close($ch);
}
curl_multi_close($mh);
错误处理与调试
- 错误码:
curl_errno($ch),错误信息:curl_error($ch)。 - 调试日志:
CURLOPT_VERBOSE打印详细调试信息到STDERR;或使用CURLOPT_STDERR指定文件句柄。 - 状态码与响应头:
curl_getinfo($ch, CURLINFO_RESPONSE_CODE),自定义回调CURLOPT_HEADERFUNCTION解析头部。
性能与安全最佳实践
- 复用 Handle:避免频繁
init/close,可重用连接(Keep-Alive)。 - 合理超时:设定连接/总超时,设置低速限制避免卡死。
- 限制重定向:防止开放重定向带来的安全风险。
- 严格证书校验:开启
SSL_VERIFYPEER/VERIFYHOST,维护 CA 包;必要时配置CURLOPT_CAINFO。 - 敏感信息:Token/密码不写入日志;请求头与 URL 参数中避免泄露。
- 代理与网络:必要时配置
CURLOPT_PROXY与认证;在受限网络中预设合理失败重试策略。
完整示例与运行
curl_examples.php
<?php
function get_json(string $url, array $headers = []): array {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => array_merge($headers, ['Accept: application/json']),
CURLOPT_TIMEOUT => 10,
]);
$body = curl_exec($ch);
if ($body === false) throw new RuntimeException(curl_error($ch), curl_errno($ch));
$code = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if ($code >= 400) throw new RuntimeException("HTTP $code");
return json_decode($body, true, 512, JSON_THROW_ON_ERROR);
}
function post_json(string $url, $data, array $headers = []): array {
$ch = curl_init($url);
$payload = json_encode($data, JSON_UNESCAPED_UNICODE);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_HTTPHEADER => array_merge($headers, [
'Content-Type: application/json',
'Accept: application/json',
]),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
]);
$body = curl_exec($ch);
if ($body === false) throw new RuntimeException(curl_error($ch), curl_errno($ch));
$code = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if ($code >= 400) throw new RuntimeException("HTTP $code");
return json_decode($body, true, 512, JSON_THROW_ON_ERROR);
}
// demo
try {
$r1 = get_json('https://httpbin.org/get?ping=pong');
$r2 = post_json('https://httpbin.org/post', ['hello' => 'world']);
print_r([$r1['args'], $r2['json']]);
} catch (Throwable $e) {
fwrite(STDERR, $e->getMessage()."\n");
}
运行:
php curl_examples.php
并发下载示例与重试封装
使用 curl_multi 并发下载到磁盘
<?php
function multi_download(array $urls, string $outDir, float $timeout = 30.0): array {
if (!is_dir($outDir)) mkdir($outDir, 0777, true);
$mh = curl_multi_init();
$chs = [];
$fps = [];
foreach ($urls as $i => $url) {
$fname = sprintf('%s/file_%03d.bin', rtrim($outDir, '/'), $i+1);
$fp = fopen($fname, 'wb');
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_FILE => $fp,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_TIMEOUT => $timeout,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
]);
curl_multi_add_handle($mh, $ch);
$chs[$fname] = $ch;
$fps[$fname] = $fp;
}
$running = null;
do {
$mrc = curl_multi_exec($mh, $running);
if ($mrc === CURLM_CALL_MULTI_PERFORM) continue;
if ($running) curl_multi_select($mh, 0.2);
} while ($running > 0 && $mrc === CURLM_OK);
$results = [];
foreach ($chs as $fname => $ch) {
$err = curl_error($ch);
$code = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_multi_remove_handle($mh, $ch);
curl_close($ch);
fclose($fps[$fname]);
$results[$fname] = ($err === '' && $code >= 200 && $code < 300) ? 'ok' : ("error: $err, code=$code");
if ($err !== '' || $code >= 400) { @unlink($fname); }
}
curl_multi_close($mh);
return $results;
}
// demo
$urls = [
'https://httpbin.org/bytes/1024',
'https://httpbin.org/bytes/2048',
'https://httpbin.org/bytes/4096',
];
$res = multi_download($urls, __DIR__.'/downloads');
print_r($res);
运行:
php multi_download.php
ls -lh downloads
简易重试封装(指数退避 + 可重试状态)
<?php
function request_with_retry(callable $doRequest, int $maxAttempts = 3, int $baseDelayMs = 200, array $retryOnHttp = [429, 500, 502, 503, 504]) {
$attempt = 0;
while (true) {
$attempt++;
try {
[$body, $code] = $doRequest(); // 返回 [string $body, int $status]
if ($code >= 200 && $code < 300) return [$body, $code];
if ($attempt >= $maxAttempts || !in_array($code, $retryOnHttp, true)) return [$body, $code];
} catch (Throwable $e) {
if ($attempt >= $maxAttempts) throw $e;
}
$delay = $baseDelayMs * (2 ** ($attempt - 1));
usleep($delay * 1000);
}
}
// 使用示例:封装一次 cURL 请求
$do = function () {
$ch = curl_init('https://httpbin.org/status/503');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5,
]);
$body = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($body === false) {
$err = new RuntimeException(curl_error($ch), curl_errno($ch));
curl_close($ch);
throw $err;
}
curl_close($ch);
return [$body, $code];
};
[$body, $code] = request_with_retry($do, 4, 250);
echo "final code=$code\n";
建议:在生产环境中结合固定最大等待、随机抖动(jitter)与全局超时,避免惊群与长尾。
FAQ
- HTTPS 报证书错误:更新系统 CA 或指定
CURLOPT_CAINFO;不要关闭校验以“解决”。 - 超时不生效:区分连接与总超时;DNS 解析可能阻塞,考虑
CURLOPT_NOSIGNAL与低速限制。 - 代理认证失败:检查
CURLOPT_PROXYUSERPWD格式user:pass与代理协议(HTTP/SOCKS)。 - JSON 编码问题:使用
JSON_UNESCAPED_UNICODE与JSON_THROW_ON_ERROR获取一致编码与异常处理。 - 重用连接:避免每次调用都新初始化,复用 handle 或用持久化封装。
正文完


