【问题标题】:wkhtmltopdf using STDIN and STDOUT in Perl with Email::Mimewkhtmltopdf 在 Perl 中使用 STDIN 和 STDOUT 和 Email::Mime
【发布时间】:2013-01-30 02:27:45
【问题描述】:

我正在尝试从同样动态生成的 HTML 动态生成订单收据的 PDF,然后通过电子邮件将其发送给某人。

我真的不想创建文件,将其附加到电子邮件,然后删除文件,所以我试图通过 STDIN(来自 Perl)将 html 发送到 wkhtmltopdf,然后从 wkhtmltopdf 捕获 PDF 输出在电子邮件附件中使用 MIME::Lite Email::Mime.

绝对有效使用 Perl 允许人们从我的网站下载动态生成的 PDF 文件,但尝试将它与 MIME::Lite 一起使用电子邮件::咪咪没用。 (它可能会起作用,但由于它已经过时,我们使用的是 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 中按原样运行它,它会向用户显示一个下载对话框,并下载一个名为“testPDF.pdf”的可读、正确的 pdf。

编辑:解决方案是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;

现在一切正常。

大部分问题似乎是wkhtmltopdf没有缓冲PDF输出并逐行发送;一旦从 STDIN 获得 HTML 输入,它就会立即将所有 PDF 输出流式传输到 STDOUT。

我认为这就是我无法让 open2 或 open3 工作的原因。

我也试过open (my $pdfOutput, "echo \"$htmlToPrint\"| wkhtmltopdf - -|"),但它在shell中运行,所以即使$htmlToPrint用引号括起来,该命令也会阻塞HTML中使用的符号。

希望有人觉得这很有帮助...

【问题讨论】:

  • MIME::Lite文档的第一句建议不要使用MIME::Lite
  • 感谢@jordanm,我花了很长时间阅读文档,但不知何故错过了。
  • 我用工作代码更新了这篇文章。我相信MIME::Lite 也可以,但我认为使用更新的推荐Email::MIME 是一件好事。问题是您不能与管道进行双向通信;你需要一个像Capture::Tiny 这样的模块。电子邮件附件也必须是base64 编码才能工作。尝试binaryquoted-printable 无效。

标签: perl stdout stdin wkhtmltopdf filehandle


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

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

【讨论】:

  • 这可以使用反引号来完成吗?如果是这样,为什么您会选择 open3 而不是反引号,反之亦然?
  • 不可能 w。反引号。您希望将内容流式传输到 STDIN,并且希望在不使用临时文件的情况下捕获其输出。也许你可以这样做: open(OUT,"echo $html| wkhtmltopdf - -|") 或者死;但它不适用于大文件。
  • 我会试试看的。 open2 和 open3 目前似乎都不起作用;我认为这是因为 wkhtmltopdf 流输出而不是一次写出一行...
  • 好的,我解决了这个问题,但我认为它需要一个新的答案。您的Email::MIME 建议很棒。 open(OUT,"echo $html| wkhtmltopdf - -|") 适用于 IO,但由于它使用 shell,它会阻塞 HTML 中的特殊字符,因此不能用于此。您的 open2/open3 建议让我想到了这个问题:link,这让我想到了Capture::Tiny 程序,它解决了我的问题。我已经更新了上面的代码。
  • 我在原始问题上发布了工作代码。编辑您的帖子并接受它作为答案会更有帮助,还是让我发布一个单独的答案并接受它?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-03-11
  • 2012-03-08
  • 2019-05-05
  • 2013-05-22
  • 2017-07-14
  • 2012-01-19
  • 2019-03-30
相关资源
最近更新 更多