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,如需转载请自行联系原作者

目录
相关文章
|
1月前
thinkphp5.1隐藏index.php入口文件
thinkphp5.1隐藏index.php入口文件
30 0
thinkphp5.1隐藏index.php入口文件
|
17天前
|
PHP
php下载MP4视频
php下载MP4视频
9 0
|
1月前
|
PHP 数据格式
PHP 中的CURL 模拟表单的post提交
PHP 中的CURL 模拟表单的post提交
16 0
|
3月前
|
PHP 数据安全/隐私保护
php案例:判断这个文件是什么编程语言代码的文件(判断java或者php)
php案例:判断这个文件是什么编程语言代码的文件(判断java或者php)
php案例:判断这个文件是什么编程语言代码的文件(判断java或者php)