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

I made a file download script that when you call it from a browser similar to "http://www.nosite.com/cgi-bin/download.pl?file=139nvios93" it sends you file, eg "test.iso" that can be more than 300 MB.

First I made the download part like this:
open(DLFILE, "<$file_location/$id") || Error('open', 'file'); @fileholder = <DLFILE>; close (DLFILE) || Error ('close', 'file'); print "Content-Type:application/x-download\n"; print "Content-Disposition: attachment;filename=$filename[0]\n\n"; print @fileholder
But it took several minutes to start the download, I assume because it was taking forever to load the file into the array.

Then I changed my code to this:
open(DLFILE, "<$file_location/$id") || Error('open', 'file'); print "Content-Type:application/x-download\n"; print "Content-Disposition: attachment;filename=$filename[0]\n\n"; while (<DLFILE>) { print $_; } close (DLFILE) || Error ('close', 'file');
Now my downloads start a lot faster. Am I right to assume that my first code attempt took forever since I had to load the file into memory first. And my second attempt is faster since I send the headers first then start sending data when I read the first line? Is there a better way to do this?

Replies are listed 'Best First'.
Re: Improving a File Download Script
by ikegami (Patriarch) on Oct 13, 2005 at 06:12 UTC
    Am I right to assume that my first code attempt took forever since I had to load the file into memory first. And my second attempt is faster since I send the headers first then start sending data when I read the first line?

    Yes.

    Is there a better way to do this?

    Is this a binary file? (application/* implies it is.) If so, you should set binmode on the output handle (after sending the header) and on the input handle.

    Again, if this is a binary file, you should probably set $/ to a block size. You're reading a line at a time, and there's no guarantee to be any line feeds in your file, so you could theoretically end up reading the whole file at once the second way too. For example, $/ = \1024; Refer to the documentation of $/ in perlvar for details.

Re: Improving a File Download Script
by neosamuri (Friar) on Oct 13, 2005 at 06:43 UTC
    As mentioned before you sould sure binmode, but I believe you should also use read like this:
    open(DLFILE, "<$file_location/$id") || Error('open', 'file'); binmode print "Content-Type:application/x-download\n"; print "Content-Disposition: attachment;filename=$filename[0]\n\n"; my $data; while (read DLFILE, $_, 1024) { print $data; }
    close (DLFILE) || Error ('close', 'file');
Re: Improving a File Download Script
by Skeeve (Parson) on Oct 13, 2005 at 06:14 UTC

    You are right with your assumption. Loading the file at once was the reason.

    Define "better".

    You could zip the file "on the fly" if it's not already zipped.

    Update: (Thanks to ikegami) You could also use read to read chunks of the file into a buffer.


    s$$([},&%#}/&/]+}%&{})*;#$&&s&&$^X.($'^"%]=\&(|?*{%
    +.+=%;.#_}\&"^"-+%*).}%:##%}={~=~:.")&e&&s""`$''`"e
Re: Improving a File Download Script
by pajout (Curate) on Oct 13, 2005 at 08:37 UTC