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 数字左侧自动补0
Mar 31 PHP
MySQL 日期时间函数常用总结
Jun 12 PHP
解析php时间戳与日期的转换
Jun 06 PHP
php使浏览器直接下载pdf文件的方法
Nov 15 PHP
Windows和Linux中php代码调试工具Xdebug的安装与配置详解
May 08 PHP
制作安全性高的PHP网站的几个实用要点
Dec 30 PHP
使用GD库生成带阴影文字的图片
Mar 27 PHP
PHP时间和日期函数详解
May 08 PHP
如何在HTML 中嵌入 PHP 代码
May 13 PHP
PHP实现的简单异常处理类示例
May 04 PHP
微信公众平台开发教程②微信端分享功能图文详解
Apr 10 PHP
php设计模式之职责链模式定义与用法经典示例
Sep 19 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
模仿OSO的论坛(五)
2006/10/09 PHP
PHP多线程抓取网页实现代码
2010/07/22 PHP
CI框架扩展系统核心类的方法分析
2016/05/23 PHP
PHP自定义函数判断是否为Get、Post及Ajax提交的方法
2017/07/27 PHP
PHP使用Curl实现模拟登录及抓取数据功能示例
2018/04/27 PHP
php提供实现反射的方法和实例代码
2019/09/17 PHP
不安全的常用的js写法
2009/09/15 Javascript
多浏览器支持的右下角浮动窗口
2010/04/01 Javascript
汉化英文版的Dreamweaver CS5并自动提示jquery
2010/11/25 Javascript
30个最好的jQuery 灯箱插件分享
2011/04/25 Javascript
jQuery EasyUI API 中文文档 - Menu菜单
2011/10/03 Javascript
jquery插件制作 图片走廊 gallery
2012/08/17 Javascript
jquery实现简单易懂的图片展示小例子
2013/11/21 Javascript
js 获取元素下面所有li的两种方法
2014/04/14 Javascript
JS+CSS实现可拖拽的漂亮圆角特效弹出层完整实例
2015/02/13 Javascript
jQuery实现锚点向下平滑滚动特效示例
2017/08/29 jQuery
[01:10:58]Spirit vs NB Supermajor小组赛 A组败者组决赛 BO3 第二场 6.2
2018/06/03 DOTA
pycharm 使用心得(二)设置字体大小
2014/06/05 Python
详解python OpenCV学习笔记之直方图均衡化
2018/02/08 Python
python 处理string到hex脚本的方法
2018/10/26 Python
python使用多线程+socket实现端口扫描
2020/05/28 Python
浅析Python 序列化与反序列化
2020/08/05 Python
CSS3中各种颜色属性的使用教程
2016/05/17 HTML / CSS
处理HTML5新标签的浏览器兼容版问题
2017/03/13 HTML / CSS
腾讯技术类校园招聘笔试试题
2014/05/06 面试题
高中军训感言1000字
2014/03/01 职场文书
空中乘务员岗位职责
2014/03/08 职场文书
幼儿园大班家长评语
2014/04/17 职场文书
热爱祖国的演讲稿
2014/05/04 职场文书
承诺书格式
2014/06/03 职场文书
综艺节目策划方案
2014/06/13 职场文书
2015年食品安全宣传周活动总结
2015/07/09 职场文书
国庆节新闻稿
2015/07/17 职场文书
JavaScript组合继承详解
2021/11/07 Javascript
一文带你探究MySQL中的NULL
2021/11/11 MySQL
Win11应用商店打开闪退怎么解决? win11应用商店打不开的多种解决办法
2022/04/05 数码科技