Download as ZIP

Сегодня в нашем блоге гостевой пост — о своем опыте использования нашего облачного хранилища расскажет Андрей Суржиков, разработчик в одной из медицинских компаний.

Мы храним в облаке 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).

Сценарий действий такой:

  1. Пользователь хочет скачать заархивированную папку из приватного контейнера.
  2. Мы даем ему ссылку примерно такого вида:

    https://наш-сервер.ru/download/directory-in-private-container

  3. Пользователь переходит по ссылке.
  4. Наш сервер выполняет запрос к API Облака Selectel с целью авторизации.

    Передаем X-Auth-User и X-Auth-Key (логин и пароль пользователя) и получаем в ответ X-Auth-Token и X-Storage-Url (токен и базовый url хранилища).Подробнее об авторизации в документации.

  5. Далее наш сервер формирует запрос вида:

    ${STORAGE_URL}/container/object_prefix?download-all-as-zip

  6. Затем стримит его пользователю в браузер (в пакете Guzzle-HTTP для PHP эта опция называется stream (подробнее в документации Guzzle).
  7. Все довольны: мы своими средствами проверили что этот пользователь имеет право скачивать эти файлы, нигде не «засветили» логин и пароль от хранилища, без нагрузки на свой сервер отдали пользователю zip-архив нужной директории.

Самодельный вариант

Этот вариант упрощенно выглядит так:

  1. Пользователь захотел скачать архив.
  2. Наш сервер забирает нужные файлы из облака.
  3. Из файлов сервер формирует архив.
  4. Отдает архив пользователю.

Плюсы:

  • можно использовать сжатие – пользователю меньше скачивать;
  • можно сделать нужную структуру папок в архиве и добавить дополнительные файлы.

Минусы:

  • надо писать код;
  • нагрузка на сервер больше, чем с первым вариантом;
  • много запросов к API Облака.

Есть два варианта действий:

  • скачать все файлы к себе на сервер, заархивировать и потом отдать пользователю архив;

    На мой взгляд это плохой вариант, особенно если у вас такие же большие (~1Гб) архивы как у нас.

  • «на лету» собирать zip-архив и стримить пользователю.

    Классный вариант, которым мы пользуемся сейчас.

Для реализации второго варианта мы воспользовались двумя библиотеками (PHP):

  1. argentcrusade/selectel-cloud-storage — работа с API Облака Selectel.
  2. 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-архива с минимальным использованием ресурсов сервера.

Расскажите нам о своем опыте использования Облачного хранилища. Ждем вас в комментариях.

Что еще почитать по теме

T-Rex 30 марта 2021

Что такое SMTP-протокол и как он устроен?

SMTP (Simple Mail Transfer Protocol) — протокол передачи почты. Он был представлен еще в 1982 году, но не теряет актуальности до сих пор. В статье разбираемся, какие задачи решает протокол и как он ра…
T-Rex 30 марта 2021
Владимир Туров 1 сентября 2020

Дело совершенно секретного iPod

Это был обычный серый день в конце 2005 года. Я сидел на рабочем месте и писал код для следующей версии iPod. Вдруг без стука ворвался директор ПО для iPod, начальник моего начальника, и закрыл дверь.
Владимир Туров 1 сентября 2020
T-Rex 21 августа 2020

TrendForce: цены на SSD упадут

Эксперты DRAMeXchange предсказывают значительное падение цен на оперативную память и твердотельные накопители в ближайшее время. Причина — сокращение спроса на чипы для NAND и DRAM.
T-Rex 21 августа 2020

Новое в блоге

Михаил Фомин 24 июня 2022

Docker Swarm VS Kubernetes — как бизнес выбирает оркестраторы

Рассказываем, для каких задач бизнесу больше подойдет Docker Swarm, а когда следует выбрать Kubernetes.
Михаил Фомин 24 июня 2022
Ульяна Малышева 30 сентября 2022

«Нулевой» локальный диск. Как мы запустили облако только с сетевыми дисками и приручили Ceph

Чем хороши сетевые диски и почему именно Ceph, рассказал директор по развитию ядра облачной платформы Иван Романько.
Ульяна Малышева 30 сентября 2022
Валентин Тимофеев 30 сентября 2022

Как проходит онбординг сотрудников ИТО? Что нужно, чтобы выйти на смену в дата-центр

Рассказываем, как обучаем новых сотрудников, какие задачи и испытания проходят инженеры прежде, чем выйти на свою первую смену.
Валентин Тимофеев 30 сентября 2022
T-Rex 28 сентября 2022

Книги по SQL: что почитать новичкам и специалистам

Собрали 6 книг, которые помогут на старте изучения SQL и при углублении в тему.
T-Rex 28 сентября 2022