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具体实现代码
Oct 12 PHP
PHP查询MySQL大量数据的时候内存占用分析
Jul 22 PHP
php数组函数序列之each() - 获取数组当前内部指针所指向元素的键名和键值,并将指针移到下一位
Oct 31 PHP
Zend Studio 实用快捷键一览表(精心整理)
Aug 10 PHP
yii实现创建验证码实例解析
Jul 31 PHP
配置php.ini实现PHP文件上传功能
Nov 27 PHP
PHP多文件上传类实例
Mar 07 PHP
PHP中把对象转换为关联数组代码分享
Apr 09 PHP
PHP与Ajax相结合实现登录验证小Demo
Mar 16 PHP
php生成mysql的数据字典
Jul 07 PHP
PHP7 新特性详细介绍
Sep 06 PHP
php array_slice 取出数组中的一段序列实例
Nov 04 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 采集书并合成txt格式的实现代码
2009/03/01 PHP
windows下安装php的memcache模块的方法
2015/04/07 PHP
yii2中关于加密解密的那些事儿
2018/06/12 PHP
jquery对象和javascript对象即DOM对象相互转换
2014/08/07 Javascript
javascript带回调函数的异步脚本载入方法实例分析
2015/07/02 Javascript
javascript实现网页子页面遍历回调的方法(涉及 window.frames、递归函数、函数上下文)
2015/07/27 Javascript
谈一谈jQuery核心架构设计
2016/03/28 Javascript
如何利用模板将HTML从JavaScript中抽离
2016/10/08 Javascript
基于JS组件实现拖动滑块验证功能(代码分享)
2016/11/18 Javascript
函数四种调用模式以及其中的this指向
2017/01/16 Javascript
Nodejs读取文件时相对路径的正确写法(使用fs模块)
2017/04/27 NodeJs
Vue中组件之间数据的传递的示例代码
2017/09/08 Javascript
jQuery中常用动画效果函数知识点整理
2018/08/19 jQuery
nodejs对项目下所有空文件夹创建gitkeep的方法
2019/08/02 NodeJs
Python数据结构与算法之图的广度优先与深度优先搜索算法示例
2017/12/14 Python
python实现多人聊天室
2020/03/31 Python
实例介绍Python中整型
2019/02/11 Python
pyqt 实现为长内容添加滑轮 scrollArea
2019/06/19 Python
django用户登录验证的完整示例代码
2019/07/21 Python
Python restful框架接口开发实现
2020/04/13 Python
tensorflow常用函数API介绍
2020/04/19 Python
python操作redis数据库的三种方法
2020/09/10 Python
美国家喻户晓的保健品品牌:Vitamin World(维他命世界)
2016/08/19 全球购物
Lowe’s加拿大:家居装修、翻新和五金店
2019/12/06 全球购物
新闻编辑自荐信
2013/11/03 职场文书
女儿十岁生日答谢词
2014/01/27 职场文书
网络优化专员求职信
2014/05/04 职场文书
2014年学校财务工作总结
2014/12/06 职场文书
检讨书大全
2015/01/27 职场文书
2015毕业寄语大全
2015/02/26 职场文书
辅导员学期工作总结
2015/08/14 职场文书
“爱眼护眼,提前预防近视”倡议书3篇
2019/10/30 职场文书
六年级作文之自救
2019/12/19 职场文书
基于python实现银行管理系统
2021/04/20 Python
低门槛开发iOS、Android、小程序应用的前端框架详解
2021/10/16 Javascript
maven 解包依赖项中的文件的解决方法
2022/07/15 Java/Android