【问题标题】:Creating a zip file on the fly from files stored on S3 using php使用 php 从存储在 S3 上的文件即时创建 zip 文件
【发布时间】:2016-11-10 17:55:52
【问题描述】:

我有一个 Laravel 网络应用程序,用户可以在其中上传文件。这些文件可能很敏感,虽然它们存储在 S3 上,但它们只能通过我的网络服务器(流式下载)访问。上传后,用户可能希望下载这些文件的选择。

以前,当用户下载文件选择时,我的 Web 服务器会从 S3 下载文件,将它们压缩到本地,然后将压缩文件发送到客户端。但是,由于文件大小,一旦投入生产,服务器响应就会经常超时。

作为一种替代方法,我想通过ZipStream 即时压缩文件,但我运气不佳。 zip 文件要么以损坏的文件结尾,要么本身已损坏并且非常小。

如果可以将 S3 上文件的流资源传递给 ZipStream,解决我的超时问题的最佳方法是什么?

我尝试了几种方法,我最近的两种方法如下:

// First method using fopen
// Results in tiny corrupt zip files
if (!($fp = fopen("s3://{$bucket}/{$key}", 'r')))
{
    die('Could not open stream for reading');
}

$zip->addFileFromPath($file->orginal_filename, "s3://{$bucket}/{$key}");
fclose($fp);


// Second method tried get download the file from s3 before sipping
// Results in a reasonable sized zip file that is corrupt
$contents = file_get_contents("s3://{$bucket}/{$key}");

$zip->addFile($file->orginal_filename, $contents); 

其中的每一个都位于遍历每个文件的循环中。在循环之后我调用 $zip->finish()。

请注意,我没有收到任何 php 错误,只是文件损坏。

【问题讨论】:

    标签: php amazon-s3 zipfile


    【解决方案1】:

    我遇到了与@cubiclewar 相同的问题并进行了一些调查。我发现对此的最新解决方案不需要 curl,它在 maennchen/ZipStream-PHP/ 库的 wiki 上可见。

    https://github.com/maennchen/ZipStream-PHP/wiki/Symfony-example

    use ZipStream;
    
    //...
    
    /**
     * @Route("/zipstream", name="zipstream")
     */
    public function zipStreamAction()
    {
        //sample test file on s3
        $s3keys = array(
          "ziptestfolder/file1.txt"
        );
    
        $s3Client = $this->get('app.amazon.s3'); //s3client service
        $s3Client->registerStreamWrapper(); //required
    
        //using StreamedResponse to wrap ZipStream functionality for files on AWS s3.
        $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'; //replace with your bucket name or get from parameters file.
                $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;
    }
    

    【讨论】:

      【解决方案2】:

      最终解决方案是使用签名的 S3 url 和 curl 为 ZipStream 提供文件流,如 s3 bucket steam zip php 所示。从上述来源编辑的结果代码如下:

      public function downloadZip()
      {
          // ...
      
          $s3 = Storage::disk('s3');
          $client = $s3->getDriver()->getAdapter()->getClient();
          $client->registerStreamWrapper();
          $expiry = "+10 minutes";
      
          // Create a new zipstream object
          $zip = new ZipStream($zipName . '.zip');
      
          foreach($files as $file)
          {
              $filename = $file->original_filename;
      
              // We need to use a command to get a request for the S3 object
              //  and then we can get the presigned URL.
              $command = $client->getCommand('GetObject', [
                  'Bucket' => config('filesystems.disks.s3.bucket'),
                  'Key' => $file->path()
              ]);
      
              $signedUrl = $request = $client->createPresignedRequest($command, $expiry)->getUri();
      
              // We want to fetch the file to a file pointer so we create it here
              //  and create a curl request and store the response into the file
              //  pointer.
              // After we've fetched the file we add the file to the zip file using
              //  the file pointer and then we close the curl request and the file
              //  pointer.
              // Closing the file pointer removes the file.
              $fp = tmpfile();
              $ch = curl_init($signedUrl);
              curl_setopt($ch, CURLOPT_TIMEOUT, 120);
              curl_setopt($ch, CURLOPT_FILE, $fp);
              curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
              curl_exec($ch);
              curl_close($ch);
              $zip->addFileFromStream($filename, $fp);
              fclose($fp);
          }
      
          $zip->finish();
      }
      

      请注意,这需要在您的服务器上安装 curl 和 php-curl 并正常运行。

      【讨论】:

      • 嗨小卧室我试过你的功能,一切似乎都工作正常,除了我在添加到 zip 文件时得到 0 字节文件,我还尝试了示例函数 fwrite($fp, 'The quick brown狐狸跳过了那只懒狗。'); $zip->addFileFromStream('goodbye.txt', $fp); fclose($fp);并得到同样的东西。任何帮助将不胜感激。谢谢
      • 当您尝试 fwrite 时,您是否删除了 curl 调用。当文件存在但为 0 字节文件时,通常意味着您的文件指针未分配。
      • 在您上面的示例代码中,没有 fwrite();是不是应该有
      • @rbz no curl 正在从 S3 检索文件并将其写入本地磁盘上由 $fp 引用的临时文件。
      • 是的,我有点看到 $ch = curl_init($signedUrl);和 curl_setopt($ch, CURLOPT_FILE, $fp);仍然不确定发生了什么,signedUrl 在浏览器上工作以检索内容。我不确定这是否是权限问题。感谢所有的帮助
      猜你喜欢
      • 2021-04-05
      • 2016-12-02
      • 2019-11-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-08-21
      • 2016-12-22
      • 1970-01-01
      相关资源
      最近更新 更多