简体   繁体   English

wkhtmltopdf在Perl中使用STDIN和STDOUT与Email :: Mime

[英]wkhtmltopdf using STDIN and STDOUT in Perl with Email::Mime

I am trying to generate a PDF for an order receipt on-the-fly from HTML that is also generated on-the-fly, then email it to someone. 我正在尝试从HTML生成即时生成的订单收据的PDF,然后通过电子邮件将其发送给某人。

I really don't want to create a file, attach it to an email, then delete the file, so I'm trying to send the html to wkhtmltopdf via STDIN (from Perl) and then capture the PDF output from wkhtmltopdf in an email attachment using MIME::Lite Email::Mime. 我真的不想创建文件,将其附加到电子邮件中,然后删除该文件,所以我试图通过STDIN(来自Perl)将html发送到wkhtmltopdf,然后在电子邮件中捕获wkhtmltopdf的PDF输出使用 MIME :: Lite 电子邮件:: Mime的附件。

This absolutely works using Perl to allow people to download a dynamically-generated PDF file from my website, but trying to use it with MIME::Lite Email::Mime doesn't work. 使用Perl 绝对可以使用此功能,使人们可以从我的网站下载动态生成的PDF文件,但是尝试将其与 MIME :: Lite Email :: Mime一起使用无效。 (it probably would have worked, but since it's out of date, we're using Email::Mime instead) (它可能会起作用,但是由于它已经过时,我们改用Email :: Mime)

I'm absolutely sure that this is due to my lack of fundamental understanding of working with filehandles, pipes, backticks, and other less-oft used things, and I'd love to get a better grasp of these things. 我绝对可以肯定,这是由于我对使用文件句柄,管道,反引号和其他不常使用的东西缺乏基本的了解,所以我想更好地掌握这些东西。

Here's what works: 这是有效的方法:

#!/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;

You can run this as is from Apache, and it will present a download dialog to the user and download a readable, correct pdf named 'testPDF.pdf'. 您可以从Apache直接运行它,它将向用户显示一个下载对话框,并下载一个名为'testPDF.pdf'的可读,正确的pdf。

EDIT: The solution is the Capture::Tiny module (and Email::Mime ): 编辑:解决方案是Capture :: Tiny模块(和Email :: 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 <me@mydomain.com>',
      To   => 'customer@theirdomain.com',
      Subject => "Your order receipt is attached...",
  ],
  parts => [ @parts ],
);

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

This all works perfectly now. 现在所有这些都可以完美运行。

Most of the problem appears to be that wkhtmltopdf does not buffer the PDF output and send it line by line; 大部分问题似乎是wkhtmltopdf没有缓冲PDF输出并逐行发送; it immediately streams all of the PDF output to STDOUT as soon as it gets the HTML input from STDIN. 一旦从STDIN获得HTML输入,它将立即将所有PDF输出流式传输到STDOUT。

I think this is why I could not get open2 or open3 to work. 我认为这就是为什么我无法使open2或open3正常工作的原因。

I also tried open (my $pdfOutput, "echo \\"$htmlToPrint\\"| wkhtmltopdf - -|") , but this runs in the shell, so even with $htmlToPrint enclosed in quotes, the command chokes on the symbols used in the HTML. 我也尝试open (my $pdfOutput, "echo \\"$htmlToPrint\\"| wkhtmltopdf - -|") ,但这在外壳中运行,因此即使$htmlToPrint用引号引起来,命令$htmlToPrint HTML。

Hope someone finds this helpful... 希望有人觉得这有帮助...

You need to use open2 or open3 to send input to a cmd then gather its output without using backtick. 您需要使用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;

You could use more fresh alternatives to send emails: 您可以使用其他更新颖的方法来发送电子邮件:

  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 => 'casey@geeknest.com' ],
      parts      => [ @parts ],
  );
  # standard modifications
  $email->header_str_set( To            => rcpts()        );

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM