PHP cURL详解


PHP cURL详解


引言

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请求的发送一般有以下几个步骤:

  1. curl_init()初始化连接句柄;
  2. curl_setopt()设置cURL选项;
  3. curl_exec()执行并获取结果;
  4. 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&para2=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_errno函数

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


返回