wkhtmltopdf menggunakan STDIN dan STDOUT di Perl dengan Email::Mime

Saya mencoba membuat PDF untuk tanda terima pesanan dengan cepat dari HTML yang juga dibuat dengan cepat, lalu mengirimkannya melalui email ke seseorang.

Saya benar-benar tidak ingin membuat file, melampirkannya ke email, lalu menghapus file tersebut, jadi saya mencoba mengirim html ke wkhtmltopdf melalui STDIN (dari Perl) dan kemudian menangkap output PDF dari wkhtmltopdf di email lampiran menggunakan MIME::Lite Email::Mime.

Ini benar-benar berfungsi menggunakan Perl untuk memungkinkan orang mengunduh file PDF yang dihasilkan secara dinamis dari situs web saya, tetapi mencoba menggunakannya dengan MIME::Lite Email::Mime tidak berhasil tidak berfungsi. (mungkin akan berhasil, tapi karena sudah ketinggalan zaman, kami menggunakan Email::Mime sebagai gantinya)

Saya benar-benar yakin bahwa hal ini disebabkan oleh kurangnya pemahaman mendasar saya dalam bekerja dengan filehandle, pipa, backticks, dan hal-hal lain yang jarang digunakan, dan saya ingin lebih memahami hal-hal ini.

Inilah yang berhasil:

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

Anda dapat menjalankan ini apa adanya dari Apache, dan ini akan menampilkan dialog pengunduhan kepada pengguna dan mengunduh pdf yang benar dan dapat dibaca bernama 'testPDF.pdf'.

EDIT: Solusinya adalah modul Capture::Tiny (dan 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 <[email protected]>',
      To   => '[email protected]',
      Subject => "Your order receipt is attached...",
  ],
  parts => [ @parts ],
);

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

Ini semua berfungsi dengan baik sekarang.

Sebagian besar masalahnya adalah wkhtmltopdf tidak menyangga keluaran PDF dan mengirimkannya baris demi baris; itu segera mengalirkan semua keluaran PDF ke STDOUT segera setelah mendapat masukan HTML dari STDIN.

Saya rasa inilah sebabnya saya tidak dapat mengaktifkan open2 atau open3.

Saya juga mencoba open (my $pdfOutput, "echo \"$htmlToPrint\"| wkhtmltopdf - -|"), tetapi ini berjalan di shell, jadi meskipun $htmlToPrint diapit tanda kutip, perintah tersebut tersedak pada simbol yang digunakan dalam HTML.

Semoga seseorang menganggap ini bermanfaat...


person waldo22    schedule 30.01.2013    source sumber
comment
Kalimat pertama dari dokumentasi MIME::Lite merekomendasikan untuk tidak menggunakan MIME::Lite.   -  person jordanm    schedule 30.01.2013
comment
terima kasih @jordanm, saya menghabiskan waktu lama membaca dokumentasinya, tetapi entah bagaimana melewatkannya.   -  person waldo22    schedule 30.01.2013
comment
Saya memperbarui posting ini dengan kode yang berfungsi. Saya yakin MIME::Lite akan berhasil juga, tetapi menurut saya menggunakan Email::MIME yang lebih baru dan direkomendasikan adalah Hal yang Baik. Masalahnya adalah Anda tidak dapat melakukan komunikasi dua arah dengan pipa; Anda memerlukan modul seperti Capture::Tiny. Lampiran email juga harus dikodekan base64 agar dapat berfungsi. Mencoba binary dan quoted-printable tidak berpengaruh.   -  person waldo22    schedule 02.02.2013


Jawaban (1)


Anda perlu menggunakan open2 atau open3 untuk mengirim masukan ke cmd lalu mengumpulkan hasilnya tanpa menggunakan mundur.

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;

Anda dapat menggunakan alternatif yang lebih baru untuk mengirim email:

  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
Bisakah ini dilakukan dengan menggunakan backtick? Jika demikian, mengapa Anda memilih open3 daripada backticks atau sebaliknya? - person waldo22; 31.01.2013
comment
Tidak mungkin w. mundur. Anda ingin melakukan streaming konten ke STDIN dan ingin mengambil outputnya tanpa menggunakan file sementara. Mungkin Anda bisa melakukan sesuatu seperti ini: open(OUT,echo $html| wkhtmltopdf - -|) or die; Tapi itu tidak akan berfungsi untuk file besar. - person user1126070; 31.01.2013
comment
Saya akan mencobanya. Baik open2 maupun open3 tampaknya tidak berfungsi saat ini; Saya pikir itu karena wkhtmltopdf mengalirkan keluarannya alih-alih menuliskannya satu baris pada satu waktu... - person waldo22; 01.02.2013
comment
Oke, saya sudah menyelesaikannya, tapi menurut saya ini perlu jawaban baru. Saran Email::MIME Anda sangat bagus. open(OUT,"echo $html| wkhtmltopdf - -|") berfungsi untuk IO, tetapi karena menggunakan shell, karakter khusus dalam HTML akan tersedak, sehingga tidak dapat digunakan untuk ini. Saran open2/open3 Anda mengarahkan saya ke pertanyaan ini: link, yang mengarahkan saya ke Capture ::Program kecil, yang memecahkan masalah saya. Saya telah memperbarui kode saya di atas. - person waldo22; 02.02.2013
comment
Saya memposting kode kerja pada pertanyaan awal saya. Apakah akan lebih bermanfaat jika mengedit postingan Anda dan menerimanya sebagai jawaban, atau bagi saya untuk memposting jawaban terpisah dan menerimanya? - person waldo22; 02.02.2013