wkhtmltopdf с использованием STDIN и STDOUT в Perl с Email::Mime

Я пытаюсь создать PDF-файл для получения заказа "на лету" из HTML, который также генерируется "на лету", а затем отправить его кому-нибудь по электронной почте.

Я действительно не хочу создавать файл, прикреплять его к электронному письму, а затем удалять файл, поэтому я пытаюсь отправить html в wkhtmltopdf через STDIN (из Perl), а затем захватить вывод PDF из wkhtmltopdf по электронной почте вложение с использованием MIME::Lite Email::Mime.

Это абсолютно работает при использовании Perl, позволяя людям загружать динамически сгенерированный PDF-файл с моего веб-сайта, но при попытке использовать его с MIME::Lite Email::Mime не работает. т работать. (это, вероятно, сработало бы, но поскольку оно устарело, вместо него мы используем Email::Mime)

Я абсолютно уверен, что это связано с отсутствием у меня фундаментального понимания работы с файловыми дескрипторами, конвейерами, обратными кавычками и другими редко используемыми вещами, и я хотел бы лучше понять эти вещи.

Вот что работает:

#!/usr/bin/perl
#### takes string containing HTML and outputs PDF to browser to download
#### (otherwise would output to STDOUT)

print "Content-Disposition: attachment; filename='testPDF.pdf'\n";
print "Content-type: application/octet-stream\n\n";

my $htmlToPrint = "<html>a bunch of html</html>";

### open a filehandle and pipe it to wkhtmltopdf
### *the arguments "- -" tell wkhtmltopdf to get 
###  input from STDIN and send output to STDOUT*
open(my $makePDF, "|-", "wkhtmltopdf", "-", "-") || die("$!");
print $makePDF $htmlToPrint;  ## sends my HTML to wkhtmltopdf which streams immediately to STDOUT

exit 1;

Вы можете запустить это как есть из Apache, и он представит пользователю диалоговое окно загрузки и загрузит читаемый правильный PDF-файл с именем «testPDF.pdf».

РЕДАКТИРОВАТЬ: Решением является модуль Capture::TinyEmail::Mime):

#!/usr/bin/perl
use Capture::Tiny qw( capture );
use Email::Sender::Simple;
use Email::MIME::Creator;

my $htmlToPrint = "<html>a bunch of html</html>";

### it's important to capture STDERR as well, since wkhtmltopdf outputs
### its console messages on STDERR instead of STDOUT, so it can output
### the PDF to STDOUT; otherwise it will spam your error log    
(my $pdfstream, my $consoleOutput, my @retvals) = capture {
    open(my $makePDF, "|-", "wkhtmltopdf", "-", "-") || die("$!");
    print $makePDF $htmlToPrint;
};

my @parts = (
Email::MIME->create(
    attributes => {
        content_type => "text/plain",
        disposition  => "inline",
        charset      => "US-ASCII",
        encoding     => "quoted-printable",
    },
    body_str => "Your order receipt is attached as a PDF.",
),
Email::MIME->create(
    attributes => {
        filename     => "YourOrderReceipt.pdf",
        content_type => "application/pdf",
        disposition  => "attachment",
        encoding     => "base64",  ## base64 is ESSENTIAL, binary and quoted-printable do not work!
        name         => "YourOrderReceipt.pdf",
    },
    body => $pdfstream,
),
);

my $email = Email::MIME->create(
  header_str => [
      From => 'Some Person <[email protected]>',
      To   => '[email protected]',
      Subject => "Your order receipt is attached...",
  ],
  parts => [ @parts ],
);

Email::Sender::Simple->send($email);
exit 1;

Все это теперь отлично работает.

Большая часть проблемы заключается в том, что wkhtmltopdf не буферизует вывод PDF и не отправляет его построчно; он немедленно передает весь вывод PDF в STDOUT, как только получает ввод HTML из STDIN.

Я думаю, именно поэтому я не мог заставить работать open2 или open3.

Я также пробовал open (my $pdfOutput, "echo \"$htmlToPrint\"| wkhtmltopdf - -|"), но это выполняется в оболочке, поэтому даже с $htmlToPrint, заключенным в кавычки, команда подавляется символами, используемыми в HTML.

Надеюсь, кто-то найдет это полезным...


person waldo22    schedule 30.01.2013    source источник
comment
В первом предложении документации MIME::Lite рекомендуется не использовать MIME::Lite.   -  person jordanm    schedule 30.01.2013
comment
спасибо @jordanm, я долго читал документацию, но как-то пропустил это.   -  person waldo22    schedule 30.01.2013
comment
Я обновил этот пост с рабочим кодом. Я считаю, что MIME::Lite тоже сработало бы, но я думаю, что использование более нового, рекомендованного Email::MIME — это хорошо. Проблема заключалась в том, что вы не можете осуществлять двустороннюю связь с помощью каналов; вам нужен модуль типа Capture::Tiny. Вложение электронной почты также должно было быть закодировано base64, чтобы работать. Пробовал binary и quoted-printable безрезультатно.   -  person waldo22    schedule 02.02.2013


Ответы (1)


Вам нужно использовать open2 или open3, чтобы отправить ввод в cmd, а затем собрать его вывод без использования обратная галочка.

local(*HIS_IN, *HIS_OUT, *HIS_ERR);
my $pid = open3(*HIS_IN, *HIS_OUT, *HIS_ERR,'wkhtmltopdf', '-', '-');
waitpid( $pid, 0 );
my $child_exit_status = $? >> 8;

Вы можете использовать более свежие альтернативы для отправки электронных писем:

  use Email::MIME::Creator;
  use IO::All;

  # multipart message
  my @parts = (
      Email::MIME->create(
          attributes => {
              filename     => "report.pdf",
              content_type => "application/pdf",
              encoding     => "quoted-printable",
              name         => "2004-financials.pdf",
          },
          #body => io( *HIS_OUT )->all, it may work
          body => *HIS_OUT,

      ),
      Email::MIME->create(
          attributes => {
              content_type => "text/plain",
              disposition  => "attachment",
              charset      => "US-ASCII",
          },
          body_str => "Hello there!",
      ),
  );

  my $email = Email::MIME->create(
      header_str => [ From => '[email protected]' ],
      parts      => [ @parts ],
  );
  # standard modifications
  $email->header_str_set( To            => rcpts()        );

  use Email::Sender::Simple;
  Email::Sender::Simple->send($email);
person user1126070    schedule 30.01.2013
comment
Можно ли это сделать с помощью обратных кавычек? Если да, то почему вы выбрали open3 вместо обратных кавычек или наоборот? - person waldo22; 31.01.2013
comment
Не возможно ж. обратная галочка. Вы хотите передать содержимое в STDIN и зафиксировать его вывод без использования временных файлов. Возможно, вы могли бы сделать что-то вроде этого: open(OUT,echo $html| wkhtmltopdf - -|) or die; Но это не будет работать для больших файлов. - person user1126070; 31.01.2013
comment
Я попробую. Ни open2, ни open3 в данный момент не работают; Я думаю, это потому, что wkhtmltopdf передает вывод вместо того, чтобы записывать его по одной строке за раз... - person waldo22; 01.02.2013
comment
Хорошо, я решаю это, но думаю, что нужен новый ответ. Ваше Email::MIME предложение было отличным. open(OUT,"echo $html| wkhtmltopdf - -|") работает для ввода-вывода, но поскольку он использует оболочку, он будет подавлять специальные символы в HTML, поэтому его нельзя использовать для этого. Ваши предложения open2/open3 привели меня к этому вопросу: ссылка, которая привела меня к Capture ::Маленькая программа, которая решила мою проблему. Я обновил свой код выше. - person waldo22; 02.02.2013
comment
Я разместил рабочий код на свой исходный вопрос. Было бы полезнее отредактировать ваш пост и принять его как ответ, или мне опубликовать отдельный ответ и принять его? - person waldo22; 02.02.2013