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 身份证号验证函数
May 07 PHP
smarty 缓存控制前的页面静态化原理
Mar 15 PHP
PHP面向对象之旅:深入理解static变量与方法
Jan 06 PHP
ThinkPHP设置禁止百度等搜索引擎转码(简单实用)
Feb 15 PHP
PHP的PDO操作简单示例
Mar 30 PHP
PHP MySql增删改查的简单实例
Jun 21 PHP
PHP实现对数组分页处理实例详解
Feb 07 PHP
完美解决thinkphp唯一索引重复时出错的问题
Mar 31 PHP
php empty 函数判断结果为空但实际值却为非空的原因解析
May 28 PHP
Laravel 实现Controller向blade前台模板赋值的四种方式小结
Oct 22 PHP
php post换行的方法
Feb 03 PHP
WordPress多语言翻译插件 - WPML使用教程
Apr 01 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
打造计数器DIY三步曲(上)
2006/10/09 PHP
PHP分页显示制作详细讲解
2006/12/05 PHP
php pack与unpack 摸板字符字符含义
2009/10/29 PHP
浅析PHP Socket技术
2013/08/02 PHP
PHP动态地创建属性和方法, 对象的复制, 对象的比较,加载指定的文件,自动加载类文件,命名空间
2016/05/06 PHP
详解PHP版本兼容之openssl调用参数
2018/07/25 PHP
关于viewport,Ext.panel和Ext.form.panel的关系
2009/05/07 Javascript
幻灯片带网页设计中的20个奇妙应用示例小结
2012/05/27 Javascript
ajax java 实现自动完成功能
2012/12/19 Javascript
Jquery post传递数组方法实现思路及代码
2013/04/28 Javascript
将nodejs打包工具整合到鼠标右键的方法
2013/05/11 NodeJs
js阻止冒泡及jquery阻止事件冒泡示例介绍
2013/11/19 Javascript
js+html5实现canvas绘制简单矩形的方法
2015/06/05 Javascript
javaScript实现可缩放的显示区效果代码
2015/10/26 Javascript
AngularJs html compiler详解及示例代码
2016/09/01 Javascript
微信小程序 devtool隐藏的秘密
2017/01/21 Javascript
js获取浏览器的各种属性
2017/04/27 Javascript
微信小程序画布圆形进度条显示效果
2020/11/17 Javascript
angularJs中跳转到指定的锚点实例($anchorScroll)
2018/08/31 Javascript
vue使用laydate时间插件的方法
2018/11/14 Javascript
vue-resourc发起异步请求的方法
2020/02/11 Javascript
JS script脚本中async和defer区别详解
2020/06/24 Javascript
[58:23]LGD vs TNC 2019国际邀请赛小组赛 BO2 第一场 8.15
2019/08/16 DOTA
python私有属性和方法实例分析
2015/01/15 Python
简单介绍Python中的decode()方法的使用
2015/05/18 Python
运动检测ViBe算法python实现代码
2018/01/09 Python
简单谈谈python中的lambda表达式
2018/01/19 Python
python实现机器人行走效果
2018/01/29 Python
python使用百度文字识别功能方法详解
2019/07/23 Python
对Python _取log的几种方式小结
2019/07/25 Python
Rentalcars.com中国:世界上最大的在线汽车租赁服务
2019/08/22 全球购物
平面设计师的工作职责
2013/11/21 职场文书
优秀的计算机专业求职信范文
2013/12/27 职场文书
高一地理教学反思
2014/01/18 职场文书
成品仓库管理员岗位职责
2015/04/09 职场文书
Nginx 路由转发和反向代理location配置实现
2021/11/11 Servers