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中文網其他相關文章!