wolfie7873 has asked for the wisdom of the Perl Monks concerning the following question:

I have a Mason-enabled mod-perl site for which I'm trying to deliver files to certain users. These files are stored as BLOBS in a MySQL db to prevent bookmarking of files on disk. The Mason help-page shows this as a method for accomplishing this with files that *are* on disk:
use Apache::SubRequest; my $subr = $r->lookup_file($file); return 404 unless -f $file and $subr->status == 200; $r->content_type($subr->content_type); $r->send_http_header; return 200 if $r->header_only; $subr->run; $m->abort;
I would use Apache2, instead, but that's minor. What I can't figure out how to do is make this work using a BLOB object instead of a file on disk. One way would be to override the headers, but the Apache::request and Apache2::request objects are just different enough that I'm not sure how to proceed. Here is one long way around:
$r->content_type('application/octet-stream'); $r->header_out('Content-disposition' => ("attachment; filename=$filen +ame_base")); open($fh,"<".$filename_full) or $m->redirect('file_open_failed.html?na +me='.$filename_full); while(<$fh>) { $m->print($_); } close $fh; return;
This gets me around the BLOB problem, but there's no $r->header_out method in Apache2, and I can't figure out the correct structure use of Apache2::headers_out() from the documentation. I'm sure it's something obvious, but I'm stumped.

Replies are listed 'Best First'.
Re: File download from BLOB
by bluescreen (Friar) on Mar 25, 2011 at 15:46 UTC

    You're almost there. The BLOB out of mysql is an scalar with binary content, then after setting the proper headers like you doing on the Apache2's example. you have to print the contents of the blob you get from the DB

      OK, getting there: Here's what I've got that provides the proper download dialog:
      $r->content_type('application/x-download'); $r->err_headers_out->add('Content-disposition' => ("attachment; filena +me=$filename")); my ($st,$sth) = (); $st = "SELECT bin_data FROM forms WHERE filename = '$filename'"; $sth = $dbh->prepare($st) or die "Prepare Failed! " . $st . $dbh->errs +tr(); $sth->execute() or die "Execute Failed! " . $st . $sth->errstr(); my $filedata = $sth->fetchrow(); $sth->finish(); $m->print($filedata); return;
      But the PDF is un-openable upon return. Is that a transmission problem, or a db problem or what?
        But the PDF is un-openable

        You haven't mentioned what platform you're on, so this is just a guess...

        Often, the issue rendering files un-openable is inadvertently having applied linefeed translations to binary formats (such as PDF).  So try using binmode on the file handle $m before writing the blob to it.

Re: File download from BLOB
by locked_user sundialsvc4 (Abbot) on Mar 25, 2011 at 16:46 UTC

    Well, it is also maybe worth mentioning that you do not have to store the data in a database if you don’t want to.   As long as the server-side code has some means to map the URL information to a file-location, it can then serve-up the data from that source.   If that location is not made available to Apache as a place from which static files can be served by its own logic, your app becomes the only way to get to the information.   The end-user neither knows nor cares where the server-app is getting its data from.

      Combining ideas from both of you, I now have this (putting the file in a directory above the docroot so Apache can't serve it.):
      $r->content_type('application/pdf'); $r->err_headers_out->add('Content-disposition' => ("attachment; filena +me=$filename")); my $fh; open($fh,"</var/www/eddie/owner_release.pdf"); binmode $fh; while(<$fh>) { $m->print($_); } close $fh; return;
      And I get the same error message from Acrobat about the file being damaged. I do notice that the file is larger now than it should be. 14K -> 25K. Any other suggestions?
        the file is larger now than it should be. 14K -> 25K

        How does the downloaded file differ from the original one, i.e. what are those additional 11k?  Maybe that will give you a clue as to what's going wrong.

        Other than that, I don't feel entitled to comment further, as I've never used Mason myself.  But maybe this thread will help, which makes me think there might be "issues" delivering binary content via Mason, unless you know exactly what you're doing.

        P.S. in my above reply I was thinking of binmoding the output handle, but taking a closer look, the $m->print() is not an IO::Handle method call (as I originally figured), but a Mason method call, which simply adds content to the output buffer.  In other words, binmoding won't work here.  Also, as you don't seem to be on Windows (as I infer from the above /var/www/...), there won't be any linefeed translations being done, anyway.

Re: File download from BLOB
by Anonymous Monk on Mar 28, 2011 at 03:34 UTC

    I bet you're forgetting to discard anything already in Mason's output buffer before sending the binary.

    $m->clear_buffer;

    You must also make sure you stop producing more output after you send the binary file.

    $m->abort;

      I bet you're forgetting to discard anything already in Mason's output buffer before sending the binary.

      I'll bet you're spot on. I changed my test file from a pdf to an excel spreadsheet. When it tries to open it tells me there are missing files, all of which are .css pages loaded by the autohandler.

      I used the $m->clear_buffer method, and Excel no longer complains about missing files, but the content of the spreadsheet is the content from the autohandler that occurs after the $m->call_next() call. Is there anyway around that?

      There is no 'content' in the called component, and this is the (current) entirety of the <%init> block.

      <%init> $filename = 'fairtax.xls'; use Apache2::SubRequest; my $file = '/var/www/eddie/fairtax.xls'; my $subr = $r->lookup_file($file); return 404 unless -f $file and $subr->status == 200; $m->clear_buffer; $r->err_headers_out->add('Content-disposition' => ("attachment; filena +me=$filename")); $r->content_type($subr->content_type); $r->send_http_header; return 200 if $r->header_only; $subr->run; $m->abort; </%init>
        Finally got it to work - here's how. Thank you to everyone who helped:
        $r->content_type('application/x-download'); $r->err_headers_out->add('Content-disposition' => ("attachment; filena +me=$filename")); my ($st,$sth) = (); $st = "SELECT bin_data FROM forms WHERE filename = '$filename'"; $sth = $dbh->prepare($st) or die "Prepare Failed! " . $st . $dbh->errs +tr(); $sth->execute() or die "Execute Failed! " . $st . $sth->errstr(); my $filedata = $sth->fetchrow(); $sth->finish(); $m->clear_buffer; $m->print( $filedata ); $m->abort; return;