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
|
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; } |
总结
以上所述是小编给大家介绍的php使用curl下载指定大小的文件,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对服务器之家网站的支持!
原文链接:http://www.tuicool.com/articles/imQniaN