php使用curl发送get,post请求,上传,下载,解析响应cookie,代理访问,http认证,cookie保持会话状态

来源:赵克立博客 分类: PHP 标签:PHP发布时间:2016-11-09 12:10:51最后更新:2021-01-04 21:54:46浏览:1820
版权声明:
本文为博主原创文章,转载请声明原文链接...谢谢。o_0。
更新时间:
2021-01-04 21:54:46
温馨提示:
学无止境,技术类文章有它的时效性,请留意文章更新时间,如发现内容有误请留言指出,防止别人"踩坑",我会及时更新文章

HTTP类

注意:环境为php7.4

使用php请求网页内容的方法有很多,curl是很强大的一种,可以上传、下载、实现代理、http认证等功能,下面使用一个类简单封装了curl的调用,保存下面代码为Http.php


每次请求后只有post的数据会被清空,其它配置项会保留,请根据自己的情况在每次请求前重新创建一个请求客户端还是复用同一个客户端


每次请求结果为如下结构

2101011609470554595468.png

  • response_code http响应码,一般情况下200为成功

  • error_msg curl 错误信息 错误信息

  • error_code curl 错误码 0为成功

  • file_save_path 如果为下载文件时,此值为下载的路径

  • raw_response_headers 服务器返回的响应头信息,格式为字符串

  • response_headers 服务器返回的响应头信息,格式为键值数组,键为小写

  • response_content 服务器响应内容,当操作的下载文件时成功此值为1,失败此值为空字符串

  • request_headers 当前请求所带的请求头信息,格式为键值数组,键为小写

  • resepon_cookie 如果服务器返回有新的cookies,则会填充到此数组中

  • curl_info 为当前curl请求结果信息


<?php

namespace ank;


use CURLFile;
use Closure;
use ReflectionMethod;
use ReflectionException;

/**
 * curl请求封装类,
 * get,post,上传,下载,代理,http认证,解析响应cookie,保持cookie会话
 * @link    https://www.zhaokeli.com/article/8016.html
 * Class Http
 * @package ank
 */
class Http
{
    /**
     * 当前请求url
     * @var string
     */
    protected string $url = '';

    /**
     * 是否开启ssl检查
     * @var bool
     */
    protected bool $sslOn = false;

    /**
     * 最大重定向次数
     * @var int
     */
    protected int $maxRedirs = 10;

    /**
     * 是否不要响应内容,true:只返回响应头,false:响应头和内容都返回
     * @var bool
     */
    protected bool $nobody = false;

    /**
     * 代理ip
     * @var string
     */
    protected string $proxyIp = '';

    /**
     * 代理端口
     * @var int
     */
    protected int $proxyPort = 80;

    /**
     * 代理用户名
     * @var string
     */
    protected string $proxyUsername = '';

    /**
     * 代理密码
     * @var string
     */
    protected string $proxyPassword = '';

    /**
     * http认证用户名
     * @var string
     */
    protected string $authUsername = '';


    /**
     * http认证密码
     * @var string
     */
    protected string $authPassword = '';

    /**
     * 请求超时
     * @var int
     */
    protected int $timeout = 60;

    /**
     * 连接超时
     * @var int
     */
    protected int $connectionTimeout = 10;

    /**
     * 请求头
     * @var array
     */
    protected array $requestHeaders = [];

    /**
     * post数据
     * @var mixed
     */
    protected array $postData = [];

    /**
     * 是否跟踪重定向
     * @var bool
     */
    protected bool $isFollowRedirect = true;

    /**
     * cookie保存路径
     * @var string
     */
    protected string $cookiePath = '';

    /**
     * 请求结果保存路径
     * @var string
     */
    protected string $fileSavePath = '';

    /**
     * 文件上传对象
     * @var CURLFile|null
     */
    protected ?CURLFile $fileUploadObj = null;

    /**
     * 是否post请求
     * @var bool
     */
    protected bool $isPost = false;

    /**
     * 是否开启gzip
     * @var bool
     */
    protected bool $gzip = false;


    /**
     * 文件流句柄
     * @var resource
     */
    private $fileHandler = null;


    /**
     * curl配置项
     * @var array
     */
    private array $curlOpts
        = [

        ];


    /**
     * curl对象
     * @var resource
     */
    protected $curlObj = null;

    /**
     * 响应头
     * @var string
     */
    protected string $responseHeaders = '';

    /**
     * 异步并发请求队列
     * @var array
     */
    protected static array $requestQueue = [];

    /**
     * 正在执行中的队列
     * @var array
     */
    protected static array $runningQueue = [];
    /**
     * 队列最大并发数
     * @var int
     */
    protected static int $maxQueue = 10;

    /**
     * 设置请求url
     * @param string $url
     * @return Http
     */
    public function setUrl(string $url): Http
    {
        $this->url = $url;
        return $this;
    }

    /**
     * 是否只要响应头
     * @param bool $isOnlyResponseHeaders
     * @return Http
     */
    public function setOnlyResponseHeaders($isOnlyResponseHeaders = false): Http
    {
        $this->nobody = $isOnlyResponseHeaders;
        return $this;
    }

    /**
     * 设置ssl检查
     * @param bool $isSSL
     * @return Http
     */
    public function setSSLVerifypeer(bool $isSSL = false): Http
    {
        $this->sslOn = $isSSL;
        return $this;
    }

    /**
     * 代理ip
     * @param string $ip
     * @return Http
     */
    public function setProxyIp(string $ip): Http
    {
        $this->proxyIp = $ip;
        return $this;
    }

    /**
     * 代理端口
     * @param int $port
     * @return Http
     */
    public function setProxyPort(int $port = 80): Http
    {
        $this->proxyPort = $port;
        return $this;
    }

    /**
     * 代理用户名
     * @param string $username
     * @return Http
     */
    public function setProxyUsername(string $username): Http
    {
        $this->proxyUsername = $username;
        return $this;
    }

    /**
     * 是否开启gzip
     * @param bool $isOn
     */
    public function setGzip($isOn = false)
    {
        $this->gzip = $isOn;
    }

    /**
     * 代理密码
     * @param string $password
     * @return Http
     */
    public function setProxyPassword(string $password): Http
    {
        $this->proxyPassword = $password;
        return $this;
    }

    /**
     * http认证用户名
     * @param string $username
     * @return Http
     */
    public function setAuthUsername(string $username): Http
    {
        $this->authUsername = $username;
        return $this;
    }

    /**
     * http认证密码
     * @param string $password
     * @return Http
     */
    public function setAuthPassword(string $password): Http
    {
        $this->authPassword = $password;
        return $this;
    }

    /**
     * 设置请求超时,默认60秒
     * @param int $time
     * @return Http
     */
    public function setTimeout(int $time = 60): Http
    {
        $this->timeout = $time;
        return $this;
    }

    /**
     * 设置连接超时,默认10秒
     * @param int $time
     * @return Http
     */
    public function setConnectTimeout(int $time = 10): Http
    {
        $this->connectionTimeout = $time;
        return $this;
    }

    /**
     * 解析头信息
     * @param      $headers
     * @param bool $isParseKey
     * @return array
     */
    protected function parseHeaders($headers, $isParseKey = true): array
    {
        if (is_string($headers)) {
            $headers = trim($headers);
            $headers = preg_split('/\r?\n/', $headers);
        }
        if (isset($headers[0]) && $isParseKey) {
            $newData = [];
            foreach ($headers as $header) {
                if (!$header) {
                    continue;
                }
                $pos = strpos($header, ':');
                // $isFirst = false;
                //兼容下http2的 :path这种头信息
                // if ($pos === 0) {
                //     $isFirst = true;
                //     $header  = ltrim($header, ':');
                //     $pos     = strpos($header, ':');
                // }
                $name  = strtolower(trim(substr($header, 0, $pos)));
                $value = trim(substr($header, $pos + 1));
                if (!$name) {
                    continue;
                }
                // $name           = ($isFirst ? ':' : '') . $name;
                $newData[$name] = $value;
            }
            $headers = $newData;
        }
        return $headers;
    }

    /**
     * 设置请求头,会重置之前的头信息
     * @param string|array
     * @return Http
     */
    public function setRequestHeaders($headers): Http
    {
        $this->requestHeaders = $this->parseHeaders($headers);
        return $this;
    }

    /**
     * 设置cookie文件路径,会在请求和响应时自动保存请求中的cookie
     * @param string $cookiePath 文件绝对路径
     * @return Http
     */
    public function setCookiePath(string $cookiePath): Http
    {
        $this->cookiePath = $cookiePath;
        return $this;
    }

    /**
     * 清空会话cookie文件
     */
    public function clearCookies()
    {
        is_file($this->cookiePath) && file_put_contents($this->cookiePath, '');
    }

    /**
     * 添加cookie,可多次调用
     * @param string $name
     * @param string $value
     * @return Http
     */
    public function addCookie(string $name, string $value): Http
    {
        if (isset($this->requestHeaders['cookie']) && $this->requestHeaders['cookie']) {
            $this->requestHeaders['cookie'] .= '; ' . trim($name) . '=' . trim($value);
        }
        else {
            $this->requestHeaders['cookie'] = trim($name) . '=' . trim($value);
        }
        return $this;
    }

    /**
     * 添加cookie数组
     * @param array $cookies
     * @return $this
     */
    public function addCookieArray(array $cookies): Http
    {
        foreach ($cookies as $name => $value) {
            $this->addCookie($name, $value);
        }
        return $this;
    }

    /**
     * 重置请求头中的cookie值
     * @param string $cookieString
     * @return $this
     */
    public function setRequestCookies(string $cookieString): Http
    {
        $this->requestHeaders['cookie'] = $cookieString;
        return $this;
    }

    /**
     * 返回请求头
     * @return array
     */
    public function getHeaders(): array
    {
        return $this->requestHeaders;
    }

    /**
     * 返回当前请求头和cookie文件中的cookie
     */
    public function getCookies(): array
    {
        $cookies = [];
        foreach ($this->requestHeaders as $name => $value) {
            if ($name === 'cookie') {
                $arr = explode(';', $value);
                foreach ($arr as $val) {
                    $pos                  = strpos($val, '=');
                    $cookieName           = trim(substr($val, 0, $pos));
                    $cookieValue          = trim(substr($val, $pos + 1));
                    $cookies[$cookieName] = $cookieValue;
                }
            }
        }
        if ($this->cookiePath || is_file($this->cookiePath)) {
            $cookieString = file_get_contents($this->cookiePath);
            $reg          = '@^[^\s\n#]+\s+[^\s\n]+\s+[^\s\n]+\s+[^\s\n]+\s+[^\s\n]+\s+(?<name>[^\s\n]+)\s+(?<value>[^\s\n]+)\r?\n?$@m';
            if (preg_match_all($reg, $cookieString, $mats)) {
                foreach ($mats['name'] as $key => $name) {
                    $cookies[trim($name)] = trim($mats['value'][$key]);
                }
            }
        }
        return $cookies;
    }

    /**
     * 添加请求头
     * @param string $name
     * @param string $value
     * @return Http
     */
    public function addHeader(string $name, string $value): Http
    {
        $this->requestHeaders[strtolower(trim($name))] = trim($value);
        return $this;
    }

    /**
     * 添加请求头数组
     * @param array $headers
     * @return Http
     */
    public function addHeaderArray(array $headers): Http
    {
        foreach ($headers as $name => $value) {
            $this->addHeader($name, $value);
        }
        return $this;
    }

    /**
     * 设置是否重定向
     * @param bool $followRedirect
     * @return Http
     */
    public function setIsFollowRedirect($followRedirect = true): Http
    {
        $this->isFollowRedirect = $followRedirect;
        return $this;
    }

    /**
     * 设置最大重定向次数,默认10
     * @param int $max
     * @return Http
     */
    public function setMaxRedirs(int $max = 10): Http
    {
        $this->maxRedirs = $max;
        return $this;
    }

    /**
     * 设置cURL传输的选项,此处设置会在执行前设置,会覆盖其它地方的值
     * @link https://www.php.net/manual/en/function.curl-setopt.php
     * @param int $option
     * @param     $value
     * @return Http
     */
    public function setOpt(int $option, $value): Http
    {
        $this->curlOpts[$option] = $value;
        return $this;
    }

    /**
     * 添加上传的文件
     * @param string $fieldName 表单字段名字
     * @param string $filePath  文件绝对路径
     * @return Http
     */
    public function addFile(string $fieldName, string $filePath): Http
    {
        if (is_file($filePath)) {
            $this->fileUploadObj        = new CURLFile($filePath, mime_content_type($filePath), basename($filePath));
            $this->postData[$fieldName] = $this->fileUploadObj;
        }
        return $this;
    }

    /**
     * 构建curl
     */
    protected function buildCurl()
    {
        $this->curlObj = curl_init();
        $options       = [
            CURLOPT_URL            => $this->url,
            CURLOPT_SSL_VERIFYPEER => $this->sslOn,
            CURLOPT_HTTP_VERSION   => CURL_HTTP_VERSION_2_0,
            CURLOPT_ENCODING       => $this->gzip,
            CURLOPT_CONNECTTIMEOUT => $this->connectionTimeout,
            CURLOPT_TIMEOUT        => $this->timeout,
            // 自动递归的抓取重定向后的页面,0时禁止重定向
            CURLOPT_FOLLOWLOCATION => $this->isFollowRedirect,
            CURLOPT_MAXREDIRS      => 10,
            //不取正文内容,在只需要响应头的时候开启这个,加快响应速度
            CURLOPT_NOBODY         => $this->nobody,
            //返回内容不直接输出到浏览器,保存到变量中
            CURLOPT_RETURNTRANSFER => 1,
            // 响应头回调地址
            // CURLOPT_HEADERFUNCTION => '\ank\Http::callbackHeader',
            CURLOPT_HEADERFUNCTION => function ($ch, $header) {
                return $this->callbackHeader($ch, $header);
            },

        ];
        //是否跳过ssl检查项。
        $options[CURLOPT_POST] = $this->isPost || $this->fileUploadObj;
        $this->postData && ($options[CURLOPT_POSTFIELDS] = $this->postData);
        $urlInfo                      = parse_url($this->url);
        $this->requestHeaders['host'] = $urlInfo['host'];
        //设置请求头
        if ($this->requestHeaders) {
            $headers = [];
            foreach ($this->requestHeaders as $name => $value) {
                $headers[] = $name . ':' . $value;
            }
            $options[CURLOPT_HTTPHEADER] = $headers;
        }

        //设置http认证用户密码
        $this->authUsername && ($options[CURLOPT_USERNAME] = $this->authUsername);
        $this->authPassword && ($options[CURLOPT_USERNAME] = $this->authPassword);
        if ($this->cookiePath) {
            file_exists($this->cookiePath) or file_put_contents($this->cookiePath, '');
            $this->appendCookie();
            // 设置将返回的cookie保存到$cookie所指文件
            $options[CURLOPT_COOKIEJAR] = $this->cookiePath;
            // 设置从$cookie所指文件中读取cookie信息以发送
            $options[CURLOPT_COOKIEFILE] = $this->cookiePath;
        }
        if ($this->proxyIp) {
            //代理设置 账号密码
            $options[CURLOPT_PROXY] = $this->proxyIp . ':' . $this->proxyPort;
            $this->proxyUsername && ($options[CURLOPT_PROXYUSERPWD] = $this->proxyUsername . ':' . $this->proxyPassword);
        }

        //如果设置了保存路径则保存文件到本地
        if ($this->fileSavePath || $this->fileUploadObj !== null) {
            if ($this->fileSavePath) {
                $dirPath = dirname($this->fileSavePath);
                is_dir($dirPath) or @mkdir($dirPath, 755, true);
                //打开文件描述符
                $this->fileHandler     = fopen($this->fileSavePath, 'w+');
                $options[CURLOPT_FILE] = $this->fileHandler;
            }
            if (PHP_SAPI === 'cli') {
                // 进度条缓冲区大小
                $options[CURLOPT_BUFFERSIZE] = 10;
                // 开启进度条
                $options[CURLOPT_NOPROGRESS] = 0;
                // 进度条的触发函数
                // $options[CURLOPT_PROGRESSFUNCTION] = '\ank\Http::callbackProgress';
                $options[CURLOPT_PROGRESSFUNCTION] = function ($ch, int $countDownloadSize, int $currentDownloadSize, int $countUploadSize, int $currentUploadSize) {
                    return $this->callbackProgress($ch, $countDownloadSize, $currentDownloadSize, $countUploadSize, $currentUploadSize);
                };
            }
        }
        //合并额外的配置项并设置到curl
        curl_setopt_array($this->curlObj, $this->curlOpts + $options);
        return $this->curlObj;
    }

    /**
     * 创建一个请求并添加到异步队列中
     * @param Closure|null $callback
     */
    protected function sendRequestAsync(Closure $callback = null)
    {
        $this->buildCurl();
        $obj                                        = $this;
        self::$requestQueue[(string)$this->curlObj] = [
            'ch'       => $this->curlObj,
            'callback' => $callback,
            'obj'      => $obj,
        ];
    }

    /**
     * 并发请求,同步处理
     * @return float 耗时s
     */
    public static function wait(): float
    {
        $t1 = microtime(true);
        // 创建批处理cURL句柄
        $mh = curl_multi_init();
        foreach (self::$requestQueue as $info) {
            curl_multi_add_handle($mh, $info['ch']);
        }
        $active = null;
        // 执行批处理句柄
        do {
            $status = curl_multi_exec($mh, $active);
        } while ($status == CURLM_CALL_MULTI_PERFORM);

        while ($active && $status == CURLM_OK) {
            // 持续查询状态并不利于处理任务,每50ms检查一次,此时释放CPU,降低机器负载
            usleep(50000);
            // 如果批处理句柄OK,重复检查操作状态直至OK。select返回值异常时为-1,正常为1(因为只有1个批处理句柄)
            if (curl_multi_select($mh) != -1) {
                do {
                    $status = curl_multi_exec($mh, $active);
                } while ($status == CURLM_CALL_MULTI_PERFORM);
            }
        }

        // 获取返回结果
        foreach (self::$requestQueue as $key => $info) {
            $content = curl_multi_getcontent($info['ch']);
            try {
                $reflectMethod = new ReflectionMethod($info['obj'], 'parseResponseData');
                $reflectMethod->setAccessible(true);
                $con = $reflectMethod->invokeArgs($info['obj'], [$content]);
                if ($info['callback'] instanceof Closure) {
                    call_user_func_array($info['callback'], [$info['ch'], $con]);
                }
            } catch (ReflectionException $e) {
            }
            unset(self::$requestQueue[$key]);
            // 移除单个curl句柄
            curl_multi_remove_handle($mh, $info['ch']);
        }
        curl_multi_close($mh);
        return (float)(microtime(true) - $t1);
    }

    /**
     * 添加队列任务
     * @param $mh
     */
    protected static function addQueueTask($mh)
    {
        foreach (self::$requestQueue as $key => $info) {
            if (self::$maxQueue == count(self::$runningQueue)) {
                break;
            }
            curl_multi_add_handle($mh, $info['ch']);
            self::$runningQueue[$key] = $info;
            unset(self::$requestQueue[$key]);
        }
    }

    /**
     * 并发请求,异步处理
     * @param int $maxQueue 队列并发最大数量
     * @return float 耗时s
     */
    public static function waitAsync(int $maxQueue = 10): float
    {
        $t1             = microtime(true);
        self::$maxQueue = $maxQueue;
        $isRunning      = true;
        // 创建批处理cURL句柄
        $mh = curl_multi_init();
        self::addQueueTask($mh);
        do {
            $active = null;
            // 执行批处理句柄
            do {
                $status = curl_multi_exec($mh, $active);
            } while ($status == CURLM_CALL_MULTI_PERFORM);

            //有连接返回立即处理,并加入新的连接
            while ($done = curl_multi_info_read($mh)) {
                //获取返回的信息
                // $info    = curl_getinfo($done['handle']);
                $content = curl_multi_getcontent($done['handle']);
                //发送返回信息到回调函数
                $key  = (string)$done['handle'];
                $info = self::$runningQueue[$key];
                try {
                    $reflectMethod = new ReflectionMethod($info['obj'], 'parseResponseData');
                    $reflectMethod->setAccessible(true);
                    $con = $reflectMethod->invokeArgs($info['obj'], [$content]);
                    if ($info['callback'] instanceof Closure) {
                        call_user_func_array($info['callback'], [$done['handle'], $con]);
                    }
                } catch (ReflectionException $e) {
                } finally {
                    // 移除单个curl句柄
                    curl_multi_remove_handle($mh, $info['ch']);
                    curl_close($info['ch']);
                    unset(self::$runningQueue[$key]);
                    self::addQueueTask($mh);
                    if (!self::$runningQueue) {
                        $isRunning = false;
                    }
                }

            }
            // Block for data in / output; error handling is done by curl_multi_exec
            $active && curl_multi_select($mh, 0.5);
        } while ($isRunning);
        curl_multi_close($mh);
        return (float)(microtime(true) - $t1);
    }

    /**
     * 执行请求
     * 返回值中responseCode为200,curlErrorCode为0,则为成功
     */
    protected function sendRequest(): array
    {
        if (!$this->url) {
            return [
                'responseCode'  => 0,
                'curlErrorCode' => 1,
                'curlErrorMsg'  => 'url is empty',
            ];
        }
        try {
            $this->buildCurl();
            $data = curl_exec($this->curlObj);
            $data = $this->parseResponseData($data ?: '');
        } catch (\Exception $e) {
            $data['response_code'] = 0;
            $data['error_msg']     = $e->getMessage();
        } finally {
            $this->fileHandler && fclose($this->fileHandler);
            curl_close($this->curlObj);
            $this->curlObj         = null;
            $this->fileUploadObj   = null;
            $this->fileSavePath    = '';
            $this->postData        = [];
            $this->url             = '';
            $this->isPost          = false;
            $this->responseHeaders = '';
        }
        return $data;
    }

    /**
     * 解析出响应的数据
     * @param string $data
     * @return array
     */
    protected function parseResponseData(string $data = ''): array
    {
        $info    = curl_getinfo($this->curlObj);
        $data    = [
            'response_code'        => $info['http_code'],
            'raw_response_headers' => $this->responseHeaders,
            'response_headers'     => $this->parseHeaders($this->responseHeaders),
            'response_content'     => $data,
            'request_headers'      => $this->requestHeaders,
            'file_save_path'       => $this->fileSavePath,
            //请求出错,返回错误信息
            'error_msg'            => curl_error($this->curlObj),
            'error_code'           => curl_errno($this->curlObj),
            'curl_info'            => $info,
        ];
        $cookie  = [];
        $urlInfo = parse_url($this->url);
        if (preg_match_all(' /Set-Cookie:\s(?<name>.+?)=(?<value>.+?);.*?\r/i', $this->responseHeaders, $mat)) {
            foreach ($mat[0] as $key => $value) {
                $cookie[$mat['name'][$key]] = [
                    'value'   => trim($mat['value'][$key]),
                    'expires' => $this->parseCookieField($value, 'expires', 0),
                    'maxAge'  => $this->parseCookieField($value, 'max-age', 0),
                    'path'    => $this->parseCookieField($value, 'path', '/'),
                    'domain'  => $this->parseCookieField($value, 'domain', $urlInfo['host']),
                ];
            }
        }
        $data['response_cookie'] = $cookie;
        return $data;
    }

    /**
     * 响应头回调
     * @param $ch
     * @param $headerString
     * @return int
     */
    private function callbackHeader($ch, $headerString): int
    {
        $this->responseHeaders .= $headerString;
        return strlen($headerString);
    }

    /**
     * 进度条下载.
     * @param     $ch
     * @param int $countDownloadSize   总下载量
     * @param int $currentDownloadSize 当前下载量
     * @param int $countUploadSize     总上传量
     * @param int $currentUploadSize   当前上传量
     * @return bool
     */
    private function callbackProgress($ch, int $countDownloadSize, int $currentDownloadSize, int $countUploadSize, int $currentUploadSize): bool
    {
        // echo PHP_EOL, $countDownloadSize, ' ', $currentDownloadSize, PHP_EOL;
        $title   = 'Download:';
        $total   = 0;
        $current = 0;
        if ($countDownloadSize > 0) {
            $total   = $countDownloadSize;
            $current = $currentDownloadSize;
        }
        if ($countUploadSize > 0) {
            $title   = 'Upload:';
            $total   = $countUploadSize;
            $current = $currentUploadSize;
        }
        // 等于 0 的时候,应该是预读资源不等于0的时候即开始下载
        // 这里的每一个判断都是坑,多试试就知道了
        if (0 === $total) {
            return false;
        }
        // 有时候会下载两次,第一次很小,应该是重定向下载
        // if ($total > $current) {
        // $this->downloaded = false;
        // 继续显示进度条
        // return false;
        // }
        // 已经下载完成还会再发三次请求
        // elseif ($this->downloaded) {
        //     return false;
        // }
        // 两边相等下载完成并不一定结束,
        // elseif ($current === $total) {
        //     return false;
        // }

        $this->draw($current, $total, $title);
        // if ($current < $total) {
        return false;
        // }
    }

    /**
     * @param int    $current
     * @param int    $total
     * @param string $title
     */
    protected function draw(int $current = 0, int $total = 0, $title = '')
    {
        $step       = $current / $total;
        $barLength  = 40;//进度条长度,不能太大
        $fullValue  = floor($step * $barLength);
        $emptyValue = $barLength - $fullValue;
        $prc        = number_format($step * 100, 2, '.', ' ');
        $colorStart = "\033[32m";
        $colorEnd   = "\033[39m";
        $charEmpty  = '░';
        $charFull   = '▓';
        //进度条前面标题
        $title = ((strlen($title) > 1) ? "{$title} " : "");

        $current = number_format($current / 1024, 2, '.', '');
        $total   = number_format($total / 1024, 2, '.', '');
        $bar     = sprintf("%4\$s%5\$s %3\$.1f%% [%1\$d/%2\$dK]", $current, $total, $prc, str_repeat($charFull, $fullValue), str_repeat($charEmpty, $emptyValue));
        print sprintf("\r%s%s%s%s", $colorStart, $title, $bar, $colorEnd);
    }

    /**
     * 从响应cookie中解析出字段
     * @param        $cookieString
     * @param        $name
     * @param string $default
     * @return string
     */
    protected function parseCookieField(string $cookieString, string $name, $default = ''): string
    {
        if (preg_match(' /Set-Cookie:.*?' . preg_quote($name) . '=(?<value>.+?)(\r|;)/i', $cookieString, $mat)) {
            return $mat['value'];
        }
        return $default;
    }

    /**
     * 追加cookie
     */
    protected function appendCookie()
    {
        $urlInfo      = parse_url($this->url);
        $domain       = $urlInfo['host'];
        $cookieString = '';
        foreach ($this->requestHeaders as $name => $value) {
            if (strtolower($name) == 'cookie') {
                $cookieString = trim($value);
            }
        }
        if (!$cookieString) {
            return;
        }
        $cookies      = explode(';', $cookieString);
        $cookieCon    = file_get_contents($this->cookiePath);
        $appendCookie = '';
        foreach ($cookies as $cookie) {
            $pos   = strpos($cookie, '=');
            $name  = trim(substr($cookie, 0, $pos));
            $value = trim(substr($cookie, $pos + 1));
            $reg   = '@^[^\s\n]+\s+[^\s\n]+\s+[^\s\n]+\s+[^\s\n]+\s+[^\s\n]+\s+' . preg_quote($name) . '\s+[^\s\n]+\r?\n?$@m';
            if (!preg_match($reg, $cookieCon)) {
                $isSubdomains = substr($domain, 0, 1) === '.' ? 'true' : 'false';
                $appendCookie .= $domain . '   ' . $isSubdomains . '  /  false  0  ' . $name . '  ' . $value . PHP_EOL;
            }
        }
        $this->cookiePath && file_put_contents($this->cookiePath, $appendCookie, FILE_APPEND);
    }

    /**
     * 创建一个新的请求
     */
    public static function build(): Http
    {
        return new static();
    }

    /**
     * 发送post请求
     * @param string|array
     * @return array
     */
    public function post($postData = []): array
    {
        $temData = $postData;
        if (is_string($temData)) {
            parse_str($postData, $temData);
        }
        $this->postData = array_merge($this->postData, $temData);
        $this->isPost   = true;
        return $this->sendRequest();
    }

    /**
     * 异步post请求(注意:不能复用客户端)
     * @param mixed        $postData
     * @param Closure|null $callback
     */
    public function postAsync($postData = [], Closure $callback = null)
    {
        $temData = $postData;
        if (is_string($temData)) {
            parse_str($postData, $temData);
        }
        $this->postData = array_merge($this->postData, $temData);
        $this->isPost   = true;
        $this->sendRequestAsync($callback);
    }

    /**
     * 发送get请求
     */
    public function get(): array
    {
        $this->isPost = false;
        return $this->sendRequest();
    }

    /**
     * 发送get请求(注意:不能复用客户端)
     * @param Closure|null $callback
     */
    public function getAsync(Closure $callback = null)
    {
        $this->isPost = false;
        $this->sendRequestAsync($callback);
    }

    /**
     * 下载文件
     * @param string $saveFilePath
     * @return array
     */
    public function download(string $saveFilePath): array
    {
        $this->fileSavePath = $saveFilePath;
        return $this->sendRequest();
    }

    /**
     * 上传文件
     * @return array
     */
    public function upload(): array
    {
        $this->isPost = true;
        return $this->sendRequest();
    }
}

GET方法

$data = Http::build()
            ->setRequestHeaders($this->headers)
            ->setUrl('https://www.baidu.com/'
            ->get();

POST方法

$postData=['name'=>'keli','cate_id'=>1];
//或
$postData='name=keli&cate_id=1';
$data = Http::build()
            ->setRequestHeaders($this->headers)
            ->setUrl('https://www.baidu.com/'
            ->post($postData);


下载文件

下载文件后返回值里响应内容降会为1,直接的响应内容的文件路径所在位置,下载文件如果是命令行模式则会有一个下载进程条显示。

$data = Http::build()
            ->setRequestHeaders($this->headers)
            ->setTimeout(1200)
            ->setUrl('https://www.baidu.com/s?wd=%E5%A5%B3%E8%A3%85')
            ->download('E:/baidusearch.html');

上传文件

上传文件会强制使用post请求,同样如果是cli模式则会显示一个上传进度条

$data = Http::build()
            ->setCookiePath('E:/cookie.txt')
            ->addFile('file','E:/upload.zip')
            ->setUrl('https://www.zhaokeli.com/'
            ->upload();


保持会话

设置保存cookie的文件路径,设置后服务端返回的cookie会保存在此路径,如果请求头里设置了cookie值,在请求完成后如果cookie文件中不存在此cookie名字时,则会写入cookie文件,时间为永不过期

$data = Http::build()
            ->setCookiePath('E:/cookie.txt')
            ->setUrl('https://www.zhaokeli.com/')
            ->get();

代理请求

http代理请求,可设置用户名和密码

$data = Http::build()
            ->setProxyIp(self::$proxyIp)
            ->setProxyPort(self::$proxyPort)
            ->setTimeout(1200)
            ->setProxyUsername(self::$proxyUsername)
            ->setProxyPassword(self::$proxyPassword)
            ->setUrl('https://www.google.com/')
            ->get();

CURL配置项

setOpt

因为curl功能设置项比较多,类中并没有使用全部配置项设置,此方法可设置curl原生配置项,此处设置会覆盖上面使用setXXXX成员函数设置的配置项

其它方法

addHeader

添加一个请求头信息,如果添加的是cookie,则如果设置过cookie,会自动追加到原来的请求cookie中

setRequestHeaders

设置请求头,可接收字段串或数组,会自动解析,格式为数组时请保证全部是键值数组或数字索引数组,请求头名字会转为小写,此项会重置请求头信息,注意和addHeader的使用顺序

$headers= <<<eot
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Referer: https://www.baidu.com
eot;
// 或
$headers=[
    'User-Agent'=> 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36'
    'Referer'=> 'https://www.baidu.com'
];

getHeaders

返回设置的请求头信息

addCookie

添加cookie,同样如果请求头存在cookie键则会追加到原有cookie后面

setRequestCookies

设置请求头中的cookie值,此操作会重置cookie,请注意addCookie的使用顺序

getCookies

返回所有cookie,先解析请求头中的cookie,如果设置的有cookie文件路径,则会解析cookie文件中的值来替换请求头中同名的cookie值,最后返回所有cookie键值数组


clearCookies

如果存在的话,清理保存的cookie文件


setOnlyResponseHeaders

是否只要响应头,设置此项后只返回响应头,不要响应内容,会加速请求





注意

curl需要特别注意的地方一定要看,curl默认post的提交数据的方式是根据post的数据决定的,如果post的数据是字符串则curl默认使用下面的方法提交

Content-Type:application/x-www-form-urlencoded

如果post的数据是是k=>v的键值数组,且后面的值中存在有数组类型的话,则请求的类型会转为下面类型发送

multipart/form-data;

如果post的数据值有数组但是请求头中又加了下面提交方式的话,就会报一个异常错误,服务器端会收不到数据,如下图所示

content-type:application/x-www-form-urlencode

下图中是服务器var_dump($_POST);输出的结果

blob.png


微信号:kelicom QQ群:215861553 紧急求助须知
Win32/PHP/JS/Android/Python