[英]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.