Download as ZIP
Сегодня в нашем блоге гостевой пост — о своем опыте использования нашего облачного хранилища расскажет Андрей Суржиков, разработчик в одной из медицинских компаний. Мы храним в облаке Selectel огромное количество файлов. В среднем, каждый месяц пользователи загружают к нам около 1 миллиона файлов (200 Гб данных), которые мы обязуемся хранить в течение 5 лет. Помимо […]
Сегодня в нашем блоге гостевой пост — о своем опыте использования нашего облачного хранилища расскажет Андрей Суржиков, разработчик в одной из медицинских компаний.
Мы храним в облаке Selectel огромное количество файлов. В среднем, каждый месяц пользователи загружают к нам около 1 миллиона файлов (200 Гб данных), которые мы обязуемся хранить в течение 5 лет. Помимо хранения, мы должны дать пользователям возможность скачивать нужные наборы файлов в виде архивов.
Для этого у нас есть два варианта:
- Стандартный — запросом в API скомандовать облаку отдать определенный контейнер или папку в виде архива (этот вариант предлагается в официальной документации Selectel по облачному хранилищу).
- Самодельный — забирать файлы из облака, а непосредственно архивацию выполнять у себя на сервере.
Рассмотрим преимущества и недостатки обоих вариантов.
Стандартный вариант
Облако дает возможность скачать любой контейнер или папку внутри контейнера в виде ZIP-архива. Эта возможность описана в официальной документации.
Плюсы:
- не надо писать код: просто сгенерировал ссылку и отдал пользователю;
- не требует ресурсов вашего сервера;
- меньше запросов к API — экономит копеечку с баланса.
Минусы:
- облако отдает архив без сжатия;
- неподходящая для нас структура папок в архиве с директориями от корня контейнера.
В случае с публичным контейнером, достаточно включить в настройках контейнера возможность скачивания в виде архива (либо с помощью заголовка X-Container-Meta-Allow-ZipDownload: True) и сформировать ссылку вида:
https://api.selcdn.ru/v1/SEL_*****/container/?download-all-as-zip=container.zip
После этого отдать ее пользователю. Как только он откроет ссылку — начнется загрузка container.zip содержащего в себе все объекты контейнера.
Приватный контейнер
Скачать архив из приватного контейнера тоже можно, только нужно передать авторизационный HTTP-заголовок X-Auth-Token (не забывайте, Облачное хранилище построено на базе OpenStack, если не нашли чего-то в документации Selectel — попробуйте поискать в документации OpenStack).
Сценарий действий такой:
- Пользователь хочет скачать заархивированную папку из приватного контейнера.
- Мы даем ему ссылку примерно такого вида:
https://наш-сервер.ru/download/directory-in-private-container
- Пользователь переходит по ссылке.
- Наш сервер выполняет запрос к API Облака Selectel с целью авторизации.
Передаем X-Auth-User и X-Auth-Key (логин и пароль пользователя) и получаем в ответ X-Auth-Token и X-Storage-Url (токен и базовый url хранилища).Подробнее об авторизации в документации.
- Далее наш сервер формирует запрос вида:
${STORAGE_URL}/container/object_prefix?download-all-as-zip
- Затем стримит его пользователю в браузер (в пакете Guzzle-HTTP для PHP эта опция называется stream (подробнее в документации Guzzle).
- Все довольны: мы своими средствами проверили что этот пользователь имеет право скачивать эти файлы, нигде не «засветили» логин и пароль от хранилища, без нагрузки на свой сервер отдали пользователю zip-архив нужной директории.
Самодельный вариант
Этот вариант упрощенно выглядит так:
- Пользователь захотел скачать архив.
- Наш сервер забирает нужные файлы из облака.
- Из файлов сервер формирует архив.
- Отдает архив пользователю.
Плюсы:
- можно использовать сжатие – пользователю меньше скачивать;
- можно сделать нужную структуру папок в архиве и добавить дополнительные файлы.
Минусы:
- надо писать код;
- нагрузка на сервер больше, чем с первым вариантом;
- много запросов к API Облака.
Есть два варианта действий:
- скачать все файлы к себе на сервер, заархивировать и потом отдать пользователю архив;
На мой взгляд это плохой вариант, особенно если у вас такие же большие (~1Гб) архивы как у нас.
- «на лету» собирать zip-архив и стримить пользователю.
Классный вариант, которым мы пользуемся сейчас.
Для реализации второго варианта мы воспользовались двумя библиотеками (PHP):
- argentcrusade/selectel-cloud-storage — работа с API Облака Selectel.
- maennchen/zipstream-php — архивация файлов «на лету» (обязательно используйте v1.0.0-alpha.1 ветку).
Ниже приведен фрагмент кода, который реализует скачивание файлов, архивацию «на лету» и стриминг результата пользователю:
<?php
/**
* Библиотека для работы с Облаком
*/
use ArgentCrusade\Selectel\CloudStorage\Api\ApiClient;
use ArgentCrusade\Selectel\CloudStorage\CloudStorage;
/**
* Библиотека для архивации файлов
*/
use ZipStream\ZipStream;
use ZipStream\Option\Archive as ArchiveOptions;
....
public function StreamArchive()
{
/**
* Название контейнера в Облаке
*/
$container_name = "private1";
/**
* Определяем объекты в контейнере Облака Selectel,
* которые нужно заархивировать
*/
$objects = [
'/dir1/file1.jpg',
'/dir1/file2.jpg',
'/dir1/file3.jpg',
'/dir1/file4.jpg',
'/dir1/file5.jpg',
'/dir1/file5.jpg',
'/dir2/video1.mov',
'/dir2/video2.mov',
'/dir2/video3.mov',
'/dir2/video4.mov'
];
/**
* Подключаемся к облаку (укажите свой логин/пароль)
* Выбираем контейнер
*/
try {
$apiClient = new ApiClient("12345_cloud_username", "123456789");
$storage = new CloudStorage($apiClient);
$container = $storage->getContainer($container_name);
} catch (\Throwable $e) {
throw new \Exception("Ошибка подключения к облаку. Причина: " . $e->getMessage());
}
/**
* Опции архиватора – смотрите в официальном репозитории
* https://github.com/maennchen/ZipStream-PHP/tree/v1.0.0-alpha.1
*/
$opt = new ArchiveOptions();
$opt->setDeflateLevel(2);
$opt->setZeroHeader(true);
$opt->setContentDisposition('attachment');
$opt->setContentType('application/octet-stream');
$opt->setHttpHeaderCallback('header');
$opt->setSendHttpHeaders(true);
/**
* Выходной поток
*/
$fd = fopen('php://output', 'w');
$opt->setOutputStream($fd);
$ZipStream = new ZipStream('archive.zip', $opt);
/**
* Включаем буферизацию вывода (
*/
ob_start();
/**
* Поочередно добавляем файлы в архив
*/
foreach ($objects as $i => $object) {
try {
$cloud_file_raw = $container->files()->find($object)->read();
$ZipStream->addFile($container_name . $object, $cloud_file_raw);
} catch (\Throwable $e) {
// Обработка ошибок
}
/**
* Опустошаем буфер
*/
ob_flush();
flush();
ob_clean();
}
$ZipStream->finish();
}
Не забудьте понаблюдать за расходом памяти: воспользуйтесь профайлером, либо просто добавьте нечто подобное в тело цикла.
file_put_contents('/tmp/mem.txt', $i . ' : ' . strval(memory_get_usage() / 1048576) . 'mb');
Если вы все правильно сделали, потребление памяти не должно увеличиваться. В нашем случае, при выкачке 2Gb архива расход памяти около 12Mb за все время работы скрипта.
Заключение
В конечном итоге мы получили удобную и простую систему для формирования и скачивания необходимого набора файлов в виде ZIP-архива с минимальным использованием ресурсов сервера.
Расскажите нам о своем опыте использования Облачного хранилища. Ждем вас в комментариях.