Как организовать скачивание файлов в Catalyst-приложении. Работа с форматами csv, tsv, txt, xml в Catalyst. Использование модуля Catalyst::View::Download . Добавление собственных форматов файлов.
Достаточно часто встречаются ситуации, когда необходимо позволить пользователю скачать файл с сайта. Например, прайс-листы в xls-формате, шаблоны договоров, отчеты о продажах в csv, бланки квитанций для платежей и т.п.
Catalyst::View::Download - создает view, который позволяет скачивать данные в различных форматах. Поддерживается работа с форматами: txt, csv, xml и html.
1. Устанавливаем модуль Catalyst::View::Download
|
1 |
force install Catalyst::View::Download |
2. Создаем новое view
|
1 2 3 4 5 |
# perl script/app_create.pl view Download Download exists "C:\Documents and Settings\natalie\www\app\app\lib\app\View" exists "C:\Documents and Settings\natalie\www\app\app\t" created "C:\Documents and Settings\natalie\www\app\app\lib\app\View\Download.pm" created "C:\Documents and Settings\natalie\www\app\app\t\view_Download.t" |
3. Используем новое view для скачивания данных в txt, xml, html и csv форматах.
Файл в csv-формате Catalyst::View::Download генерирует сам, достаточно передать ему подготовленный массив с данными.
lib/app/Controller/Admin/PaymentReports.pm :
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
package app::Controller::Admin::PaymentReports; use Moose; use namespace::autoclean; use utf8; BEGIN { extends 'Catalyst::Controller'; } sub payment_reports_csv :Path('/admin/payment_reports_csv') :Args(0) { my ( $self, $c ) = @_; my @data; push(@data,['Наименование', 'Сумма','Дата продажи']); push(@data,['Коляска дет., арт.45666', '3200','12.09.2014']); push(@data,['Набор игрушек, арт.67687', '320','12.09.2014']); push(@data,['Набор для творчества, арт.23221', '150','12.09.2014']); $c->stash->{'download'} = 'text/csv'; $c->stash->{'outfile_name'} = 'paymentreport'; $c->stash->{'csv'} = \@data; $c->forward('View::Download'); } sub get_blank_txt :Path('/admin/get_blank_txt') :Args(0) { my ( $self, $c ) = @_; my $blank = <<EOF; Квитанция № "____"___________________20__г. Учреждение___________________________________ _____________________________________________ Место нахождения_____________________________ Принято от___________________________________ (фио) В уплату_____________________________________ (вид продукции, услуги, работы) Сумма, всего_________________________________ _________________________________руб.____коп. Получил____________ ___________ _____________ (должность) (подпись) (расшифровка) Уплатил__________ "____"___________20__г. (подпись) EOF $c->stash->{'download'} = 'text/plain'; $c->stash->{'outfile_name'} = 'paymentblank'; $c->stash->{'plain'} = $blank; $c->forward('View::Download'); } sub get_blank_html :Path('/admin/get_blank_html') :Args(0) { my ( $self, $c ) = @_; my $blank = qq{<!DOCTYPE html><html><body> <b>Квитанция №</b></body></html>}; $c->stash->{'download'} = 'text/html'; $c->stash->{'outfile_name'} = 'paymentblank'; $c->stash->{'html'} = $blank; $c->forward('View::Download'); } sub get_price_xml :Path('/admin/get_price_xml') :Args(0) { my ( $self, $c ) = @_; my $blank = qq{ <ApiRequest xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <ApiKey>76544999</ApiKey> <Data> <Row> <Name>Position 1</Name> <Cost>45.33$</Cost> </Row> <Row> <Name>Position 2</Name> <Cost>22.80$</Cost> </Row> </Data> </ApiRequest> }; $c->stash->{'download'} = 'text/xml'; $c->stash->{'outfile_name'} = 'price'; $c->stash->{'xml'} = $blank; $c->forward('View::Download'); } __PACKAGE__->meta->make_immutable; 1; |
4. Запускаем сервер
Вводим в браузере http://localhost:3000/admin/payment_reports_csv .
Нам будет предложено сохранить paymentreport.csv файл , или сразу открыть его с помощью какой-нибудь программы.
Для получения файлов с данными в других форматах, надо использовать адреса:
- http://localhost:3000/admin/get_price_xml - файл с именем price.xml
- http://localhost:3000/admin/get_blank_html - файл с именем paymentblank.html
- http://localhost:3000/admin/get_blank_txt - файл с именем paymentblank.txt
Добавление собственных форматов файлов
Допустим, мы хотим использовать Catalyst::View::Download для того, чтобы пользователи могли скачивать файлы в форматах, отличных от предлагаемых.
Например, часто требуется отдавать данные в pdf или doc форматах.
Добавить поддержку новых форматов в Catalyst::View::Download можно самостоятельно. Для этого достаточно немного изучить исходный код модулей Catalyst::View::Download и Catalyst::View::Download::Plain.
Для того, чтобы позволить клиенту скачивать файлы в формате doc, создаем модуль
app::View::Download::Doc. Содержимое можно просто скопировать из модуля Catalyst::View::Download::Plain, а вставки plain заменить на doc. "Content-Type" указать соответствующий для doc-файлов (можно посмотреть в таблицах mime-типов).
Созданный модуль позволит скачивать файлы doc-файлы. Doc-файл может быть создан заранее с помощью OpenOffice (и т.п.), либо генерироваться отдельными методами catalyst-приложения.
lib/app/View/Download/Doc.pm :
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
package app::View::Download::Doc; use Moose; use namespace::autoclean; extends 'Catalyst::View'; our $VERSION = "0.01"; $VERSION = eval $VERSION; __PACKAGE__->config( 'stash_key' => 'doc' ); sub process { my $self = shift; my ($c) = @_; my $template = $c->stash->{ 'template' }; my $content = $self->render( $c, $template, $c->stash ); $c->res->headers->header( "Content-Type" => "application/vnd. openxmlformats-officedocument.wordprocessingml.document" ) if ( $c->res->headers->header( "Content-Type" ) eq "" ); $c->res->body( $content ); } sub render { my $self = shift; my ( $c, $template, $args ) = @_; my $stash_key = $self->config->{ 'stash_key' }; my $content = $c->stash->{ $stash_key } || $c->response->body; return $content; } __PACKAGE__->meta->make_immutable; 1; |
После того, как создан модуль app::View::Download::Doc, прописываем его в конфиге модуля Catalyst::View::Download .
lib/app/View/Download.pm :
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
package app::View::Download; use Moose; use namespace::autoclean; extends 'Catalyst::View::Download'; __PACKAGE__->config->{'content_type'}{'application/vnd.openxmlformats- officedocument.wordprocessingml.document'} = { outfile_ext => 'doc', module => '+Download::Doc' }; __PACKAGE__->meta->make_immutable; 1; |
После этого создаем модуль, или просто подпрограмму, которая будет обрабатывать запрос от пользователя, на скачивание doc-файла. При необходимости, можно генерить файл самостоятельно, в рамках catalyst-приложения, но в данном примере, мы просто берем готовый doc-файл, считываем его и передаем клиенту.
lib/app/Controller/Admin/Partner.pm :
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
package app::Controller::Admin::Partner; use Moose; use namespace::autoclean; BEGIN { extends 'Catalyst::Controller'; } sub get_oferta :Path('/admin/partner/get_oferta') :Args(0) { my ( $self, $c ) = @_; my $filename = '/oferta.doc'; open(my $fh, '<;', $filename) or die "Can't open file: $!"; binmode($fh); local $/; my $content = <$fh>; close($fh); $c->stash->{'download'} = 'application/vnd.openxmlformats-office document.wordprocessingml.document'; $c->stash->{'outfile_name'} = 'oferta_for_partners'; $c->stash->{'doc'} = $content; $c->forward('View::Download'); } __PACKAGE__->meta->make_immutable; 1; |
После того, как пользователь введет в браузере http://localhost:3000/admin/partner/get_oferta, ему будет предложено сохранить файл под именем oferta_for_partners.doc .
Полезные ссылки
search.cpan.org: Catalyst::View::Download
А что произойдет с этой поделкой, если качать будет медленный клиент? Например, качать по 1-му байту в 10 секунд.
Предвижу, что процесс каталиста будет отрабатывать только этого клиента. А сколько у вас в проекте процессов каталиста - штук 10-15. Получается пишем медленных клиентов штук 10-15 и вешаем ваш сайт задешево. Как-то так
Андрей, я не рассматривала уровень быстродействия данного решения. Возможно потому, что в тех условиях, где его использовала я - это не было необходимо. А конкретнее: административные интерфейсы, крайне редкая потребность (2-4 раза в месяц) скачать файлы с отчетами.
Для подобных задач - заниматься оптимизацией, быстродействием, профайлингом - просто не актуально. Если бы речь шла о файловом хранилище - это было бы совершенно другое дело.
Я думаю, человек, который захочет что-то реализовать, в любом случае будет САМ анализировать свои потребности и принимать решение - как именно он будет решать задачу. Я же просто делюсь опытом.