设为首页 收藏本站
查看: 548|回复: 0

[经验分享] php使用curl下载指定大小的文件

[复制链接]

尚未签到

发表于 2018-12-13 08:42:09 | 显示全部楼层 |阅读模式
  php中使用基于libcurl的curl函数,可以对目标url发起http请求并获取返回的响应内容。通常的请求方式类似如下的代码:
public function callFunction($url, $postData, $method, header='')
{
    $maxRetryTimes = 3;
    $curl = curl_init();
    /******初始化请求参数start******/
    if(strtoupper($method) !== 'GET' && $postData){
        curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($postData));
    }elseif (strtoupper($method) === 'GET' && $postData){
        $url .= '?'. http_build_query($postData);
    }
    /******初始化请求参数end******/
    curl_setopt_array($curl, array(
        CURLOPT_URL => $url,
        CURLOPT_TIMEOUT => 10,
        CURLOPT_NOBODY => 0,
        CURLOPT_RETURNTRANSFER => 1
    ));
    if(method == 'POST'){
        curl_setopt($curl, CURLOPT_POST, true);
    }
    if(false == empty()){
        curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
    }
    $response = false;
    while(($response === false) && (--$maxRetryTimes > 0)){
        $response = trim(curl_exec($curl));
    }
    return $response;
}  上面代码中的这个$response是curl发起的这次http请求从$url获取到的数据,如果没有在$header中通过range来指定要下载的大小,无论这个资源多大,那么都要请求完整的并返回的是这个URI的完整内容。通常只用curl来请求求一些接口或者远程调用一个函数获取数据,,所以这个场景下CURLOPT_TIMEOUT这个参数很重要。
  对于curl的使用场景不止访问数据接口,还要对任意的url资源进行检测是否能提供正确的http服务。当用户填入的url是一个资源文件时,例如一个pdf或者ppt之类的,这时候如果网络状况较差的情况下用curl请求较大的资源,将不可避免的出现超时或者耗费更多的网络资源。之前的策略是完全下载(curl会下载存储在内存中),请求完后检查内容大小,当超过目标值就把这个监控的任务暂停。这样事发后限制其实治标不治本,终于客户提出了新的需求,不能停止任务只下载指定大小的文件并返回md5值由客户去校验正确性。
  经过了一些尝试,解决了这个问题,记录过程如下文。
  1、尝试使用 CURLOPT_MAXFILESIZE。
  对php和libcurl的版本有版本要求,完全的事前处理,当发现目标大于设置时,直接返回了超过大小限制的错误而不去下载目标了,不符合要求。
  2、使用curl下载过程的回调函数。
  参考http://php.net/manual/en/function.curl-setopt-array.php,最终使用了CURLOPT_WRITEFUNCTION参数设置了on_curl_write,该函数将会1s中被回调1次。
$ch = curl_init();
$options = array(CURLOPT_URL        => 'http://www.php.net/',
CURLOPT_HEADER        => false,
CURLOPT_HEADERFUNCTION    => 'on_curl_header',
CURLOPT_WRITEFUNCTION    => 'on_curl_write'
);  最终我的实现片段:
function on_curl_write($ch, $data)
{
    $pid = getmypid();
    $downloadSizeRecorder = DownloadSizeRecorder::getInstance($pid);
    $bytes = strlen($data);
    $downloadSizeRecorder->downloadData .= $data;
    $downloadSizeRecorder->downloadedFileSize += $bytes;
//    error_log(' on_curl_write '.$downloadSizeRecorder->downloadedFileSize." > {$downloadSizeRecorder->maxSize} \n", 3, '/tmp/hyb.log');
    //确保已经下载的内容略大于最大限制
    if (($downloadSizeRecorder->downloadedFileSize - $bytes) > $downloadSizeRecorder->maxSize) {
        return false;
    }
    return $bytes;  //这个不正确的返回,将会报错,中断下载 "errno":23,"errmsg":"Failed writing body (0 != 16384)"
}  DownloadSizeRecorder是一个单例模式的类,curl下载时记录大小,实现返回下载内容的md5等。
class DownloadSizeRecorder
{
    const ERROR_FAILED_WRITING = 23; //Failed writing body
    public $downloadedFileSize;
    public $maxSize;
    public $pid;
    public $hasOverMaxSize;
    public $fileFullName;
    public $downloadData;
    private static $selfInstanceList = array();
    public static function getInstance($pid)
    {
        if(!isset(self::$selfInstanceList[$pid])){
            self::$selfInstanceList[$pid] = new self($pid);
        }
        return self::$selfInstanceList[$pid];
    }
    private function __construct($pid)
    {
        $this->pid = $pid;
        $this->downloadedFileSize = 0;
        $this->fileFullName = '';
        $this->hasOverMaxSize = false;
        $this->downloadData = '';
    }
    /**
     * 保存文件
     */
    public function saveMaxSizeData2File(){
        if(empty($resp_data)){
            $resp_data = $this->downloadData;
        }
        $fileFullName = '/tmp/http_'.$this->pid.'_'.time()."_{$this->maxSize}.download";
        if($resp_data && strlen($resp_data)>0)
        {
            list($headerOnly, $bodyOnly) = explode("\r\n\r\n", $resp_data, 2);
            $saveDataLenth = ($this->downloadedFileSize < $this->maxSize) ? $this->downloadedFileSize : $this->maxSize;
            $needSaveData = substr($bodyOnly, 0, $saveDataLenth);
            if(empty($needSaveData)){
                return;
            }
            file_put_contents($fileFullName, $needSaveData);
            if(file_exists($fileFullName)){
                $this->fileFullName = $fileFullName;
            }
        }
    }
    /**
     * 返回文件的md5
     * @return string
     */
    public function returnFileMd5(){
        $md5 = '';
        if(file_exists($this->fileFullName)){
            $md5 = md5_file($this->fileFullName);
        }
        return $md5;
    }
    /**
     * 返回已下载的size
     * @return int
     */
    public function returnSize(){
        return ($this->downloadedFileSize < $this->maxSize) ? $this->downloadedFileSize : $this->maxSize;
    }
    /**
     * 删除下载的文件
     */
    public function deleteFile(){
        if(file_exists($this->fileFullName)){
            unlink($this->fileFullName);
        }
    }
}  

  curl请求的代码实例中,实现限制下载大小
……
curl_setopt($ch, CURLOPT_WRITEFUNCTION, 'on_curl_write');//设置回调函数
……
$pid = getmypid();
$downloadSizeRecorder = DownloadSizeRecorder::getInstance($pid);
$downloadSizeRecorder->maxSize = $size_limit;
……
//发起curl请求
$response = curl_exec($ch);
……
//保存文件,返回md5
$downloadSizeRecorder->saveMaxSizeData2File();  //保存
$downloadFileMd5 = $downloadSizeRecorder->returnFileMd5();
$downloadedfile_size = $downloadSizeRecorder->returnSize();
$downloadSizeRecorder->deleteFile();  到这里,踩了一个坑。增加了on_curl_write后,$response会返回true,导致后面取返回内容的时候异常。好在已经实时限制了下载的大小,用downloadData来记录了已经下载的内容,直接可以使用。
if($response === true){
    $response = $downloadSizeRecorder->downloadData;
}  





运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-650740-1-1.html 上篇帖子: PHP系列(十)GD库 下篇帖子: PHP数组foreach追加值
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表