У меня есть веб-приложение Laravel, в котором пользователи могут загружать файлы. Эти файлы могут быть конфиденциальными, и, хотя они хранятся на S3, они доступны только через мои веб-серверы (потоковая загрузка). После загрузки пользователи могут захотеть загрузить выборку из этих файлов.
Ранее, когда пользователи загружали выбранные файлы, мой веб-сервер загружал файлы с 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 только поврежденные файлы.
В конце концов, решение состояло в том, чтобы использовать подписанные S3 URL и curl, чтобы обеспечить поток файлов для ZipStream, как продемонстрировано ведро s3 паровой почтовый индекс 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.
У меня были те же проблемы, что и у @cubiclewar, и я немного разбирался. Я обнаружил, что самое современное решение для этого не нуждается в завитке, и это видно здесь в вики для maennchen / ZipStream-PHP / библиотека.
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;
}