php使用curl下载指定大小的文件实例代码


Posted in PHP onSeptember 30, 2017

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;
}

总结

以上所述是小编给大家介绍的php使用curl下载指定大小的文件,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

PHP 相关文章推荐
php环境配置 php5 mysql5 apache2 phpmyadmin安装与配置
Nov 17 PHP
默默小谈PHP&amp;MYSQL分页原理及实现
Jan 02 PHP
PHP 出现乱码和Sessions验证问题的解决方法!
Dec 06 PHP
PHP程序员面试 切忌急功近利(更需要注重以后的发展)
Sep 01 PHP
PHP抓屏函数实现屏幕快照代码分享
Jan 02 PHP
PHP二维数组排序的3种方法和自定义函数分享
Apr 09 PHP
php学习笔记之面向对象
Nov 08 PHP
PHP实现将浏览历史页面网址保存到cookie的方法
Jan 26 PHP
PHP生成随机密码方法汇总
Aug 27 PHP
Laravel5.7 Eloquent ORM快速入门详解
Apr 12 PHP
phpstudy后门rce批量利用脚本的实现
Dec 12 PHP
宝塔面板在NGINX环境中TP5.1如何运行?
Mar 09 PHP
ThinkPHP3.1.x修改成功与失败跳转页面的方法
Sep 29 #PHP
PHP 获取 ping 时间的实现方法
Sep 29 #PHP
使用PHP+MySql实现微信投票功能实例代码
Sep 29 #PHP
PHP使用PDO访问oracle数据库的步骤详解
Sep 29 #PHP
Laravel中批量赋值Mass-Assignment的真正含义详解
Sep 29 #PHP
WHOOPS PHP调试库的使用
Sep 29 #PHP
Laravel中获取路由参数Route Parameters的五种方法示例
Sep 29 #PHP
You might like
如何正确理解PHP的错误信息
2006/10/09 PHP
社区(php&amp;&amp;mysql)五
2006/10/09 PHP
php实现mysql数据库备份类
2008/03/20 PHP
PHP入门教程之操作符与控制结构流程详解
2016/09/09 PHP
php使用curl伪造浏览器访问操作示例
2019/09/30 PHP
通过PHP的Wrapper无缝迁移原有项目到新服务的实现方法
2020/04/02 PHP
JavaScript 产生不重复的随机数三种实现思路
2012/12/13 Javascript
用示例说明filter()与find()的用法以及children()与find()的区别分析
2013/04/26 Javascript
js清理Word格式示例代码
2014/02/13 Javascript
JavaScrip关于创建常量的知识点
2017/12/07 Javascript
JS实现的汉字与Unicode码相互转化功能分析
2018/05/25 Javascript
JS实现HTML页面中动态显示当前时间完整示例
2018/07/30 Javascript
详解Vue组件之作用域插槽
2018/11/22 Javascript
JS学习笔记之闭包小案例分析
2019/05/29 Javascript
[59:07]海涛为你详解DOTA2新版本“贤哲秘契”
2014/11/22 DOTA
python模块之StringIO使用示例
2015/04/08 Python
Python获取邮件地址的方法
2015/07/10 Python
python strip() 函数和 split() 函数的详解及实例
2017/02/03 Python
python实现分页效果
2017/10/25 Python
使用Python通过win32 COM实现Word文档的写入与保存方法
2018/05/08 Python
Python切片索引用法示例
2018/05/15 Python
Python datetime包函数简单介绍
2019/08/28 Python
python pygame实现球球大作战
2019/11/25 Python
Python pandas 列转行操作详解(类似hive中explode方法)
2020/05/18 Python
利用python+ffmpeg合并B站视频及格式转换的实例代码
2020/11/24 Python
详解python定时简单爬取网页新闻存入数据库并发送邮件
2020/11/27 Python
一款css实现的鼠标经过按钮的特效
2014/09/11 HTML / CSS
Haglöfs瑞典官方网站:haglofs火柴棍,欧洲顶级户外品牌
2018/10/18 全球购物
英文版银行求职信
2013/10/09 职场文书
房地产财务管理制度
2014/02/02 职场文书
幼儿园三八妇女节活动方案
2014/03/11 职场文书
对祖国的寄语大全
2014/04/11 职场文书
节约用水的口号
2014/06/20 职场文书
答谢酒会主持词
2015/07/02 职场文书
Python深度学习之Pytorch初步使用
2021/05/20 Python
Python中Schedule模块使用详解 周期任务神器
2022/04/19 Python