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