引言
socket、fsockopen、stream、curl 都是PHP进行网络请求的工具,具体区别参见 https://ibaiyang.github.io/blog/php/2019/08/15/PHP-socket-fsockopen-stream-curl-区别.html
PHP 的 Client URL 库(简写为cURL) 支持 Daniel Stenberg 创建的 libcurl 库,能够连接通讯各种服务器、使用各种协议。 libcurl 目前支持的协议有 http、https、ftp、gopher、telnet、dict、file、ldap。 libcurl 同时支持 HTTPS 证书、HTTP POST、HTTP PUT、 FTP 上传(也能通过 PHP 的 FTP 扩展完成)、HTTP 基于表单的上传、 代理、cookies、用户名+密码的认证。
Client URL 库 是一个利用URL语法规定来传输文件和数据的工具,支持很多协议,如HTTP、FTP、TELNET等。 PHP也支持 cURL 库,封装了常见互联网协议操作。它还可以根据URL前缀是“HTTP” 还是“HTTPS”自动选择是否加密发送内容,非常灵活。
Linux 需要安装 libcurl包才能使用 PHP 的 cURL 函数。PHP 需要使用 7.10.5 或更高版本的 libcurl。
要 PHP 支持 cURL,必须在编译 PHP 时加上 --with-curl[=DIR]
选项,DIR 为包含 lib 和 include 的目录路径。
在 include 目录中必须有一个名为curl,包含了easy.h 和curl.h 的文件夹。lib文件夹里应该有一个名为libcurl.a的文件。
本文将介绍如何在PHP中运用 cURL 函数。
cURL函数组成
使用cURL的PHP扩展完成一个HTTP请求的发送一般有以下几个步骤:
- curl_init()初始化连接句柄;
- curl_setopt()设置cURL选项;
- curl_exec()执行并获取结果;
- curl_close()关闭会话释放cURL连接句柄。
/**
* Initialize a cURL session
* @link https://php.net/manual/en/function.curl-init.php
* @param string $url [optional] <p>
* If provided, the CURLOPT_URL option will be set
* to its value. You can manually set this using the
* curl_setopt function.
* </p>
* @return resource|false a cURL handle on success, false on errors.
* @since 4.0.2
* @since 5.0
*/
function curl_init ($url = null) {}
/**
* Perform a cURL session
* @link https://php.net/manual/en/function.curl-exec.php
* @param resource $ch
* @return string|bool true on success or false on failure. However, if the CURLOPT_RETURNTRANSFER
* option is set, it will return the result on success, false on failure.
* @since 4.0.2
* @since 5.0
*/
function curl_exec ($ch) {}
/**
* Return the last error number
* @link https://php.net/manual/en/function.curl-errno.php
* @param resource $ch
* @return int the error number or 0 (zero) if no error
* occurred.
* @since 4.0.3
* @since 5.0
*/
function curl_errno ($ch) {}
/**
* Close a cURL session
* @link https://php.net/manual/en/function.curl-close.php
* @param resource $ch
* @return void
* @since 4.0.2
* @since 5.0
*/
function curl_close ($ch) {}
curl_setopt
/**
* Set an option for a cURL transfer
* @link https://php.net/manual/en/function.curl-setopt.php
* @param resource $ch
* @param int $option <p>
* The CURLOPT_XXX option to set.
* </p>
* @param mixed|callable $value <p>
* The value to be set on option.
* </p>
*
* @return bool true on success or false on failure.
* @since 4.0.2
* @since 5.0
*/
function curl_setopt ($ch, $option, $value) {}
cURL 使用中,最复杂的就是 curl_setopt 设置cURL传输选项选项,下面列几个常用的选项:
选项 | 说明 |
---|---|
CURLOPT_URL | 需要获取的 URL 地址,也可以在curl_init() 初始化会话的时候。 |
CURLOPT_RETURNTRANSFER | TRUE 将curl_exec()获取的信息以字符串返回,而不是直接输出。 |
CURLOPT_HEADER | 启用时会将返回的头文件信息作为数据流输出。 |
CURLOPT_POST | TRUE 时会发送 POST 请求,类型为:application/x-www-form-urlencoded ,是 HTML 表单提交时最常见的一种。 |
CURLOPT_POSTFIELDS | 全部数据使用HTTP协议中的 “POST” 操作来发送。 要发送文件,在文件名前面加上@ 前缀并使用完整路径。 文件类型可在文件名后以 ;type=mimetype 的格式指定。这个参数可以是 urlencoded 后的字符串,类似para1=val1¶2=val2&... ,也可以使用一个以字段名为键值,字段数据为值的数组。 如果value是一个数组,Content-Type头将会被设置成multipart/form-data 。 从 PHP 5.2.0 开始,使用 @ 前缀传递文件时,value 必须是个数组。 从 PHP 5.5.0 开始, @ 前缀已被废弃,文件可通过 CURLFile 发送。 设置 CURLOPT_SAFE_UPLOAD 为 TRUE 可禁用 @ 前缀发送文件,以增加安全性。 |
CURLOPT_TIMEOUT | 允许 cURL 函数执行的最长秒数。 |
CURLOPT_SSL_VERIFYPEER | 设为true,只信任CA颁布的证书;设为false,信任任何证书 |
CURLOPT_SSL_VERIFYHOST | 2:检查证书中是否设置域名,并且是否与提供的主机名匹配;1:检查证书中是否设置域名;0:不验证域名是否存在 |
值得说明的是curl_setopt($con, CURLOPT_POSTFIELDS, $params);
这里 $params 可以是string,也可以是array。
当是array时,就是传统的post的数组,Content-Type头将会被设置成multipart/form-data
。
当是string时,就有可能是数组http_build_query()
后的字符串,也有可能是json()格式化后的字符串,
Content-Type头将会被设置成application/x-www-form-urlencoded
。看具体的使用。
更多可以看这里:
https://www.php.net/manual/zh/function.curl-setopt.php
简单使用
使用cURL发送HTTP的典型过程举例。
GET示例
<?php
// 1. 初始化
$ch = curl_init();
// 2. 设置选项,包括URL
curl_setopt($ch, CURLOPT_URL, "http://www.test.com");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HEADER, 0);
// 3. 执行并获取HTML文档内容
$output = curl_exec($ch);
if ($output === FALSE ) {
echo "cURL Error:".curl_error($ch);
}
// 4. 释放curl句柄
curl_close($ch);
上述代码中使用到了四个函数
- curl_init() 和 curl_close() 分别是初始化cURL连接和关闭cURL连接,都比较简单。
- curl_exec() 执行cURL请求,如果没有错误发生,该函数的返回是对应URL返回的数据,以字符串表示满意;如果发生错误, 该函数返回 FALSE。需要注意的是,判断输出是否为FALSE用的是全等号,这是为了区分返回空串和出错的情况。
- cURL函数库里最重要的函数是curl_setopt(),它可以通过设定cURL函数库定义的选项来定制HTTP请求。上述代码片段中使用了三个重要的选项:
- CURLOPT_URL 指定请求的URL;
- CURLOPT_RETURNTRANSFER 设置为 true 表示稍后执行的curl_exec函数的返回是URL的返回字符串,而不是表示成功的TRUE;
- CURLLOPT_HEADER设置为 false 表示不输出HTTP头部信息。
POST示例
上面可以说是一个典型的GET请求,下面举一个POST的例子:
<?php
function doCurlPostRequest($url, $requestString, $timeout = 5)
{
if ($url == '' || $requestString == '' || $timeout <= 0) {
return false;
}
$con = curl_init((string)$url);
curl_setopt($con, CURLOPT_POST, true);
curl_setopt($con, CURLOPT_POSTFIELDS, $requestString);
curl_setopt($con, CURLOPT_RETURNTRANSFER, true);
curl_setopt($con, CURLOPT_HEADER, false);
curl_setopt($con, CURLOPT_TIMEOUT, (int)$timeout);
return curl_exec($con);
}
封装示例
一般都是在系统中封装,然后调用,上面那个GET的也可以封装一下,方便以后调用。
示例一
上面是标准的使用案例,一般的例子也举一下:
<?php
class CurlRequset
{
public static function Get($url)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
$result = curl_exec($ch);
if($result === false) {
// 记录日志
// write_logs("test", "Get is false", curl_error($ch));
}
curl_close($ch);
return $result;
}
public static function Post($url, $postData = array())
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
$result = curl_exec($ch);
if ($result === false) {
// 记录日志
// write_logs("test", "Post is false", curl_error($ch));
}
curl_close($ch);
return $result;
}
}
其中http_build_query()函数 使用给出的数组生成一个 url-encoded 请求字符串。
示例二
我们调用完接口,随时以日志形式储存接口访问相关信息。具体封装示例:
<?php
class CurlRequset
{
// 日志类型
const CATAGORY = 'curl';
public static function Get($url)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
$result = curl_exec($ch);
if ($result === false) {
// 记录日志
// write_logs("test", "Get is false", curl_error($ch));
}
$result = self::resolveResult($url, array(), $result, $ch);
curl_close($ch);
return $result;
}
public static function Post($url, $postData = array())
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
$result = curl_exec($ch);
if ($result === false) {
// 记录日志
// write_logs("test", "Post is false", curl_error($ch));
}
$result = self::resolveResult($url, $postData, $result, $ch);
curl_close($ch);
return $result;
}
private static function resolveResult($url, $params = array(), $result = '', $ch = null)
{
$return = array(
'flag' => 'success', //结果标志
'data' => $result, //返回数据
'errno' => 0, //错误码
'errmsg' => '', //错误信息
);
if (!$ch) {
return $return;
}
$errNo = curl_errno($ch);
$errMsg = curl_error($ch);
$curlInfo = curl_getinfo($ch);
$return['curlinfo'] = $curlInfo;
if ($errNo > 0) {
$return['flag'] = 'fail';
$return['errno'] = $errNo;
$return['errmsg'] = $errMsg;
} elseif (!empty($curlInfo) && intval($curlInfo['http_code']) != 200) {
$return['flag'] = 'fail';
$return['errno'] = $curlInfo['http_code'];
$return['errmsg'] = 'http response error code : ' . $curlInfo['http_code'];
}
$temp = $return;
if (strlen($return['data']) > 4000) { //返回内容长度大于4000的截取后记录日志
$temp['data'] = substr($temp['data'], 0, 500) . '......';
}
$logData = self::logFormat(array_merge(array('url' => $url, 'params' => $params), $temp));
Yii::log($logData, $return['flag'] == 'success' ? CLogger::LEVEL_INFO : CLogger::LEVEL_WARNING, self::CATAGORY);
if ($return['flag'] == 'fail') {
Y::alarm(5, '接口访问错误', "{$return['errno']}: {$return['errmsg']}");
}
return $return;
}
private static function logFormat($logData = array())
{
$result = '';
if (is_array($logData) && !empty($logData)) {
foreach ($logData as $key => $val) {
if (is_array($val)) {
$val = json_encode($val);
}
$result .= "【" . $key . " : " . $val . "】,";
}
}
return rtrim($result, ',');
}
}
:) 可以看出我用的是Yii框架,Yii::log()具体要看你的配置文件是怎么写的,关于接口我是这样写的:
'log' => array (
'class' => 'CLogRouter',
'routes' => array (
array(
'class' => 'CDbLogRoute',
'levels' => 'info,warning',
'connectionID' => 'db',
'logTableName' => '0000_api_log',
'categories' => 'curl',
),
// 以及其他
)
),
用的数据表储存,表结构:
CREATE TABLE `0000_api_log` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`level` varchar(128) DEFAULT NULL,
`category` varchar(128) DEFAULT NULL,
`logtime` int(11) DEFAULT NULL,
`message` text,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
数据一览:
既然在数据表中写了,那肯定要写查看接口访问日志的方法,这样才算是完全把握程序运行。把它当成一般的表数据访问来写就好,很快的。
示例三
另外看到一个写的比较好的curl封装方法,这里记一下,以后使用时可以参考:
<?php
function curl($url, $type = 'GET', $data = '', $Async = true)
{
$ch = curl_init();
if ($Async) {
// Asynchronous:异步请求
curl_setopt($ch, CURLOPT_NOSIGNAL, true); // 注意,毫秒超时一定要设置这个
curl_setopt($ch, CURLOPT_TIMEOUT_MS, 500); // 超时毫秒,小于500时不稳定,测试在600以上可以
}
curl_setopt($ch, CURLOPT_URL, $url);
// false 禁止 cURL 验证对等证书(peer's certificate)。要验证的交换证书可以在 CURLOPT_CAINFO 选项中设置,
// 或在 CURLOPT_CAPATH中设置证书目录。自cURL 7.10开始默认为 true。从 cURL 7.10开始默认绑定安装。
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
// 设置为 1 是检查服务器SSL证书中是否存在一个公用名(common name)。
// 译者注:公用名(Common Name)一般来讲就是填写你将要申请SSL证书的域名 (domain)或子域名(sub domain)。
// 设置成 2,会检查公用名是否存在,并且是否与提供的主机名匹配。
// 0 为不检查名称。 在生产环境中,这个值应该是 2(默认值)。值 1 的支持在 cURL 7.28.1 中被删除了。
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
// 在HTTP请求中包含一个"User-Agent: "头的字符串。
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (compatible; MSIE 5.01; Windows NT 5.0)');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($ch, CURLOPT_AUTOREFERER, 1);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $type);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$info = curl_exec($ch);
if (curl_errno($ch)) {
return 'Errno' . curl_error($ch);
}
curl_close($ch);
return $info;
}
或者如下:
<?php
/**
* CURL
* @param $url 请求地址
* @param string $type 请求类型,GET或POST
* @param string $data 请求内容
* @param int $Async 是否异步
* @return bool|mixed
*/
function curl($url, $type = 'GET', $data = '', $Async = 500)
{
$ch = curl_init();
if ($Async) {
// $Asynchronous:异步请求
curl_setopt($ch, CURLOPT_NOSIGNAL, true); // 注意,毫秒超时一定要设置这个
curl_setopt($ch, CURLOPT_TIMEOUT_MS, $Async); // 超时毫秒,根据网络情况设定
}
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (compatible; MSIE 5.01; Windows NT 5.0)');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($ch, CURLOPT_AUTOREFERER, 1);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $type);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$info = curl_exec($ch);
if (curl_errno($ch)) {
// echo 'Errno'.curl_error($ch);
return false;
}
curl_close($ch);
return $info;
}
示例四
附一个完整的类:
<?php
namespace common\widgets;
class CurlRequest
{
public static function httpGet($url, $headers = [])
{
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); // 对认证证书来源的检查
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2); // 从证书中检查SSL加密算法是否存在
curl_setopt($curl, CURLOPT_USERAGENT, @$_SERVER['HTTP_USER_AGENT']); // 模拟用户使用的浏览器
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); // 使用自动跳转
curl_setopt($curl, CURLOPT_AUTOREFERER, 1); // 自动设置Referer
curl_setopt($curl, CURLOPT_HTTPGET, 1); // 发送一个常规的Get请求
curl_setopt($curl, CURLOPT_TIMEOUT, 30); // 设置超时限制防止死循环
curl_setopt($curl, CURLOPT_HEADER, 0); // 显示返回的Header区域内容
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); // 获取的信息以文件流的形式返回
$tmpInfo = curl_exec($curl); // 执行操作
if (curl_errno($curl)) {
return false;
}
curl_close($curl);
return $tmpInfo; // 返回数据
}
public static function httpPost($url, $params = [], $headerMap = [])
{
$headers = array();
foreach ($headerMap as $key => $value) {
$headers[] = $key . ': ' . $value;
}
$curl = curl_init(); // 启动一个CURL会话
curl_setopt($curl, CURLOPT_URL, $url); // 要访问的地址
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); // 对认证证书来源的检查
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2); // 从证书中检查SSL加密算法是否存在
curl_setopt($curl, CURLOPT_USERAGENT, @$_SERVER['HTTP_USER_AGENT']); // 模拟用户使用的浏览器
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); // 使用自动跳转
curl_setopt($curl, CURLOPT_AUTOREFERER, 1); // 自动设置Referer
curl_setopt($curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
curl_setopt($curl, CURLOPT_USERPWD, "miaomiao:miao123456");
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_POST, TRUE); // 发送一个常规的Post请求
curl_setopt($curl, CURLOPT_POSTFIELDS, $params); // Post提交的数据包
curl_setopt($curl, CURLOPT_TIMEOUT, 30); // 设置超时限制防止死循环
curl_setopt($curl, CURLOPT_HEADER, 0); // 显示返回的Header区域内容
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); // 获取的信息以文件流的形式返回
$tmpInfo = curl_exec($curl); // 执行操作
if (curl_errno($curl)) {
// curl_errno($curl) 具体错误码
return false;
}
curl_close($curl); // 关键CURL会话
return $tmpInfo; // 返回数据
}
}
示例五
还有头信息拼接比较复杂的例子(如与app交互):
<?php
class CurlRequest
{
public static function Curls($type = 'GET', $url = '', $data = [])
{
if ($type == 'GET') {
//拼接url
if ($data != []) {
$url .= '?';
foreach ($data as $key => $value) {
$url .= $key . '=' . $value . '&';
}
//去掉最后一个字符
$url = substr($url, 0, -1);
}
//初始化
$ch = curl_init();
//设置选项,包括URL
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HEADER, 0);
//执行并获取HTML文档内容
$output = curl_exec($ch);
//释放curl句柄
curl_close($ch);
return $output;
}
if ($type == 'POST') {
//初始化
$ch = curl_init();
//设置选项,包括URL
curl_setopt($ch, CURLOPT_URL, $url);
// post数据
curl_setopt($ch, CURLOPT_POST, 1);
// post的变量
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
curl_close($ch);
return $output;
}
if ($type == 'APPPOST') {
$initTime = time();
$headers = [
'uid' => 0,
'accessUser' => '',
'version' => '2.1.2',
'time' => $initTime,
'role' => 0,
'userIp' => '192.168.1.1',
'requestId' => $initTime . rand(1000, 9999),
'clientType' => 0,
];
$key = 'abc12398709213asbnvhdsd1209';
ksort($headers);
foreach ($headers as $v) {
$key .= $v;
}
$headers['token'] = md5($key);
$header = [
'X-Apple-Tz: 0',
'X-Apple-Store-Front: 143444,12',
'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Encoding: gzip, deflate',
'Accept-Language: en-US,en;q=0.5',
'Cache-Control: no-cache',
'Content-Type: application/x-www-form-urlencoded; charset=utf-8',
'uid:0',
'accessUser:',
'version:2.1.2',
'time:' . $initTime,
'role: 0',
'userIp: 192.168.1.1',
'requestId: ' . $headers['requestId'],
'clientType:0',
'token:' . $headers['token']
];
//初始化
$ch = curl_init();
//设置选项,包括URL
if (ENV_CONFIG === 'dev' || ENV_CONFIG === 'pre') {
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); //强制协议为1.0
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect: ')); //头部要送出'Expect: '
curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); //强制使用IPV4协议解析域名
}
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
// post数据
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);//这个是重点。
// post的变量
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
curl_close($ch);
return $output;
}
return false;
}
}
示例六
这种写法也可以学习下:
<?php
class CurlRequest
{
public static function httpPost($url, $params = [], $headerMap = [])
{
// 写入开始日志
self::$timing = microtime(true); // 计算消耗时间
$logId = time() . mt_rand(100000, 999999);
$post_params['params'] = $params;
$post_params['headerMap'] = $headerMap;
LogService::InputApiLog($logId, 'post', $url, $post_params);
// 接口请求
$headers = array();
foreach ($headerMap as $key => $value) {
$headers[] = $key . ': ' . $value;
}
$curl = curl_init(); // 启动一个CURL会话
curl_setopt($curl, CURLOPT_URL, $url); // 要访问的地址
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($curl, CURLOPT_POSTFIELDS, $params); // Post提交的数据包
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_TIMEOUT, 30); // 设置超时限制防止死循环
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); // 获取的信息以文件流的形式返回。而不是直接打印输出
$tmpInfo = curl_exec($curl); // 执行操作
if (curl_errno($curl)) {
return false;
}
curl_close($curl); // 关闭CURL会话
// 写入结束日志
$time_escaped = number_format((microtime(true) - self::$timing) * 1000);
LogService::OutputApiLog($logId, $time_escaped, $tmpInfo);
// 返回数据
return $tmpInfo;
}
}
示例七
下面再看一个PHP请求封装的例子:
<?php
function sendRequest($url, $params = [], $method = 'POST', $options = [])
{
$method = strtoupper($method);
$protocol = substr($url, 0, 5);
$query_string = is_array($params) ? http_build_query($params) : $params;
$ch = curl_init();
$defaults = [];
if ('GET' == $method) {
$geturl = $query_string ? $url . (stripos($url, "?") !== false ? "&" : "?") . $query_string : $url;
$defaults[CURLOPT_URL] = $geturl;
} else {
$defaults[CURLOPT_URL] = $url;
if ($method == 'POST') {
$defaults[CURLOPT_POST] = 1;
} else {
$defaults[CURLOPT_CUSTOMREQUEST] = $method;
}
$defaults[CURLOPT_POSTFIELDS] = $params;
}
$defaults[CURLOPT_HEADER] = false;
$defaults[CURLOPT_USERAGENT] = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.98 Safari/537.36";
$defaults[CURLOPT_FOLLOWLOCATION] = true;
$defaults[CURLOPT_RETURNTRANSFER] = true;
$defaults[CURLOPT_CONNECTTIMEOUT] = 3;
$defaults[CURLOPT_TIMEOUT] = 30;
// disable 100-continue
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:'));
if ('https' == $protocol) {
$defaults[CURLOPT_SSL_VERIFYPEER] = false;
$defaults[CURLOPT_SSL_VERIFYHOST] = false;
}
curl_setopt_array($ch, (array)$options + $defaults);
$ret = curl_exec($ch);
$err = curl_error($ch);
if (false === $ret || !empty($err)) {
$errno = curl_errno($ch);
$info = curl_getinfo($ch);
curl_close($ch);
return [
'ret' => false,
'errno' => $errno,
'msg' => $err,
'info' => $info,
];
}
curl_close($ch);
return [
'ret' => true,
'msg' => $ret,
];
}
如一个POST方法的application/json
请求:
$result = sendRequest($dyUrl, json_encode($post_data), 'POST', [CURLINFO_CONTENT_TYPE => "application/json"]);
访问HTTPS
<?php
/**
* curl POST
*
* @param string url
* @param array 数据
* @param int 请求超时时间
* @param bool HTTPS时是否进行严格认证
* @return string
*/
function curlPost($url, $data = array(), $timeout = 30, $CA = true) {
$cacert = getcwd() . '/cacert.pem'; //CA根证书
$SSL = substr($url, 0, 8) == "https://" ? true : false;
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout-2);
if ($SSL && $CA) {
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); // 只信任CA颁布的证书
curl_setopt($ch, CURLOPT_CAINFO, $cacert); // CA根证书(用来验证的网站证书是否是CA颁布)
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); // 检查证书中是否设置域名,并且是否与提供的主机名匹配
} else if ($SSL && !$CA) {
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 信任任何证书
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 1); // 检查证书中是否设置域名
}
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:')); //避免data数据过长问题
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
//curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data)); //data with URLEncode
$ret = curl_exec($ch);
//var_dump(curl_error($ch)); //查看报错信息
curl_close($ch);
return $ret;
}
如果URL地址是https打头,那就走SSL,否则就走普通的HTTP协议。
是否走HTTPS的话就安全了吗?其实SSL也有不同的验证程度。
例如需不需要验证证书中的公用名呢?(BTW:公用名(Common Name)一般来讲就是填写你将要申请SSL证书的域名 (domain)或子域名(sub domain)。)
需要验证主机名吗?
是任何证书都信任呢还是只信任CA颁布的呢?
如果网站SSL证书买的是CA的(通常比较贵),那么访问时可以使用比较严格的认证,即:
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); // 只信任CA颁布的证书
curl_setopt($ch, CURLOPT_CAINFO, $cacert); // CA根证书(用来验证的网站证书是否是CA颁布)
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); // 检查证书中是否设置域名,并且是否与提供的主机名匹配
如果网站的证书是自己生成的,或者是网上的小机构申请的,那么访问时如果使用严格认证则不会通过,直接返回false。 (对了,返回false时可以打印curl_error($ch)查看具体错误信息。)此时可以根据情况通过降低验证程度来保证正常访问,例如:
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 信任任何证书
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 1); // 检查证书中是否设置域名(为0也可以,就是连域名存在与否都不验证了)
平时我们使用浏览器访问各个https网站时,有时会遇到证书不受信的提示,其实就是因为这些网站的证书不是正规CA机构颁布的。
市面上各种浏览器中都内置了CA根证书列表信息,访问有CA颁布证书的网站时,会根据根证书验证这些网站的证书,所以就不会有这个提示了。
关于CA根证书文件,其实就是包含了各个主要CA机构的公钥证书,用来验证网站的证书是否是这些机构颁发的。
CURLFile 类
CURLFile 应该与选项 CURLOPT_POSTFIELDS 一同使用用于上传文件。
类方法有:
- CURLFile::__construct — 创建 CURLFile 对象
- CURLFile::getFilename — 获取被上传文件的 文件名
- CURLFile::getMimeType — 获取被上传文件的 MIME 类型
- CURLFile::getPostFilename — 获取 POST 请求时使用的 文件名
- CURLFile::setMimeType — 设置被上传文件的 MIME 类型
- CURLFile::setPostFilename — 设置 POST 请求时使用的文件名
- CURLFile::__wakeup — 反序列化句柄
class CURLFile {
public $name;
public $mime;
public $postname;
/**
* Create a CURLFile object
* @link https://secure.php.net/manual/en/curlfile.construct.php
* @param string $filename <p>Path to the file which will be uploaded.</p>
* @param string $mimetype [optional] <p>Mimetype of the file.</p>
* @param string $postname [optional] <p>Name of the file.</p>
* @since 5.5.0
*/
function __construct($filename, $mimetype, $postname) {
}
/**
* Get file name
* @link https://secure.php.net/manual/en/curlfile.getfilename.php
* @return string Returns file name.
* @since 5.5.0
*/
public function getFilename() {
}
/**
* Get MIME type
* @link https://secure.php.net/manual/en/curlfile.getmimetype.php
* @return string Returns MIME type.
* @since 5.5.0
*/
public function getMimeType() {
}
/**
* Get file name for POST
* @link https://secure.php.net/manual/en/curlfile.getpostfilename.php
* @return string Returns file name for POST.
* @since 5.5.0
*/
public function getPostFilename() {
}
/**
* Set MIME type
* @link https://secure.php.net/manual/en/curlfile.setmimetype.php
* @param string $mime
* @since 5.5.0
*/
public function setMimeType($mime) {
}
/**
* Set file name for POST
* https://secure.php.net/manual/en/curlfile.setpostfilename.php
* @param string $postname
* @since 5.5.0
*/
public function setPostFilename($postname) {
}
/**
* @link https://secure.php.net/manual/en/curlfile.wakeup.php
* Unserialization handler
* @since 5.5.0
*/
public function __wakeup() {
}
}
看一个上传图片的实例:
<?php
$target = "http://youraddress.tld/example/upload.php"; // 上传地址
# http://php.net/manual/en/curlfile.construct.php // PHP 手册 函数参考 其它服务 cURL CURLFile CURLFile::__construct 文档
// 创建一个CURLFile 对象,用过程化的方法
$cfile = curl_file_create('resource/test.png', 'image/png', 'testpic'); // try adding
// 创建一个CURLFile 对象,用面向对象的方法
# $cfile = new CURLFile('resource/test.png','image/png','testpic'); // 取消注释,如果上面用过程的方法不起作用
// 组织POST数据
$imgdata = array('myimage' => $cfile);
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $target);
curl_setopt($curl, CURLOPT_USERAGENT, 'Opera/9.80 (Windows NT 6.2; Win64; x64) Presto/2.12.388 Version/12.15');
curl_setopt($curl, CURLOPT_HTTPHEADER, array('User-Agent: Opera/9.80 (Windows NT 6.2; Win64; x64) Presto/2.12.388 Version/12.15', 'Referer: http://someaddress.tld', 'Content-Type: multipart/form-data'));
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); // stop verifying certificate
curl_setopt($curl, CURLOPT_POST, true); // enable posting
curl_setopt($curl, CURLOPT_POSTFIELDS, $imgdata); // post images
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); // if any redirection after upload
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
$r = curl_exec($curl);
curl_close($curl);
再看一个用curl来模拟上传文件的实例,与html的form结果一样:
<?php
class CurlRequest
{
public static function upload($content = 'hello', $upload = '/tmp/324324s4324.png')
{
$url = "http://test.com/upload.php";
$postData['content'] = $content;
$postData['upload'] = class_exists('\CURLFile') ? new \CURLFile($upload) : '@' . $upload; // 这里看到我们使用了 CURLFile 类
return self::docurl($url, $postData);
}
public static function docurl($url, $postData)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, 0);
$result = curl_exec($ch);
curl_close($ch);
return $result;
}
}
接收方收到的数据可以打印出来看看:
var_dump($_POST);
var_dump($_FILES);
结果:
array(1) {
["content"] =>
string(5) "hello"
}
array(1) {
["upload"]=>
array(5) {
["name"]=>
string(41) "324324s4324.png"
["type"]=>
string(9) "image/png"
["tmp_name"]=>
string(14) "/tmp/phpNU9Wd0"
["error"]=>
int(0)
["size"]=>
int(297357)
}
}
参考资料
PHP 手册 函数参考 其它服务 Client URL 库 https://www.php.net/manual/zh/book.curl.php
PHP 手册 函数参考 其它服务 cURL cURL 函数 curl_setopt http://php.net/manual/zh/function.curl-setopt.php
http://www.devdo.net/php-curl.html
http://www.jb51.net/article/34745.htm
Curl 错误号 https://blog.csdn.net/realzhi/article/details/17068085
Postman高级应用:生成cURL和多语言代码 https://blog.csdn.net/qq598535550/article/details/80889843
考虑 PHP 5.0~5.6 各版本兼容性的 cURL 文件上传 https://segmentfault.com/a/1190000000725185
php使用curl访问https示例代码 https://www.php.cn/php-weizijiaocheng-373018.html