
PHP 中 curl_init 函数的用法与基础示例
在 PHP 中调用远程 HTTP 服务时,curl_init() 是 cURL 扩展提供的初始化函数。它负责创建一个 cURL 句柄,后续可以通过 curl_setopt() 设置请求选项,再用 curl_exec() 执行请求。它既能用于简单的 GET 请求,也能配合请求头、请求体、超时和传输选项完成更复杂的 HTTP 通信。
需要先明确一点:curl_init() 本身不会发送请求,也不负责解析响应。它只是准备一个可配置的传输句柄。完整流程通常是“初始化句柄、设置选项、执行请求、检查结果、读取响应信息、关闭句柄”。理解这条流程,能避免把初始化成功误认为请求成功。
一、适用场景与运行条件
cURL 适用于 PHP 程序需要主动访问其他服务的场景,例如:
- 调用第三方 REST API,获取 JSON 或提交数据;
- 向内部服务发送 HTTP 请求,获取业务数据;
- 下载文件或读取远程资源;
- 需要自定义请求头、请求方式、超时、代理或 TLS 校验的 HTTP 通信。
使用前,服务器需要安装并启用 PHP cURL 扩展。可以在命令行检查:
php -m | grep -i curl
也可以在 PHP 中判断扩展是否可用:
<?php
if (!extension_loaded('curl')) {
throw new RuntimeException('PHP cURL 扩展未启用');
}
Windows 环境通常需要在对应 PHP 配置中启用 cURL 扩展;Linux 环境则需按发行版和 PHP 版本安装相应扩展包。CLI 与 PHP-FPM、Apache 可能使用不同的配置文件,因此命令行检查通过,不一定代表 Web 进程也已启用。部署时应分别确认运行环境。
二、curl_init 的参数和返回值
常见函数签名如下:
curl_init(?string $url = null): CurlHandle|false
PHP 8 中,成功时返回 CurlHandle 对象,失败时返回 false。旧版本 PHP 的句柄类型表现不同,历史版本中返回的是资源;编写兼容旧项目的代码时,应结合项目 PHP 版本查阅对应文档。现代 PHP 项目通常可按 PHP 8 的返回类型处理。
$url 是可选的 URL 字符串。传入 URL 时,它会作为请求目标;不传入时,可以先初始化句柄,再通过 CURLOPT_URL 指定目标。下面两种写法都可以:
<?php
$handle = curl_init('https://example.com/');
<?php
$handle = curl_init();
if ($handle === false) {
throw new RuntimeException('无法初始化 cURL');
}
curl_setopt($handle, CURLOPT_URL, 'https://example.com/');
参数应为字符串或 null。不要把数组、对象等其他类型当作 URL 传入。函数也不会替你验证 URL 是否可信、是否能连接,或目标服务是否正常。初始化成功只表示句柄创建成功,不代表目标地址可访问。
可用协议与传输能力受 PHP cURL 扩展及其所链接的 libcurl 版本、构建选项影响。不能假设每台服务器都支持相同协议或功能;需要时可用 curl_version() 检查运行环境的 cURL 信息,并以部署环境的实际能力为准。
三、最小 GET 请求:设置、执行、检查
下面的示例发起一个 GET 请求,将响应内容输出,并分别检查传输错误和 HTTP 状态码:
<?php
$url = 'https://httpbin.org/get';
$handle = curl_init($url);
if ($handle === false) {
throw new RuntimeException('无法初始化 cURL');
}
curl_setopt_array($handle, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_TIMEOUT => 15,
]);
$response = curl_exec($handle);
if ($response === false) {
$message = curl_error($handle);
$number = curl_errno($handle);
curl_close($handle);
throw new RuntimeException("cURL 请求失败({$number}):{$message}");
}
$statusCode = curl_getinfo($handle, CURLINFO_HTTP_CODE);
curl_close($handle);
if ($statusCode < 200 || $statusCode >= 300) {
throw new RuntimeException("服务返回 HTTP 状态码:{$statusCode}");
}
echo $response;
这里的 CURLOPT_RETURNTRANSFER 很重要。设为 true 后,curl_exec() 将响应内容作为返回值交给程序;未设置时,响应内容通常会直接输出,curl_exec() 的返回值也就不能按“响应正文”来理解。
请求结果要分两层判断:
- 传输是否成功:
curl_exec()返回false,通常表示传输层发生错误,例如连接失败、超时或 TLS 握手失败。使用curl_errno()和curl_error()获取错误码与错误描述。 - HTTP 请求是否成功:即使传输成功,服务器仍可能返回 404、500 等状态码。HTTP 错误状态通常不会让
curl_exec()自动返回false,需通过curl_getinfo()获取状态码并由业务代码判断。
因此,不应仅通过 $response !== false 就认定业务请求成功。还要检查状态码、响应格式和业务字段。反过来,也不要把 HTTP 404 简单描述成 cURL 传输错误:它是服务器返回的 HTTP 响应。
四、使用 curl_setopt 与 curl_setopt_array
初始化后通过 curl_setopt() 设置单个选项,或者通过 curl_setopt_array() 一次设置多个选项。下面的代码作用相同:
<?php
curl_setopt($handle, CURLOPT_RETURNTRANSFER, true);
curl_setopt($handle, CURLOPT_TIMEOUT, 15);
<?php
curl_setopt_array($handle, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 15,
]);
对于选项值,类型和含义由具体的 CURLOPT_* 选项决定,不能简单地把所有值都当作字符串处理。比如超时选项使用数字,返回传输内容的选项使用布尔值,请求头选项接受字符串数组。设置函数可能返回布尔结果;在需要严格诊断的程序中,应检查设置是否成功,并确认当前 PHP 与 libcurl 支持所用选项。
常用选项包括:
CURLOPT_URL:请求目标 URL。CURLOPT_RETURNTRANSFER:将响应内容作为curl_exec()返回值。CURLOPT_CONNECTTIMEOUT:连接阶段的超时限制,单位为秒。CURLOPT_TIMEOUT:整个传输过程的超时限制,单位为秒。CURLOPT_HTTPHEADER:请求头列表,例如Accept或Authorization。CURLOPT_POST、CURLOPT_POSTFIELDS:用于常见的 POST 请求。CURLOPT_FOLLOWLOCATION:是否跟随重定向,开启前应考虑目标地址的安全边界。
五、发送 GET 参数
GET 参数通常放在 URL 查询字符串中。不要手工拼接未经编码的用户输入,否则空格、&、? 等字符可能改变参数结构。可以使用 http_build_query() 生成查询字符串:
<?php
$params = [
'q' => 'php curl',
'page' => 2,
];
$query = http_build_query($params);
$url = 'https://httpbin.org/get?' . $query;
$handle = curl_init($url);
if ($handle === false) {
throw new RuntimeException('无法初始化 cURL');
}
curl_setopt_array($handle, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_TIMEOUT => 15,
]);
$response = curl_exec($handle);
if ($response === false) {
$message = curl_error($handle);
curl_close($handle);
throw new RuntimeException($message);
}
$statusCode = curl_getinfo($handle, CURLINFO_HTTP_CODE);
curl_close($handle);
if ($statusCode !== 200) {
throw new RuntimeException("预期返回 200,实际为 {$statusCode}");
}
echo $response;
示例使用 httpbin.org 演示请求格式;实际项目应替换为自己信任且可访问的服务。若参数含有敏感信息,不应因为“方便”就放进 URL:查询字符串可能被记录在服务器日志、代理日志或其他监控系统中。认证凭据一般应放在受保护的请求头或其他符合服务端协议的载体中。
六、发送表单 POST 请求
对于传统的表单提交,可以通过 CURLOPT_POST 和 CURLOPT_POSTFIELDS 发送数据:
<?php
$formData = [
'username' => 'alice',
'note' => 'hello',
];
$handle = curl_init('https://httpbin.org/post');
if ($handle === false) {
throw new RuntimeException('无法初始化 cURL');
}
curl_setopt_array($handle, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query($formData),
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_TIMEOUT => 15,
]);
$response = curl_exec($handle);
if ($response === false) {
$message = curl_error($handle);
curl_close($handle);
throw new RuntimeException($message);
}
$statusCode = curl_getinfo($handle, CURLINFO_HTTP_CODE);
curl_close($handle);
if ($statusCode < 200 || $statusCode >= 300) {
throw new RuntimeException("HTTP 请求失败:{$statusCode}");
}
echo $response;
把数组直接传给 CURLOPT_POSTFIELDS,在部分 PHP 与 cURL 用法中会触发 multipart/form-data 编码,通常用于文件上传或多部分表单。若接口要求 application/x-www-form-urlencoded,明确使用 http_build_query() 可以让发送格式更清楚。具体格式应与服务端接口约定一致。
七、发送 JSON 请求
调用 JSON API 时,先用 json_encode() 编码数据,再设置匹配的 Content-Type 请求头。编码失败时不应继续发送空内容:
<?php
$payload = [
'name' => 'Alice',
'enabled' => true,
];
$json = json_encode($payload, JSON_UNESCAPED_UNICODE);
if ($json === false) {
throw new RuntimeException('JSON 编码失败:' . json_last_error_msg());
}
$handle = curl_init('https://httpbin.org/post');
if ($handle === false) {
throw new RuntimeException('无法初始化 cURL');
}
curl_setopt_array($handle, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $json,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Accept: application/json',
],
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_TIMEOUT => 15,
]);
$response = curl_exec($handle);
if ($response === false) {
$message = curl_error($handle);
curl_close($handle);
throw new RuntimeException($message);
}
$statusCode = curl_getinfo($handle, CURLINFO_HTTP_CODE);
curl_close($handle);
if ($statusCode < 200 || $statusCode >= 300) {
throw new RuntimeException("HTTP 请求失败:{$statusCode}");
}
echo $response;
若 JSON 响应还需要在 PHP 中使用,应继续调用 json_decode(),并检查解析结果。响应正文可能不是合法 JSON,也可能是空字符串或错误页,不能假设只要请求成功就一定能解析。
八、设置请求头与认证信息
请求头常用于声明响应格式、认证令牌或应用版本。请求头值应由程序安全地构造,避免把含有换行符的外部输入直接拼进请求头:
<?php
$token = getenv('API_TOKEN');
if ($token === false || $token === '') {
throw new RuntimeException('未配置 API_TOKEN');
}
$handle = curl_init('https://api.example.com/v1/profile');
if ($handle === false) {
throw new RuntimeException('无法初始化 cURL');
}
curl_setopt_array($handle, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Accept: application/json',
'Authorization: Bearer ' . $token,
],
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_TIMEOUT => 15,
]);
$response = curl_exec($handle);
if ($response === false) {
$message = curl_error($handle);
curl_close($handle);
throw new RuntimeException($message);
}
$statusCode = curl_getinfo($handle, CURLINFO_HTTP_CODE);
curl_close($handle);
echo "HTTP {$statusCode}\n";
echo $response;
示例中的域名是占位地址,令牌从环境变量读取,避免将真实凭据硬编码进源代码。生产环境还应使用合适的密钥管理方式,并避免将令牌、完整请求头或含有敏感内容的响应写入普通日志。
九、下载响应到文件
下载较大的文件时,可以让 cURL 把响应直接写入文件,避免把整个文件内容保存在 PHP 字符串中。以下示例写入临时文件,并在传输失败或服务器返回非 2xx 状态时删除它:
<?php
$url = 'https://example.com/archive.zip';
$path = __DIR__ . '/archive.zip';
$file = fopen($path, 'wb');
if ($file === false) {
throw new RuntimeException('无法创建下载文件');
}
$handle = curl_init($url);
if ($handle === false) {
fclose($file);
throw new RuntimeException('无法初始化 cURL');
}
curl_setopt_array($handle, [
CURLOPT_FILE => $file,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_TIMEOUT => 120,
]);
$success = curl_exec($handle);
if ($success === false) {
$message = curl_error($handle);
curl_close($handle);
fclose($file);
@unlink($path);
throw new RuntimeException('下载失败:' . $message);
}
$statusCode = curl_getinfo($handle, CURLINFO_HTTP_CODE);
curl_close($handle);
fclose($file);
if ($statusCode < 200 || $statusCode >= 300) {
@unlink($path);
throw new RuntimeException("下载服务返回 HTTP {$statusCode}");
}
echo "文件已保存到 {$path}\n";
下载代码还需要考虑磁盘空间、文件大小限制、目标路径权限和并发写入。若文件名来自远端响应头或用户输入,不能直接作为本地路径使用;应生成服务端控制的文件名,避免路径穿越或覆盖重要文件。若要验证文件内容,不能只根据扩展名判断,应结合可信来源、文件签名或业务校验。
十、超时、重定向和 TLS 校验
网络请求应设置超时。CURLOPT_CONNECTTIMEOUT 限制建立连接所用时间,CURLOPT_TIMEOUT 限制整个请求过程。只设置连接超时而没有整体超时,仍可能让请求在连接建立后长时间等待。超时值应根据业务接口的正常响应时间、任务类型和系统并发量确定。
cURL 通常会对 HTTPS 证书进行校验。正式环境不应为了“解决证书错误”而关闭校验,例如把 CURLOPT_SSL_VERIFYPEER 设置为 false,或关闭主机名校验。关闭校验会削弱对服务器身份的确认,使连接更容易遭受中间人攻击。正确做法是修复服务器证书、系统 CA 证书或 CA 配置问题,并保持校验开启。不要在生产代码中采用关闭校验作为通用绕过办法。
是否跟随重定向应依据业务需求决定。开启 CURLOPT_FOLLOWLOCATION 后,请求可能访问重定向指向的新地址。在涉及不可信 URL、内网服务或敏感凭据时,应评估重定向带来的风险,限制允许的协议和目标范围,并确认重定向时认证信息的处理符合预期。不要把“跟随所有重定向”当作无条件安全的默认策略。
十一、安全边界:避免把 cURL 变成 SSRF 入口
如果程序允许用户提供请求 URL,再把它交给 curl_init($url),就可能形成服务器端请求伪造(SSRF)风险。攻击者可能利用服务器的网络位置访问本机管理端口、云平台元数据服务或内部系统。即使 URL 是 HTTPS,也不能因此视为安全;协议加密与目标授权是两个不同问题。
安全控制应围绕“程序允许访问哪些目标”来设计,而不是只依赖 URL 格式检查。常见做法包括:
- 优先采用目标服务白名单或固定的 API 主机名,避免直接接受完整 URL;
- 限制允许的协议、端口和主机,拒绝不需要的协议;
- 在访问前后考虑 DNS 解析、重绑定和重定向问题,仅做一次字符串检查不足以覆盖全部风险;
- 限制服务器网络出口,使应用进程不能随意访问管理网段和敏感内部服务;
- 为请求设置连接与整体超时,并限制响应体大小、并发数和重试次数;
- 不向用户暴露内部网络结构、完整错误堆栈或包含凭据的诊断信息。
如果功能本身就是“抓取任意 URL”,需要把它视为高风险能力,采用严格的目标校验、网络隔离和资源限制。单纯过滤掉字符串中的 localhost 或某个内网 IP,并不能构成可靠的完整防护。
十二、关闭句柄与资源管理
请求完成后应调用 curl_close() 释放句柄。在 PHP 8 中句柄是对象,生命周期也受垃圾回收管理,但显式关闭仍能让资源释放时机更清晰,也便于兼容旧版本代码。使用文件句柄作为响应输出目标时,还需单独关闭文件。
异常处理时要避免遗漏清理动作。简单脚本可以像前面的示例一样,在每条失败路径中关闭句柄;更复杂的程序则可把请求封装在函数中,并使用 try/finally 确保清理逻辑执行。无论采用哪种写法,响应正文、错误信息和 HTTP 状态码都应在关闭前读取,因为关闭句柄后不应再依赖它获取传输信息。
十三、常见错误与排查方法
1. 认为 curl_init 成功就代表请求成功
curl_init() 只创建句柄。连接发生在 curl_exec() 阶段,网络、证书和远端服务问题要在执行时检查。
2. 忘记设置 CURLOPT_RETURNTRANSFER
这会使响应可能直接输出到页面或命令行,而不是作为字符串返回。需要在程序中解析响应时,通常应显式设置为 true。
3. 只检查 curl_exec 返回值
传输成功不等于 HTTP 状态码成功。通过 curl_getinfo($handle, CURLINFO_HTTP_CODE) 检查状态码,并根据业务协议判断是否接受响应。
4. 没有设置超时
远端服务异常或网络卡顿可能拖住 PHP 工作进程。通常至少设置连接超时和整体超时;具体值应考虑接口特性,而不是机械照搬示例。
5. 关闭 TLS 证书校验
这不是可靠的证书错误修复方式,会降低 HTTPS 连接的身份验证能力。应检查证书链、CA 证书和服务器配置,保持校验开启。
6. POST 数据编码与接口要求不一致
服务端期望 JSON 时,应发送 JSON 正文并设置合适的 Content-Type;期望表单编码时,则按表单格式发送。请求体格式错误往往会造成服务端无法识别字段。
7. 把服务器错误页当成业务数据
服务端可能在 500 状态码下返回 HTML 错误页,也可能在 200 状态码下返回业务失败 JSON。应联合检查 HTTP 状态码、响应类型和业务字段。
8. 在日志中记录敏感信息
完整 URL、请求头和请求体可能含有令牌、个人信息或查询条件。记录诊断信息前应进行脱敏,并控制日志访问权限和保留期限。
十四、一个可复用的基础封装
对于小型项目,可以将初始化、超时设置、错误检查和状态码检查集中到一个函数中。下面的函数接受 URL 与可选请求配置,返回响应正文;具体 API 的认证头、请求体和业务判断仍由调用方负责:
<?php
function requestWithCurl(string $url, array $options = []): string
{
$handle = curl_init($url);
if ($handle === false) {
throw new RuntimeException('无法初始化 cURL');
}
$defaults = [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_TIMEOUT => 15,
];
try {
if (!curl_setopt_array($handle, $defaults + $options)) {
throw new RuntimeException('设置 cURL 选项失败');
}
$response = curl_exec($handle);
if ($response === false) {
$number = curl_errno($handle);
$message = curl_error($handle);
throw new RuntimeException(
"cURL 错误 {$number}:{$message}"
);
}
$statusCode = curl_getinfo($handle, CURLINFO_HTTP_CODE);
if ($statusCode < 200 || $statusCode >= 300) {
throw new RuntimeException(
"远端服务返回 HTTP {$statusCode}"
);
}
return $response;
} finally {
curl_close($handle);
}
}
$response = requestWithCurl('https://httpbin.org/get');
echo $response;
这里使用数组联合运算符 + 合并默认选项和调用方选项:左侧已有的键优先,因此调用方不能覆盖默认超时。若希望允许调用方覆盖默认值,应采用符合预期的合并方式,并在覆盖后仍执行安全校验。封装函数也没有自动解决 SSRF、响应大小限制、重试策略或业务 JSON 校验;这些需要根据具体应用补充。
总结
curl_init() 是 PHP cURL 请求流程的起点,负责创建句柄并可选地接收目标 URL。真正的请求需要设置选项并调用 curl_exec();结果判断则要区分传输错误与 HTTP 状态码。日常使用中,显式设置返回模式和超时、保持 TLS 证书校验、按接口要求构造请求体、及时关闭句柄,是可靠调用的基础。
如果 URL、请求头或请求内容受用户控制,还必须考虑 SSRF、凭据泄露、重定向和资源消耗等问题。把这些边界与基础调用流程一并纳入设计,比只让请求“跑通”更重要。









