在 Symfony 中使用 ZipStream:在 Mac OSX 上使用 Archive Utility 不会解压流式 zip 下载

Using ZipStream in Symfony: streamed zip download will not decompress using Archive Utility on Mac OSX

我有一个 symfony 2.8 应用程序,在用户单击 "download" 按钮时,我使用 s3 上的几个大(图像、视频)文件的键使用 ZipStream 将其作为 zip 文件流式传输到浏览器(https://github.com/maennchen/ZipStream-PHP).

文件流和下载为 zip(存储,未压缩)在浏览器中成功,zip 登陆下载。但是,当尝试使用 Archive Utility(OSX 上的本地存档软件)在 Mac OSX El Capitan 中打开 zip 时,它会出错。错误:

无法将 "test.zip" 扩展为 "Downloads"。 (错误 2 - 没有这样的文件或目录。)

我在 SO 上看到了较早的相同问题并尝试了这些修复,特别是这个 post: 并跟进了 ZipStream 中相关的问题和 PR,以及 Guzzle等

问题是,上述修复程序早在 2011 年就已修复,而那时候事情还在继续。所以应用相同的修复程序我没有得到有效的结果。

我尝试过的具体修复是: 1. 按照建议将 "version to extract" 设置为 0x000A。也作为另一个 post 中推荐的“20”。我为 "version made by" 设置了相同的设置。 2. 我试图将压缩方法强制设置为 'deflate' 而不是 'stored' 以查看我是否得到了有效的结果。存储的结果是我所需要的,适合用作图像和视频容器文件的 zip。

我可以使用名为 The Unarchiver 的第三方存档应用程序提取 zip。但是,用户不会知道也不能指望安装替代存档应用程序来适合我的网络应用程序。那不是一个有效的解决方案。

有没有人有解决这个问题的知识或经验,可以帮助我解决这个问题?

注意:将 zip 流式传输到浏览器是必需的解决方案。考虑到这种方法的时间和开销,将资产从 s3 下载到服务器以创建一个 zip,然后将生成的 zip 流式传输到浏览器并不是一个解决方案。

根据需要添加信息:

代码和设置: - 文件存储在 s3 上。 - Web 应用程序是 PHP7.0 上的 symfony 2.8,在 Ubuntu 的 ec2 上运行。 - 使用 aws-sdk-php,创建一个具有有效凭据的 s3client,然后我在 s3client 上注册 StreamWrapper (s3client->registerStreamWrapper())。这是通过 fopen 从 s3 获取文件以流式传输到 ZipStream 库:

$this->s3Client = $s3Client;  
$this->s3Client->registerStreamWrapper();

// Initialize the ZipStream object and pass in the file name which
//  will be what is sent in the content-disposition header.
// This is the name of the file which will be sent to the client.
// Define suitable options for ZipStream Archive.
$opt = array(
        'comment' => 'test zip file.',
        'content_type' => 'application/octet-stream'
      );
$zip = new ZipStream\ZipStream($filename, $opt);

$keys = array(
      "zipsimpletestfolder/file1.txt"
  );

foreach ($keys as $key) {
    // Get the file name in S3 key so we can save it to the zip file 
    // using the same name.
    $fileName = basename($key);

    $bucket = 'mg-test';
    $s3path = "s3://" . $bucket . "/" . $key;

    if ($streamRead = fopen($s3path, 'r')) {
      $zip->addFileFromStream($fileName, $streamRead);        
    } else {
      die('Could not open stream for reading');
    }
}

$zip->finish();

Zip 输出结果:

响应Headers:

编辑 我愿意使用另一种方法将文件作为 zip 流式传输到浏览器,如果建议的话,可以在 Mac、Windows、Linux 上本地打开而无需使用 ZipStream。只是不要创建一个 zip 文件服务器端以随后流式传输。

下载的 zip 文件的问题是它包含 html 来自调用 ZipStream->addFileFromStream() 的 Symfony 控制器的响应。基本上,当 ZipStream 流式传输数据以在客户端浏览器中创建 zip 下载时,还会发送控制器操作响应,最好的猜测是,两者在客户端浏览器中混淆了。在十六进制编辑器中打开 zip 文件并看到其中的 html 显然是问题所在。

为了使它在 Symfony 2.8 中与 ZipStream 一起工作,我只是在控制器操作中使用了 Symfony 的 StreamedResponse,并在 streamedResponse 的函数中使用了 ZipStream。为了流式传输 S3 文件,我刚刚将一个 s3keys 数组和 s3client 传入该函数。所以:

use Symfony\Component\HttpFoundation\StreamedResponse;
use Aws\S3\S3Client;    
use ZipStream;

//...

/**
 * @Route("/zipstream", name="zipstream")
 */
public function zipStreamAction()
{
    //test file on s3
    $s3keys = array(
      "ziptestfolder/file1.txt"
    );

    $s3Client = $this->get('app.amazon.s3'); //s3client service
    $s3Client->registerStreamWrapper(); //required

    $response = new StreamedResponse(function() use($s3keys, $s3Client) 
    {

        // Define suitable options for ZipStream Archive.
        $opt = array(
                'comment' => 'test zip file.',
                'content_type' => 'application/octet-stream'
              );
        //initialise zipstream with output zip filename and options.
        $zip = new ZipStream\ZipStream('test.zip', $opt);

        //loop keys useful for multiple files
        foreach ($s3keys as $key) {
            // Get the file name in S3 key so we can save it to the zip 
            //file using the same name.
            $fileName = basename($key);

            //concatenate s3path.
            $bucket = 'bucketname';
            $s3path = "s3://" . $bucket . "/" . $key;        

            //addFileFromStream
            if ($streamRead = fopen($s3path, 'r')) {
              $zip->addFileFromStream($fileName, $streamRead);        
            } else {
              die('Could not open stream for reading');
            }
        }

        $zip->finish();

    });

    return $response;
}

已解决。也许这有助于其他人在 Symfony 中使用 ZipStream。