web-dev-qa-db-ja.com

phpを使用してS3に保存されたファイルからオンザフライでZipファイルを作成する

私はLaravelユーザーがファイルをアップロードできるWebアプリを持っています。これらのファイルは機密性が高く、S3に保存されていますが、私のWebサーバー経由でのみアクセスされます(ストリーミングダウンロード)。アップロードされたユーザーはこれらのファイルの選択をダウンロードしたい。

以前は、ユーザーが選択したファイルをダウンロードしようとすると、私のWebサーバーはS3からファイルをダウンロードし、ローカルでZipしてから、Zipをクライアントに送信していました。ただし、ファイルサイズが原因で本番環境に入ると、サーバーの応答が頻繁にタイムアウトします。

別の方法として、 ZipStream を介してファイルをその場で圧縮したいのですが、あまり運がありませんでした。 Zipファイルは、ファイルが破損するか、それ自体が破損していて非常に小さいものになります。

S3上のファイルのストリームリソースをZipStreamに渡すことが可能であり、タイムアウトの問題に対処するための最良の方法は何ですか?

私はいくつかの方法を試しましたが、最近の2つは次のとおりです。

// 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エラーが発生しないことに注意してください。

8
cubiclewar

結局のところ、解決策は、署名されたS3 URLとcurlを使用して、 s3バケットSteam Zip php で示されるようにZipStreamのファイルストリームを提供することでした。前述のソースから編集された結果のコードは次のとおりです。

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がサーバーにインストールされ、機能している必要があることに注意してください。

5
cubiclewar

@cubiclewarと同じ問題があり、少し調査しました。これに対する最新のソリューションはカールを必要とせず、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
Ben