通过PHP从外部Web服务流式传输大文件

问题描述:

我目前正在使用允许分块下载/上传二进制文件(应允许更大文件)的外部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方法的每个循环之后内存不会被释放?

嗯,我感觉很傻。这原来只是另一个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); 
} 

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

尝试这对我的作品

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