cURL 详解
cURL(Client URL Library)是 PHP 中最强大、最常用的网络请求扩展。它基于 libcurl 库,支持 HTTP、HTTPS、FTP、SCP、LDAP 等多种协议,是现代 PHP 应用与外部服务通信的核心工具。本节将全面讲解 cURL 的基础用法、配置选项和常见场景。
基础概念
什么是 cURL
cURL 是一个利用 URL 语法传输数据的命令行工具和库。PHP 的 cURL 扩展(ext-curl)是对 libcurl 的封装,提供了面向过程和面向对象两种编程接口。
cURL 能做什么
- 发送 HTTP/HTTPS 请求(GET、POST、PUT、DELETE、PATCH 等)
- 上传和下载文件
- 处理 Cookie 和 Session
- SSL/TLS 证书验证
- 代理服务器配置
- HTTP 认证(Basic、Digest、Bearer 等)
- 请求超时控制
- 自定义 HTTP 头
- 响应头解析
安装与启用
大多数 PHP 发行版默认包含 cURL 扩展。确认扩展是否已启用:
php
<?php
declare(strict_types=1);
// 检查 cURL 扩展是否已加载
if (!extension_loaded('curl')) {
echo "cURL 扩展未启用" . PHP_EOL;
} else {
echo "cURL 版本: " . curl_version()['version'] . PHP_EOL;
echo "libcurl 版本: " . curl_version()['version_number'] . PHP_EOL;
}在 Linux 上安装:
bash
# Ubuntu/Debian
sudo apt-get install php-curl
# CentOS/RHEL
sudo yum install php-curl
# 编译安装时
./configure --with-curl基本语法
cURL 请求生命周期
一个完整的 cURL 请求包含以下步骤:
- 初始化:
curl_init()创建 cURL 句柄 - 配置选项:
curl_setopt()/curl_setopt_array()设置请求参数 - 执行请求:
curl_exec()发送请求并获取响应 - 获取信息:
curl_getinfo()获取请求信息 - 处理错误:
curl_errno()/curl_error()错误处理 - 关闭句柄:
curl_close()释放资源
发送 GET 请求
php
<?php
declare(strict_types=1);
function httpGet(string $url, array $headers = []): string
{
// 1. 初始化 cURL 句柄
$ch = curl_init();
// 2. 设置请求选项
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true, // 将响应作为字符串返回
CURLOPT_HEADER => false, // 不包含响应头
CURLOPT_FOLLOWLOCATION => true, // 跟随重定向
CURLOPT_MAXREDIRS => 10, // 最大重定向次数
CURLOPT_ENCODING => '', // 自动处理压缩编码(gzip、deflate)
CURLOPT_CONNECTTIMEOUT => 30, // 连接超时(秒)
CURLOPT_TIMEOUT => 60, // 总超时(秒)
CURLOPT_HTTPHEADER => $headers,
]);
// 3. 执行请求
$response = curl_exec($ch);
// 4. 错误检查
if ($response === false) {
$errno = curl_errno($ch);
$error = curl_error($ch);
curl_close($ch);
throw new RuntimeException("cURL 请求失败 [{$errno}]: {$error}");
}
// 5. 获取请求信息
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$totalTime = curl_getinfo($ch, CURLINFO_TOTAL_TIME);
// 6. 关闭句柄
curl_close($ch);
if ($httpCode >= 400) {
throw new RuntimeException("HTTP 请求失败,状态码: {$httpCode}");
}
return $response;
}
// 使用示例
try {
$data = httpGet('https://api.example.com/users');
echo $data;
} catch (RuntimeException $e) {
echo "错误: " . $e->getMessage() . PHP_EOL;
}发送 POST 请求
php
<?php
declare(strict_types=1);
function httpPost(
string $url,
array|string $data = [],
array $headers = [],
string $contentType = 'json'
): string {
$ch = curl_init();
// 根据内容类型处理请求体
if ($contentType === 'json') {
$payload = is_array($data) ? json_encode($data) : $data;
$headers[] = 'Content-Type: application/json';
} elseif ($contentType === 'form') {
$payload = is_array($data) ? http_build_query($data) : $data;
$headers[] = 'Content-Type: application/x-www-form-urlencoded';
} else {
$payload = is_array($data) ? json_encode($data) : $data;
}
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => false,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_TIMEOUT => 30,
]);
$response = curl_exec($ch);
if ($response === false) {
$error = curl_error($ch);
curl_close($ch);
throw new RuntimeException("POST 请求失败: {$error}");
}
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return $response;
}
// JSON POST 示例
$response = httpPost('https://api.example.com/users', [
'name' => '张三',
'email' => 'zhangsan@example.com',
]);
echo $response;
// 表单 POST 示例
$response = httpPost(
'https://api.example.com/login',
[
'username' => 'admin',
'password' => 'secret',
],
[],
'form'
);详细说明
CURLOPT 常用选项一览
基础选项
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
CURLOPT_URL | string | - | 请求的 URL |
CURLOPT_RETURNTRANSFER | bool | false | 为 true 时返回响应字符串而非直接输出 |
CURLOPT_HEADER | bool | false | 为 true 时在响应中包含头部 |
CURLOPT_FOLLOWLOCATION | bool | false | 跟随 HTTP 重定向 |
CURLOPT_MAXREDIRS | int | - | 最大重定向次数(需配合 FOLLOWLOCATION) |
CURLOPT_CUSTOMREQUEST | string | - | 自定义 HTTP 方法(PUT、DELETE、PATCH) |
超时选项
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
CURLOPT_TIMEOUT | int | 0(无限) | 请求最大执行时间(秒) |
CURLOPT_CONNECTTIMEOUT | int | 300 | 尝试连接的最长时间(秒) |
CURLOPT_TIMEOUT_MS | int | - | 以毫秒为单位的超时(7.16.2+) |
SSL/TLS 选项
php
<?php
declare(strict_types=1);
function secureRequest(string $url): string
{
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_SSL_VERIFYPEER => true, // 验证对等证书(生产环境必须为 true)
CURLOPT_SSL_VERIFYHOST => 2, // 检查主机名与证书匹配
CURLOPT_CERTINFO => true, // 获取证书信息
CURLOPT_CAINFO => '/path/to/cacert.pem', // CA 证书路径
// 也可以通过环境变量指定
// CURLOPT_CAPATH => '/path/to/certs/', // CA 证书目录
]);
$response = curl_exec($ch);
if ($response === false) {
$error = curl_error($ch);
curl_close($ch);
throw new RuntimeException("安全请求失败: {$error}");
}
// 获取 SSL 证书详情
$certInfo = curl_getinfo($ch, CURLINFO_CERTINFO);
curl_close($ch);
return $response;
}安全警告
在生产环境中,永远不要将 CURLOPT_SSL_VERIFYPEER 设置为 false。禁用 SSL 验证会使应用面临中间人攻击(MITM)的风险。如果遇到证书验证问题,应正确配置 CA 证书路径。
HTTP 方法
php
<?php
declare(strict_types=1);
class HttpClient
{
private string $baseUrl;
private array $defaultHeaders;
private int $timeout;
public function __construct(
string $baseUrl,
array $defaultHeaders = [],
int $timeout = 30
) {
$this->baseUrl = rtrim($baseUrl, '/');
$this->defaultHeaders = array_merge([
'Accept: application/json',
'Content-Type: application/json',
], $defaultHeaders);
$this->timeout = $timeout;
}
public function request(
string $method,
string $path,
array|string|null $data = null
): array {
$url = $this->baseUrl . '/' . ltrim($path, '/');
$ch = curl_init();
$options = [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => $this->timeout,
CURLOPT_CUSTOMREQUEST => strtoupper($method),
CURLOPT_HTTPHEADER => $this->defaultHeaders,
];
if ($data !== null) {
$options[CURLOPT_POSTFIELDS] = json_encode($data);
}
curl_setopt_array($ch, $options);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$info = curl_getinfo($ch);
if ($response === false) {
$error = curl_error($ch);
curl_close($ch);
throw new RuntimeException("请求失败: {$error}");
}
curl_close($ch);
return [
'status_code' => $httpCode,
'body' => $response,
'info' => $info,
];
}
public function get(string $path): array
{
return $this->request('GET', $path);
}
public function post(string $path, array $data): array
{
return $this->request('POST', $path, $data);
}
public function put(string $path, array $data): array
{
return $this->request('PUT', $path, $data);
}
public function delete(string $path): array
{
return $this->request('DELETE', $path);
}
public function patch(string $path, array $data): array
{
return $this->request('PATCH', $path, $data);
}
}
// 使用示例
$client = new HttpClient('https://api.example.com/v1');
// GET
$users = $client->get('users');
// POST
$newUser = $client->post('users', [
'name' => '李四',
'email' => 'lisi@example.com',
]);
// PUT
$updated = $client->put('users/1', [
'name' => '李四(已更新)',
]);
// DELETE
$client->delete('users/1');
// PATCH
$patched = $client->patch('users/2', [
'email' => 'newemail@example.com',
]);Cookie 处理
php
<?php
declare(strict_types=1);
function requestWithCookies(string $url): string
{
$cookieFile = sys_get_temp_dir() . '/curl_cookies.txt';
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
// Cookie 选项
CURLOPT_COOKIEJAR => $cookieFile, // 保存 Cookie 到文件
CURLOPT_COOKIEFILE => $cookieFile, // 从文件读取 Cookie
// 或者直接设置 Cookie 字符串
// CURLOPT_COOKIE => 'session_id=abc123; user=john',
// Cookie 相关行为
CURLOPT_COOKIESESSION => true, // 忽略先前保存的 Cookie(仅发起新的会话)
]);
$response = curl_exec($ch);
curl_close($ch);
return $response;
}响应头获取
php
<?php
declare(strict_types=1);
function fetchWithHeaders(string $url): array
{
$responseHeaders = [];
$ch = curl_init();
// 使用回调函数捕获响应头
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADERFUNCTION => function (
$ch,
string $header
) use (&$responseHeaders): int {
$len = strlen($header);
// 跳过状态行和空行
if ($len > 2) {
$parts = explode(':', $header, 2);
if (count($parts) === 2) {
$key = trim($parts[0]);
$value = trim($parts[1]);
$responseHeaders[$key] = $value;
}
}
return $len;
},
]);
$body = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($body === false) {
$error = curl_error($ch);
curl_close($ch);
throw new RuntimeException("请求失败: {$error}");
}
curl_close($ch);
return [
'status_code' => $httpCode,
'headers' => $responseHeaders,
'body' => $body,
];
}
// 使用示例
$result = fetchWithHeaders('https://api.example.com/data');
echo "状态码: {$result['status_code']}" . PHP_EOL;
echo "Content-Type: {$result['headers']['Content-Type']}" . PHP_EOL;
echo "响应体: {$result['body']}" . PHP_EOL;认证方式
php
<?php
declare(strict_types=1);
// Basic 认证
function requestWithBasicAuth(
string $url,
string $username,
string $password
): string {
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPAUTH => CURLAUTH_BASIC,
CURLOPT_USERPWD => "{$username}:{$password}",
]);
$response = curl_exec($ch);
curl_close($ch);
return $response !== false ? $response : throw new RuntimeException(curl_error($ch));
}
// Bearer Token 认证
function requestWithBearerToken(string $url, string $token): string
{
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $token,
'Accept: application/json',
],
]);
$response = curl_exec($ch);
curl_close($ch);
return $response !== false ? $response : throw new RuntimeException(curl_error($ch));
}
// Digest 认证
function requestWithDigestAuth(
string $url,
string $username,
string $password
): string {
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPAUTH => CURLAUTH_DIGEST,
CURLOPT_USERPWD => "{$username}:{$password}",
]);
$response = curl_exec($ch);
curl_close($ch);
return $response !== false ? $response : throw new RuntimeException(curl_error($ch));
}代理配置
php
<?php
declare(strict_types=1);
function requestViaProxy(
string $url,
string $proxyHost,
int $proxyPort,
?string $proxyAuth = null
): string {
$ch = curl_init();
$options = [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_PROXY => "{$proxyHost}:{$proxyPort}",
CURLOPT_PROXYTYPE => CURLPROXY_HTTP, // HTTP 代理
// CURLPROXY_SOCKS4, CURLPROXY_SOCKS4A, CURLPROXY_SOCKS5 也可用
];
if ($proxyAuth !== null) {
$options[CURLOPT_PROXYUSERPWD] = $proxyAuth;
}
curl_setopt_array($ch, $options);
$response = curl_exec($ch);
if ($response === false) {
$error = curl_error($ch);
curl_close($ch);
throw new RuntimeException("代理请求失败: {$error}");
}
curl_close($ch);
return $response;
}
// SOCKS5 代理示例
function requestViaSocks5(
string $url,
string $proxyHost,
int $proxyPort
): string {
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_PROXY => "{$proxyHost}:{$proxyPort}",
CURLOPT_PROXYTYPE => CURLPROXY_SOCKS5,
]);
$response = curl_exec($ch);
curl_close($ch);
return $response !== false ? $response : throw new RuntimeException(curl_error($ch));
}curl_getinfo 获取信息
curl_getinfo() 返回请求执行后的详细信息:
php
<?php
declare(strict_types=1);
function getInfo(string $url): array
{
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
]);
curl_exec($ch);
// 获取全部信息
$allInfo = curl_getinfo($ch);
// 获取单个信息
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$totalTime = curl_getinfo($ch, CURLINFO_TOTAL_TIME);
$namelookup = curl_getinfo($ch, CURLINFO_NAMELOOKUP_TIME);
$connect = curl_getinfo($ch, CURLINFO_CONNECT_TIME);
$pretransfer = curl_getinfo($ch, CURLINFO_PRETRANSFER_TIME);
$startTransfer = curl_getinfo($ch, CURLINFO_STARTTRANSFER_TIME);
$redirectCount = curl_getinfo($ch, CURLINFO_REDIRECT_COUNT);
$sizeUpload = curl_getinfo($ch, CURLINFO_SIZE_UPLOAD);
$sizeDownload = curl_getinfo($ch, CURLINFO_SIZE_DOWNLOAD);
$speedDownload = curl_getinfo($ch, CURLINFO_SPEED_DOWNLOAD);
$url = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
$contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
curl_close($ch);
return [
'http_code' => $httpCode,
'total_time' => $totalTime,
'dns_time' => $namelookup,
'connect_time' => $connect,
'transfer_time' => $startTransfer,
'download_speed' => $speedDownload,
'download_size' => $sizeDownload,
'upload_size' => $sizeUpload,
'effective_url' => $url,
'content_type' => $contentType,
];
}实战示例
REST API 客户端
php
<?php
declare(strict_types=1);
/**
* 通用 REST API 客户端
*/
class ApiClient
{
private string $baseUrl;
private string $token;
private array $headers = [];
private int $timeout;
private int $connectTimeout;
public function __construct(
string $baseUrl,
string $token = '',
int $timeout = 30,
int $connectTimeout = 10
) {
$this->baseUrl = rtrim($baseUrl, '/');
$this->token = $token;
$this->timeout = $timeout;
$this->connectTimeout = $connectTimeout;
}
public function setHeaders(array $headers): self
{
$this->headers = $headers;
return $this;
}
public function setTimeout(int $timeout): self
{
$this->timeout = $timeout;
return $this;
}
public function request(
string $method,
string $uri,
array $data = [],
array $query = []
): array {
$url = $this->buildUrl($uri, $query);
$ch = $this->createHandle($method, $url, $data);
$response = $this->execute($ch);
curl_close($ch);
return $response;
}
private function buildUrl(string $uri, array $query): string
{
$url = $this->baseUrl . '/' . ltrim($uri, '/');
if (!empty($query)) {
$url .= '?' . http_build_query($query);
}
return $url;
}
private function createHandle(
string $method,
string $url,
array $data
): \CurlHandle {
$ch = curl_init();
$headers = array_merge(
[
'Accept: application/json',
'Content-Type: application/json',
'User-Agent: PHP-ApiClient/1.0',
],
$this->headers
);
if (!empty($this->token)) {
$headers[] = "Authorization: Bearer {$this->token}";
}
$options = [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => $this->timeout,
CURLOPT_CONNECTTIMEOUT => $this->connectTimeout,
CURLOPT_CUSTOMREQUEST => strtoupper($method),
CURLOPT_HTTPHEADER => $headers,
CURLOPT_ENCODING => '',
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 5,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
];
if (!empty($data) && in_array(strtoupper($method), ['POST', 'PUT', 'PATCH'])) {
$options[CURLOPT_POSTFIELDS] = json_encode($data);
}
curl_setopt_array($ch, $options);
return $ch;
}
private function execute(\CurlHandle $ch): array
{
$body = curl_exec($ch);
if ($body === false) {
throw new RuntimeException(
"cURL 错误 [" . curl_errno($ch) . "]: " . curl_error($ch)
);
}
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$info = curl_getinfo($ch);
$decoded = json_decode($body, true);
return [
'status' => $httpCode,
'data' => $decoded,
'raw' => $body,
'info' => $info,
];
}
}
// 使用示例
$client = new ApiClient('https://jsonplaceholder.typicode.com');
// 获取用户列表
$users = $client->get('users');
// 获取单个用户
$user = $client->get('users/1');
// 创建文章
$post = $client->post('posts', [
'title' => 'Hello World',
'body' => 'This is a test post.',
'userId' => 1,
]);
// 更新文章
$updated = $client->put('posts/1', [
'title' => 'Updated Title',
]);
// 删除文章
$client->delete('posts/1');
// 带查询参数的请求
$filtered = $client->get('posts', query: ['userId' => 1]);请求重试机制
php
<?php
declare(strict_types=1);
class RetryableCurlClient
{
private int $maxRetries;
private int $retryDelay;
private array $retryableStatusCodes = [
429, // Too Many Requests
500, // Internal Server Error
502, // Bad Gateway
503, // Service Unavailable
504, // Gateway Timeout
];
public function __construct(int $maxRetries = 3, int $retryDelay = 1000)
{
$this->maxRetries = $maxRetries;
$this->retryDelay = $retryDelay;
}
public function get(string $url): string
{
$lastException = null;
$attempt = 0;
while ($attempt < $this->maxRetries) {
$attempt++;
try {
$response = $this->doRequest($url);
return $response;
} catch (RuntimeException $e) {
$lastException = $e;
if ($attempt < $this->maxRetries) {
// 指数退避
$delay = $this->retryDelay * (2 ** ($attempt - 1));
usleep($delay * 1000); // 微秒转毫秒
}
}
}
throw $lastException;
}
private function doRequest(string $url): string
{
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
if ($body === false) {
throw new RuntimeException("请求失败: {$error}");
}
if (in_array($httpCode, $this->retryableStatusCodes)) {
throw new RuntimeException(
"可重试的 HTTP 错误,状态码: {$httpCode}"
);
}
return $body;
}
}注意事项
常见错误码
| 错误码 | 常量 | 说明 | 解决方案 |
|---|---|---|---|
| 6 | CURLE_COULDNT_RESOLVE_HOST | DNS 解析失败 | 检查 URL、DNS 配置 |
| 7 | CURLE_COULDNT_CONNECT | 连接失败 | 检查目标服务器状态 |
| 28 | CURLE_OPERATION_TIMEDOUT | 操作超时 | 增加 timeout 值 |
| 35 | CURLE_SSL_CONNECT_ERROR | SSL 连接错误 | 检查证书、SSL 配置 |
| 60 | CURLE_SSL_CACERT | CA 证书验证失败 | 配置正确的 CA 证书 |
| 67 | CURLE_LOGIN_DENIED | 认证失败 | 检查用户名密码 |
内存管理
php
<?php
declare(strict_types=1);
// 正确:手动关闭句柄
$ch = curl_init('https://example.com');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch); // 释放资源
// PHP 8.0+ : CurlHandle 是对象,在离开作用域时自动释放
function fetchUrl(string $url): string
{
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec($ch);
curl_close($ch);
return $result !== false ? $result : '';
}编码问题
php
<?php
declare(strict_types=1);
// CURLOPT_ENCODING 设置为空字符串,让 cURL 自动处理 gzip/deflate
curl_setopt($ch, CURLOPT_ENCODING, '');
// 等同于发送 Accept-Encoding: gzip, deflate, br
// cURL 会自动解压响应体
// 手动指定编码
curl_setopt($ch, CURLOPT_ENCODING, 'gzip');最佳实践
- 始终检查返回值:
curl_exec()在失败时返回false - 设置合理的超时:避免无限等待,通常 5~30 秒
- 启用 SSL 验证:生产环境必须启用
CURLOPT_SSL_VERIFYPEER - 关闭连接:使用完毕后调用
curl_close()释放资源 - 使用
curl_setopt_array():比多次调用curl_setopt()更高效 - 捕获响应头信息:便于调试和日志记录
- 使用异常处理:将错误封装到异常中,便于上层处理
下一节
继续学习:cURL 高级用法 - 并发请求、文件上传下载