2010-12-09 96 views
0

我目前正在使用允許分塊下載/上傳二進制文件(應允許更大文件)的外部SOAP Web服務。我需要允許最終用戶使用我的PHP應用程序通過瀏覽器下載文件。提供小文件效果很好,但25MB +文件會導致Web服務器耗盡內存。通過PHP從外部Web服務流式傳輸大文件

我正在使用原生PHP Soap Client(無MTOM支持),並通過提交表單來請求下載。目前,似乎Web服務器在向瀏覽器輸出任何內容之前嘗試下載整個文件(例如,直到整個文件通過PHP進行處理之後,「下載」提示纔會顯示)。

我的方法看起來像這樣(抱歉,如果它很混亂,我已經在這個問題上一段時間了)。

public function download() 
{ 
    $file_info_from_ws ... //Assume setup from $_REQUEST params 

    //Don't know if these are needed 
    gc_enable(); 
    set_time_limit(0); 
    @apache_setenv('no-gzip', 1); 
    @ini_set('zlib.output_compression', 0); 

    //File Info 
    $filesize = $file_info_from_ws->get_filesize(); 
    $fileid = $file_info_from_ws->get_id(); 
    $filename = $file_info_from_ws->get_name(); 
    $offset = 0; 
    $chunksize = (1024 * 1024); 

    //Clear any previous data 
    ob_clean(); 
    ob_start(); 

    //Output headers 
    header('Content-Type: application/octet-stream'); 
    header('Content-Length: ' . $filesize); 
    header('Content-Transfer-Encoding: binary'); 
    header('Content-Disposition: attachment; filename="' . $filename . '"'); 
    header('Accept-Ranges: bytes'); 

    while($offset < $filesize) 
    { 
     $chunk = $this->dl_service()->download_chunked_file($fileid, $offset, $chunksize); 
     if($chunk) 
     { 
     //Immediately echo out the stream 
     $chunk->render(); 
     $offset += $chunksize; 
     unset($chunk); //Shouldn't this trigger GC? 
     ob_flush(); 
     } 
    } 
    ob_end_flush(); 
} 

所以我的主要問題是: 什麼是從外部資源(web服務,數據庫等),通過PHP向最終用戶輸出的大型二進制數據塊的最佳方式?最好不要殺死內存/ CPU太多。

我也很好奇如下:
爲什麼不第一輸出後的下載提示彈出?
爲什麼在about方法的每個循環之後內存不會被釋放?

回答

1

嗯,我感覺很傻。這原來只是另一個PHP主題。顯然,即使我用ob_flush來刷新輸出緩衝區(我認爲)應該已經將標題和塊發送到瀏覽器,但是在腳本完成之前,標題和輸出實際上並沒有被刷新到瀏覽器。

即使輸出自刷新,您仍然必須明確flush PHP的寫入緩衝區和Web服務器回到客戶端。不這樣做導致內存擴展,下載提示不會顯示,直到整個下載完成。

這裏是工作方法的版本:下面的代碼

public function download() 
{ 
    $file_info ... //Assume init'ed from WS or DB 

    //Allow for long running process 
    set_time_limit(0); 

    //File Info 
    $filesize = $file_info->get_filesize(); 
    $fileid = $file_info->get_id(); 
    $filename = $file_info->get_name(); 
    $offset = 0; 
    $chunksize = (1024 * 1024); 

    //Clear any previous data 
    ob_clean(); 
    ob_start(); 

    //Output headers to notify browser it's a download 
    header('Content-Type: application/octet-stream'); 
    header('Content-Length: ' . $filesize); 
    header('Content-Disposition: attachment; filename="' . $filename . '"'); 

    while($offset < $filesize) 
    { 
     //Retrieve chunk from service 
     $chunk = $this->dl_service()->download_chunked_file($fileid, $offset, $chunksize); 
     if($chunk) 
     { 
     //Immediately echo out the stream 
     $chunk->render(); 
     //NOTE: The order of flushing IS IMPORTANT 
     //Flush the data to the output buffer 
     ob_flush(); 
     //Flush the write buffer directly to the browser 
     flush(); 
     //Cleanup and prepare next request 
     $offset += $chunksize; 
     unset($chunk); 
     } 
    } 
    //Exit the script immediately to prevent other output from corrupting the file 
    exit(0); 
} 
1

http://php.net/manual/en/function.fpassthru.php

這可能有一定的幫助。它也可能改變你想做任何事情的方式。

+0

的fopen可以打開外部URL。所以它應該在理論上起作用。 – DampeS8N 2010-12-09 19:20:18

+0

不幸的是,API不能直接由URL調用。因爲WebService是一個非常複雜的enterprise-y SOAP服務,並且download_chunk方法實際上需要相當複雜的對象作爲參數。 – 2010-12-09 19:27:52

0

嘗試這對我的作品

public function testUpload(){ 

    $response = array(); 
    if (empty($_FILES) || $_FILES['file']['error']) { 
    $response["code"] = 2; 
      $response["message"] = "failed to move uploaded file"; 
      echo json_encode($response); 
    } 

    $chunk = isset($_REQUEST["chunk"]) ? intval($_REQUEST["chunk"]) : 0; 
    $chunks = isset($_REQUEST["chunks"]) ? intval($_REQUEST["chunks"]) : 0; 

    $fileName = isset($_REQUEST["name"]) ? $_REQUEST["name"] : $_FILES["file"]["name"]; 
    $filePath = "uploads/$fileName"; 

    // Open temp file 
    $out = @fopen("{$filePath}.part", $chunk == 0 ? "wb" : "ab"); 

    if ($out) { 
     // Read binary input stream and append it to temp file 
     $in = @fopen($_FILES['file']['tmp_name'], "rb"); 

     if ($in) { 
     while ($buff = fread($in, 4096)) 
       fwrite($out, $buff); 

     } else 

    $response["code"] = 2; 
    $response["message"] = "Oops! Failed to open input Stream error occurred."; 
    echo json_encode($response); 
     @fclose($in); 

     @fclose($out); 

     @unlink($_FILES['file']['tmp_name']); 
    } else 

     $response["code"] = 2; 
     $response["message"] = "Oops! Failed to open output error occurred."; 
     echo json_encode($response); 


    // Check if file has been uploaded 
    if (!$chunks || $chunk == $chunks - 1) { 
     // Strip the temp .part suffix off 
     rename("{$filePath}.part", $filePath); 
    } 
    $response["code"] = 2; 
    $response["message"] = "successfully uploaded"; 
    echo json_encode($response); 
} 

to know more click

相關問題