我目前正在使用外部SOAP Web服务,允许分块下载/上传二进制文件(应该允许更大的文件)。我需要允许最终用户使用我的PHP应用程序通过浏览器下载文件。提供小文件效果很好,但25MB +文件会导致Web服务器内存不足。
我正在使用本机PHP Soap客户端(没有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方法中的每个循环后都没有释放内存?
答案 0 :(得分:1)
http://php.net/manual/en/function.fpassthru.php
这可能有所帮助。它也可能改变你想做的一切。
答案 1 :(得分:1)
我觉得很傻。事实证明这只是另一个PHP主义。显然,即使我用ob_flush
刷新输出缓冲区(我认为)应该已经将标题和块发送到浏览器,但是在脚本完成之前,标题和输出实际上并没有被刷新到浏览器。
即使输出是self被刷新,你仍然必须显式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);
}