php使用curl下载指定大小的文件

简介:

    php中使用基于libcurl的curl函数,可以对目标url发起http请求并获取返回的响应内容。通常的请求方式类似如下的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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次。

1
2
3
4
5
6
$ch  = curl_init();
$options  array (CURLOPT_URL        =>  'http://www.php.net/' ,
CURLOPT_HEADER        => false,
CURLOPT_HEADERFUNCTION    =>  'on_curl_header' ,
CURLOPT_WRITEFUNCTION    =>  'on_curl_write'
);

    最终我的实现片段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
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请求的代码实例中,实现限制下载大小

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
……
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来记录了已经下载的内容,直接可以使用。

1
2
3
if ( $response  === true){
     $response  $downloadSizeRecorder ->downloadData;
}





本文转自 byteh 51CTO博客,原文链接:http://blog.51cto.com/byteh/1969809,如需转载请自行联系原作者

目录
相关文章
|
3月前
|
PHP
php常见问题,php.ini文件不存在或者找不到,mb_strlen()函数未定义系列问题,dll模块找不到的解决
本文介绍了解决PHP常见问题的步骤,包括定位和创建`php.ini`文件,以及解决`mb_strlen()`函数未定义和DLL模块加载错误的具体方法。
php常见问题,php.ini文件不存在或者找不到,mb_strlen()函数未定义系列问题,dll模块找不到的解决
|
4天前
|
PHP 计算机视觉 UED
Buzz库:PHP图像处理中的异步图像下载和保存
Buzz库:PHP图像处理中的异步图像下载和保存
|
17天前
|
数据采集 JavaScript 网络安全
为什么PHP爬虫抓取失败?解析cURL常见错误原因
豆瓣电影评分是电影市场的重要参考,通过网络爬虫技术可以高效采集评分数据,帮助电影制作和发行方优化策略。本文介绍使用PHP cURL库和代理IP技术抓取豆瓣电影评分的方法,解决反爬机制、网络设置和数据解析等问题,提供详细代码示例和优化建议。
为什么PHP爬虫抓取失败?解析cURL常见错误原因
|
3月前
|
前端开发 PHP
php学习笔记-php文件表单上传-day06
本文介绍了PHP文件上传处理流程、预定义变量`$_FILES`的使用、文件上传状态代码以及文件上传实现函数。同时,通过一个文件上传的小例子,演示了文件上传表单的创建、文件上传表单处理的PHP页面编写以及运行测试输出。
php学习笔记-php文件表单上传-day06
|
3月前
|
缓存 监控 算法
分析慢日志文件来优化 PHP 脚本的性能
分析慢日志文件来优化 PHP 脚本的性能
|
3月前
进入靶场,出现一张照片,右击查看源代码,发现有一个注释的source.php文件
这段代码实现了一个网站上弹出的促销海报动画效果,包含一个关闭按钮。当促销海报弹出时,会在三秒后开始抖动一两下。海报使用固定定位居中显示,带有阴影和圆角,关闭按钮位于右上角。可以通过修改时间参数调整弹出时间。
20 0
|
4月前
|
存储 安全 数据库连接
php.ini 文件的用途是什么?
【8月更文挑战第29天】
75 1
|
4月前
|
PHP
PHP遍历文件并同步上传到服务器
在进行网站迁移时,由于原网站的图片文件过多,采用打包下载再上传的方式耗时过长,且尝试使用FTP工具从旧服务器传输至新服务器时失败。为解决此问题,特使用PHP编写了一款工具,该工具能扫描指定目录下的所有`.webp`图像文件,并将其上传至新的服务器,极大地提高了迁移效率。
107 16
|
4月前
|
Java 应用服务中间件 PHP
PHP——调用java文件中的方法
PHP——调用java文件中的方法
59 0
PHP——调用java文件中的方法
|
5月前
|
API PHP UED
​一个PHP文件实现联系表单自动发送邮件
使用PHP和AOKSend服务,可以创建一个联系表单,收集用户信息并自动发送邮件。HTML表单包含姓名、邮箱和消息字段。PHP文件`send_mail.php`处理表单提交,通过AOKSend的SMTP设置(如主机、端口、API密钥)使用PHPMailer发送邮件到指定地址。代码中还包括安全措施,如使用`htmlspecialchars`防止XSS攻击。这种方法增强了网站的用户沟通体验,并依赖AOKSend的稳定性和API进行高效邮件发送。