[英]How do I serve a large file for download with Perl?
我需要提供一個大文件(500+ MB),以便從Web服務器無法訪問的位置下載。 我發現了使用PHP提供大文件的問題,這與我的情況相同,但我使用的是Perl而不是PHP。
我嘗試逐行打印文件,但這不會導致瀏覽器在抓取整個文件之前提示下載:
use Tie::File;
open my $fh, '<', '/path/to/file.txt';
tie my @file, 'Tie::File', $fh
or die 'Could not open file: $!';
my $size_in_bytes = -s $fh;
print "Content-type: text/plain\n";
print "Content-Length: $size_in_bytes\n";
print "Content-Disposition: attachment; filename=file.txt\n\n";
for my $line (@file) {
print $line;
}
untie @file;
close $fh;
exit;
Perl是否具有與PHP的readfile()
函數相同的功能(如PHP所示)或者有沒有辦法完成我在這里嘗試做的事情?
如果你只想將輸入粘貼到輸出,這應該可以解決問題。
use Carp ();
{ #Lexical For FileHandle and $/
open my $fh, '<' , '/path/to/file.txt' or Carp::croak("File Open Failed");
local $/ = undef;
print scalar <$fh>;
close $fh or Carp::carp("File Close Failed");
}
我想在回應“Perl是否有PHP ReadFile Equivelant”時,我想我的答案是“但它並不真的需要一個”。
我已經使用過PHP的手動文件IO控件而且它們很痛苦,相比之下,Perls只是如此易於使用,因為一個適合所有人的功能似乎過度殺戮。
此外,您可能希望查看X-SendFile
支持,並基本上向您的Web服務器發送一個標頭,告訴它要發送的文件: http : //john.guen.in/past/2007/4/17/send_files_faster_with_xsendfile/ (假設它當然具有足以訪問該文件的權限,但該文件通常不能通過標准URI訪問)
編輯注意到,最好是做一個循環,我測試了一個硬盤驅動器上面的代碼,它並含蓄地嘗試存儲在一個看不見的臨時變量整個事情,吃所有您的RAM。
以下改進的代碼以8192個字符塊的形式讀取給定文件,這樣可以提高內存效率,並且可以獲得與我的磁盤原始讀取速率相當的吞吐量。 (我還指出它/ dev / full適合和咯咯笑,並且獲得了500mb / s的健康吞吐量,並且它沒有吃掉我所有的公羊,所以一定要好)
{
open my $fh , '<', '/dev/sda' ;
local $/ = \8192; # this tells IO to use 8192 char chunks.
print $_ while defined ( $_ = scalar <$fh> );
close $fh;
}
{
open my $fh , '<', '/dev/sda5' ;
print $_ while ( sysread $fh, $_ , 8192 );
close $fh;
}
這實際上使性能提高了一倍......在某些情況下,我獲得了比DD更好的吞吐量O_o。
readline函數稱為readline
(也可以寫為<>
)。
我不確定你遇到了什么問題。 也許for循環不是懶惰的評價(他們不是)。 或者,也許Tie :: File搞砸了什么? 無論如何,用於一次讀取一行文件的慣用Perl是:
open my $fh, '<', $filename or die ...;
while(my $line = <$fh>){
# process $line
}
無需使用Tie :: File。
最后,你不應該自己處理這類事情。 這是Web框架的工作。 如果您使用的是Catalyst (或HTTP :: Engine ),您只需說:
open my $fh, '<', $filename ...
$c->res->body( $fh );
並且框架將自動有效地提供文件中的數據。 (通過readline使用stdio在這里不是一個好主意,最好從磁盤中讀取塊中的文件。但是誰在乎,它是抽象的!)
您可以使用我的Sys :: Sendfile模塊。 它應該是高效的(因為它在引擎蓋下使用sendfile),但不完全可移植(目前僅支持Linux,FreeBSD和Solaris)。
回答(原始)問題(“Perl是否有相當於PHP的readline()
函數......?”),答案是“尖括號語法”:
open my $fh, '<', '/path/to/file.txt';
while (my $line = <file>) {
print $line;
}
但是,使用此方法獲取內容長度並不一定容易,因此我建議使用Tie::File
。
注意
使用:
for my $line (<$filehandle>) { ... }
(正如我最初寫的那樣)將文件的內容復制到列表中並對其進行迭代。 運用
while (my $line = <$filehandle>) { ... }
才不是。 處理小文件時差異不大,但在處理大文件時肯定可以。
回答(更新的)問題(“Perl是否有相當於PHP的readfile()
函數......?”),答案正在悄悄解決 。 有幾種語法 ,但Perl6::Slurp
似乎是當前的模塊選擇。
隱含的問題(“為什么在抓取整個文件之前瀏覽器沒有提示下載?”)與你在文件中的閱讀方式完全無關,而且與瀏覽器認為的好形式有關。 我猜想瀏覽器會看到mime-type並決定它知道如何顯示純文本。
更仔細地看看Content-Disposition問題,我記得在IE中忽略Content-Disposition有類似的麻煩。 不幸的是我不記得解決方法了。 IE在這里有很長的問題歷史 (舊頁面,指的是IE 5.0,5.5和6.0)。 但是,為了澄清,我想知道:
您使用什么樣的鏈接指向這個大文件(即,您使用的是普通a href="perl_script.cgi?filename.txt
鏈接還是使用某種類型的Javascript)?
您使用什么系統來實際提供文件? 例如,網絡服務器是否在沒有網絡服務器的情況下與其他計算機建立自己的連接,然后將文件復制到網絡服務器,然后將文件發送給最終用戶,或者用戶是否在沒有網絡服務器的情況下直接連接到計算機?
在最初的問題中,您寫道“這不會導致瀏覽器在抓取整個文件之前提示下載”,並且在評論中您寫道“在下載整個文件之前,我仍然沒有獲得該文件的下載提示”。 這是否意味着文件在瀏覽器中顯示(因為它只是文本),在瀏覽器下載完整個文件后,您會得到“您要在哪里保存此文件”提示符,或其他內容?
我有一種感覺,HTTP標頭有可能在某些時候被剝離,或者一個Cache-control標頭被添加(這顯然會導致麻煩)。
當你說“這不會導致瀏覽器提示下載” - 什么是“瀏覽器”?
不同的瀏覽器行為不同,IE特別有意,它會忽略標頭並根據讀取文件的前幾個kb自行決定做什么。
換句話說,我認為您的問題可能出在客戶端,而不是服務器端。
試着撒謊到“瀏覽器”並告訴它該文件是application / octet-stream類型。 或者為什么不直接壓縮文件,特別是因為它太大了。
不要使用for/foreach (<$input>)
因為它一次讀取整個文件然后迭代它。 改為使用while (<$input>)
。 sysread
解決方案很好,但sendfile
是性能最佳的。
我通過告訴瀏覽器它是application / octet-stream類型而不是text / plain類型來成功完成它。 顯然大多數瀏覽器更喜歡顯示文本/純內聯而不是為用戶提供下載對話框選項。
它在技術上對瀏覽器撒謊,但它完成了這項工作。
提供大型文件以供下載的最有效方法取決於您使用的Web服務器。
X-Sendfile
建議 : 文件下載完成右邊有一些鏈接描述如何為Apache , lighttpd (mod_secdownload:通過url生成的安全性), nginx 。 PHP中有一些例子,Ruby(Rails),Python可以用於Perl。
基本上它歸結為:
Content-Type
, Content-Disposition
, Content-length
? , X-Sendfile
或X-Accel-Redirect
等)。 可能有CPAN模塊,網絡框架插件就是這樣做的,例如@Leon Timmermans在他的回答中提到了Sys::Sendfile
。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.