Отдача большого зипа из контекста REST-энкодера

Аватар пользователя marassa marassa 18 ноября 2018 в 19:47

Вопрос архитектурно-идеологического свойства имею Wink
Заканчиваю делать экспорт KML и KMZ. Делаю в виде дополнительного энкодера, который базируется на ядерном xml-энкодере и просто добавляет к списку доступных для REST-экспорта форматов KML и KMZ. В принципе все получается, только вот что непонятно:
Согласно спецификации интерфейса, функция encode возвращает результат своей деятельности в виде текстовой переменной. Последующая упаковка этого результата в http response и его отдача - не забота энкодера, что и правильно. Для kml-формата, который по сути текст и есть, так же как xml и json, с этим никаких проблем нет. Но KMZ - по сути zip-файл, который помимо kml-файла может содержать ещё кучу всякой увесистой мельтимудии. И создаётся он, естественно, не внутри php-переменной, а сторонней утилитой во внешнем файле. Я, конечно, могу потом засосать этот файл в php-переменную, что сейчас и делаю, и всё работает, но берут меня сомнения - правильный ли это подход с т.з. производительности и потенциального падения сервера на слишком большом файле?
Теоретически я могу, наверное, прямо в энкодере сформировать BinaryFileResponse и принудительно послать его, по факту прервав исполнение всего последующего кода, но это тоже как-то неизящно будет - ведь теоретически энкодер может быть вызван из какого-нибудь нестандартного контекста, где отсылка респонса вообще не предусматривается?
Как бы поступили вы, уважаемые гуру?

Лучший ответ

Аватар пользователя marassa marassa 22 ноября 2018 в 7:41
1

В общем, подумал и оставил как есть Wink
Меня смущали две вещи: "бинарность" данных, помещаемых в строковую переменную, и их потенциальный размер.
Прочтение документации по PHP показало, что размещение произвольных бинарных данных (включая 0x00) в строке абсолютно штатная ситуация для PHP (подозреваю, что все, кроме меня, это знали давно, но я же не настоящий сварщик Wink
С размером тоже все оказалось не так страшно: как выяснилось в результате экспериментов, единственная мельтимудия, которую гуглмапс согласен брать из kmz-файла, это иконки, а они маленькие, а с учётом того, что сам KML файл ужимается раз в десять, то размер kmz получается никак не больше исходного kml, который берется из той же переменной.
Мой код выложен на орг как 8.x-1.x-dev (https://www.drupal.org/project/kml/issues/2994088), кому надо - берите, пробуйте, комментируйте.
Всем спасибо за полезную дискуссию!

Комментарии

Аватар пользователя Orion76 Orion76 18 ноября 2018 в 21:49

имхо конечно, но rest должен отдавать именно "текст" в нужном формате (xml,json и т.п.)
а уже в этом тексте должны быть поля с "атрибутами" файла (ссылка, наименование и т.п.)

Аватар пользователя marassa marassa 18 ноября 2018 в 22:17

В данном случае от REST'а фактически используется только инфраструктура, на самом деле не представляю себе выдачу KML/KMZ именно в REST-контексте - речь идёт об отдаче пользователю файла, который он сохраняет.
Я плясал от модуля Views Data Export, он предназначен для экспорта данных представления в разных форматах (именно то, что мне нужно для выгрузки наборов геомаркеров), и он базируется именно на "REST Export" дисплее (а не Feed, как модуль KML для семёрки), видимо, потому, что для REST уже сделан мощный и легко расширяемый набор энкодеров. Кстати, у него ещё имеется плагин для экспорта в формате XLS, который тоже бинарный, и он тоже выдаётся через "текстовую" переменную.

Аватар пользователя Orion76 Orion76 19 ноября 2018 в 1:06
1

Бинарный формат отдает \Drupal\image\Controller\ImageStyleDownloadController::deliver

А там просто отдается контент файла и нужные заголовки.

для экспорта данных представления в разных форматах (именно то, что мне нужно для выгрузки наборов геомаркеров

У Вас один формат - xml
а xml views без всяких "помошников" умеет..
По аналогии с дисплеем Feed и стилем RSS

А вот как KML в зип завернуть перерасход ресурсов не получить, тут подумать надо..

если KML-файлы не слишком "динамичные", т.е. вьюс, который их формирует не имеет большое кол-во экспозед и контекстных фильтров, можно подумать как кэшировать эти файлы на диске.
Например по аналогии с тем же image-style
При первом запросе оригинальное изображение "стилизуется" и сохраняется на диск, а при последующих запросах стилизованное изображение отдается уже вэб-сервером(nginx и т.п.) сразу с диска.

Не знаю, какая у вас "логика" работы пользователей с KML-KMZ файлами, поэтому пофантазирую-)

я бы сделал в папке files 2 папки: kml и kmz

при первом запросе kml-файла создавал бы папку files/kml/[идентификатор запроса]
кидал бы в нее сам файл kml и симлинки на ресурсы
и отдавал по аналогии с \Drupal\image\Controller\ImageStyleDownloadController::deliver
kml-файл пользователю

при первом запросе kmz файла зиповал бы папку
files/kml/[идентификатор запроса] в файл
files/kmz/[идентификатор запроса].kmz
и отдавал kmz файл пользователю

пути ссылок на файлы должны соответствовать их физическому пути на диске

останется только настроить web-сервер, чтобы при наличии файла на диске он отдавал их напрямую.

как-то так-)

Аватар пользователя marassa marassa 19 ноября 2018 в 9:54

Orion76 wrote:

Бинарный формат отдает \Drupal\image\Controller\ImageStyleDownloadController::deliver
А там просто отдается контент файла и нужные заголовки.

Да его много кто отдает, только не из нужного мне контекста Wink Тому контроллеру request аж в параметрах передается на растерзание. А энкодеру с реквестом хозяйничать по рангу не положено Wink

Orion76 wrote:

У Вас один формат - xml

Ну это и так, и не так. Для формата HAL специальный модуль написан, хотя тоже можно сказать, что нет такого формата, а есть JSON. KML содержит совершено определенные теги, которые мой энкодер и помогает правильно заполнить, хотя парентом у него конечно XML-энкодер.

Orion76 wrote:

xml views без всяких "помошников" умеет..

По аналогии с дисплеем Feed и стилем RSS

Я долго думал - на Feed мне ориентироваться или на REST Export/serialization. Семерочный модуль KML сделан на Feed, причем там генерация KML сделана совсем "на коленке", через шаблоны, даже без использования стандартных xml-инструментов. Но я семерку совсем не знаю (и, если честно, не очень хочу знать Wink и мне не хотелось в нее вникать, чтобы перетащить семерочный модуль на восьмерку. А Views Data Export, базирующийся на ядерных rest и serialization, делает практически то, что мне нужно, причем уже с использованием современных восьмерочных инструментов, вот я на него и сориентировался. Я, кстати, не уверен, что в контексте дисплея Feed было бы проще отдать бинарный файл, там тоже

    $output = (string) $renderer->renderRoot($build);
    $response->setContent($output);

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

Orion76 wrote:

если KML-файлы не слишком "динамичные", т.е. вьюс, который их формирует не имеет большое кол-во экспозед и контекстных фильтров, можно подумать как кэшировать эти файлы на диске

В моем случае не вариант - файлы генерируются для каждого города (коих сотни), а в перспективе будет возможность экспортировать всё что попало в произвольное окошко карты по boundary фильтру.
Тут наверное нужно уточнить: меня-то вполне устраивает то, что получилось, просто хотел поделиться модулем с сообществом (когда добью до конца), и решил заранее провентилировать не слишком ли срамное решение выбрал Wink

Аватар пользователя gun_dose gun_dose 19 ноября 2018 в 10:09
1

marassa wrote:

Я долго думал - на Feed мне ориентироваться или на REST Export/serialization.

По своему опыту скажу, что ни Feeds, ни Migrations в восьмёрке лучше не использовать как основу для какого-то специфического или ключевого функционала, ибо основные фишки этих модулей - глючность, непроизводительность и могут отваливаться после каждого обновления ядра.

По поводу задачи - KML-формат тянет на то, чтобы быть REST-эндпоинтом. KMZ больше похож на контроллер. Архитектурно это совершенно разные вещи. Но интуитивно понятно, что по большей части функционал схож. Поэтому логичнее всего воткнуть этот функционал в сервис, чтобы обращаться к нему из рест-плагина и из контроллера. Можно ещё попробовать использовать трейт, но мне кажется, что в данной ситуации он не подойдёт.

Аватар пользователя marassa marassa 19 ноября 2018 в 10:19

gun_dose wrote:

По своему опыту скажу, что ни Feeds, ни Migrations в восьмёрке лучше не использовать как основу для какого-то специфического или ключевого функционала

Мне почему-то интуитивно так и казалось - спасибо за подтверждение Wink
gun_dose wrote:

KML-формат тянет на то, чтобы быть REST-эндпоинтом. KMZ больше похож на контроллер. Архитектурно это совершенно разные вещи. Но интуитивно понятно, что по большей части функционал схож.

В том-то и дело, что и функционал схож, и код схож, и с точки зрения юзера это просто две ссылки, стоящие рядом - скачать как kml и скачать как kmz.
gun_dose wrote:

логичнее всего воткнуть этот функционал в сервис, чтобы обращаться к нему из рест-плагина и из контроллера

Подумаю эту мысль, спасибо.

Аватар пользователя marassa marassa 22 ноября 2018 в 7:41
1

В общем, подумал и оставил как есть Wink
Меня смущали две вещи: "бинарность" данных, помещаемых в строковую переменную, и их потенциальный размер.
Прочтение документации по PHP показало, что размещение произвольных бинарных данных (включая 0x00) в строке абсолютно штатная ситуация для PHP (подозреваю, что все, кроме меня, это знали давно, но я же не настоящий сварщик Wink
С размером тоже все оказалось не так страшно: как выяснилось в результате экспериментов, единственная мельтимудия, которую гуглмапс согласен брать из kmz-файла, это иконки, а они маленькие, а с учётом того, что сам KML файл ужимается раз в десять, то размер kmz получается никак не больше исходного kml, который берется из той же переменной.
Мой код выложен на орг как 8.x-1.x-dev (https://www.drupal.org/project/kml/issues/2994088), кому надо - берите, пробуйте, комментируйте.
Всем спасибо за полезную дискуссию!