How can I provide a temp file download using mod_perl and CGI::Application?
Asked Answered
Z

3

5

My web app runs on Apache mod_perl using CGI::Application. I want to provide a download of a generated file. In the past (before we were using mod_perl and CGI::App) I just spooled out a csv file to STDOUT as it was generated. Now I'm shooting for a little more refinement - creating an Excel spreadsheet using Spreadsheet::WriteExcel - and I can't seem it to get to print it out from the file handle.

sub export_list {
  my $self = shift;

  binmode(STDOUT);
  my $str;
  open my $fh, '>', \$str;
  my $workbook = Spreadsheet::WriteExcel->new($fh);
  my $worksheet = $workbook->add_worksheet();

  $worksheet->write_col(0,0, ['some','data','here']);
  warn $str;
  return $str;
}

The output is just a blank response, and the warn is blank as well.

The method I'm using to write the spreadsheet to a filehandle is pretty much straight out of the documentation, so I assume the problem is due to some CGI::App noobery on my part. The documentation's suggested methods for filehandles and mod_perl proved pretty fruitless as well.

I guess I should mention I'm running on Windows, and that my current workaround is to create a file and provide the user with a link to it. That poses more problems, however, in regards to clearing out the directory and when to do so, and also authentication for access to the generated files.

Suggestions? Scathing criticism?

Zeitler answered 28/8, 2009 at 20:20 Comment(0)
A
4

You shouldn't need to mess with STDOUT; CGI-App should handle that properly for you under the hood. You'll also may need to close the filehandle before you try to send the data.

It looks like you're not setting a proper content type for the Excel data, though. For anything other than text/html, you'll need to set it manually. Try something like this:

sub export_list {
    my $self = shift;

    my $str;
    open my $fh, '>', \$str or die "Can't open to var: $!";
    my $workbook = Spreadsheet::WriteExcel->new($fh);
    my $worksheet = $workbook->add_worksheet();

    $worksheet->write_col(0,0, ['some','data','here']);

    $workbook->close;
    close $fh;

    warn $str;

    $self->header_add( -type => 'application/vnd.ms-excel' );
    return $str;

}

You may also be interested in CGI::Application::Plugin::Stream

Anatropous answered 28/8, 2009 at 21:39 Comment(1)
Yeah I guess closing the filehandle forces it to flush to the variable so I can return it. Also, I had to add -attachment => 'filename.xls' to the header or firefox couldn't figure out what to do with the file. I'll look into CGI::A::P::Stream, but the files we're making shouldn't be big enough to matter. Thanks!Zeitler
B
4

Instead of creating the whole spreadsheet in memory, you should either write it out to a file and them stream it when finished (using CGI::Application::Plugin::Stream helps here, but you'd still need to clean it up afterwards, but really every web app should have a temp directory that periodically gets cleaned up) or print it as you create it (which means making the FH STDIN instead which might be trickier under mod_perl or maybe not).

And then remember to close your workbook when it's done.

Blackheart answered 28/8, 2009 at 21:41 Comment(0)
C
3

You want to close the workbook. Also close the filehandle:

warn "length 1=".length($str);
$workbook->close();
close($fh) or die "error on close: $!";
warn "length 2=".length($str);

length 1=0 at wx.pl line 16.
length 2=5632 at wx.pl line 19.
Cohesion answered 28/8, 2009 at 21:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.