我需要提供一个大文件(500+ MB),以便从Web服务器无法访问的位置下载。我发现问题Serving large files with 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所建议的那样),还是有办法完成我在这里尝试做的事情?
答案 0 :(得分:7)
如果您只想将输入粘贴到输出,这应该可以解决问题。
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
支持,并基本上向您的网络服务器发送标头,告诉它要发送的文件:http://john.guen.in/past/2007/4/17/send_files_faster_with_xsendfile/(当然,它具有足以访问该文件的权限文件,但文件通常不能通过标准URI访问
编辑注意, 更好地在循环中执行它,我用硬盘驱动器测试了上面的代码,它隐式地尝试将整个内容存储在一个看不见的临时变量,吃掉你所有的公羊。
以下改进的代码以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。
答案 1 :(得分:2)
readline函数称为readline
(也可以写成
<>
)。
我不确定你遇到了什么问题。也许就是循环 没有懒惰的评价(他们不是)。或者,也许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在这里不是一个好主意,它是 最好从磁盘中读取块中的文件。但谁在乎呢,就是这样 抽象!)
答案 2 :(得分:2)
您可以使用我的Sys::Sendfile模块。它应该是高效的(因为它使用了引擎盖下的sendfile),但不是完全可移植的(目前只支持Linux,FreeBSD和Solaris)。
答案 3 :(得分:1)
回答(原始)问题(“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()
函数相同的...?”),答案是slurping。有一个couple of syntaxes,但Perl6::Slurp
似乎是当前的首选模块。
隐含的问题(“为什么在抓取整个文件之前浏览器没有提示下载?”)与您在文件中的阅读方式完全无关,而且与浏览器认为的内容有关好形式。我猜想浏览器会看到mime-type并决定它知道如何显示纯文本。
仔细观察Content-Disposition问题,我记得IE在忽略Content-Disposition方面遇到了类似的问题。不幸的是我不记得解决方法了。 IE has a long history of problems here(旧页面,指IE 5.0,5.5和6.0)。但是,为了澄清,我想知道:
您使用哪种链接指向此大文件(即,您使用的是普通的a href="perl_script.cgi?filename.txt
链接,还是使用某种类型的Javascript)?
您使用什么系统来实际提供文件?例如,网络服务器是否在没有网络服务器的情况下与其他计算机建立自己的连接,然后将文件复制到网络服务器,然后将文件发送给最终用户,或者用户是否在没有网络服务器的情况下直接连接到计算机
在最初的问题中你写道“这不会导致浏览器在抓取整个文件之前提示下载”并在评论中写道“我仍然没有得到该文件的下载提示,直到整件事都下载了。“这是否意味着文件在浏览器中显示(因为它只是文本),在浏览器下载整个文件后,您会得到“您要在哪里保存此文件”提示或其他内容?
我有一种感觉,HTTP标头有可能在某些时候被剥离,或者一个Cache-control标头被添加(这显然会导致麻烦)。
答案 4 :(得分:1)
当您说“这不会导致浏览器提示下载”时 - 什么是“浏览器”?
不同的浏览器行为不同,IE特别有意,它会忽略标题,并根据读取文件的前几个kb自行决定做什么。
换句话说,我认为您的问题可能出在客户端,而不是服务器端。
尝试撒谎到“浏览器”并告诉它该文件的类型为application / octet-stream。或者为什么不直接压缩文件,特别是因为它太大了。
答案 5 :(得分:1)
不要使用for/foreach (<$input>)
,因为它会立即读取整个文件,然后对其进行迭代。请改用while (<$input>)
。 sysread
解决方案很好,但sendfile
是性能最佳的解决方案。
答案 6 :(得分:0)
我通过告诉浏览器它是application / octet-stream类型而不是text / plain类型来成功完成它。显然大多数浏览器更喜欢显示文本/纯内联而不是给用户一个下载对话框选项。
它在技术上对浏览器撒谎,但它可以完成任务。
答案 7 :(得分:0)
提供大型文件以供下载的最有效方法取决于您使用的Web服务器。
X-Sendfile
suggestion:File Downloads Done Right有一些链接描述了如何为Apache,lighttpd(mod_secdownload:通过网址生成安全性),nginx执行此操作。 PHP中有一些例子,Ruby(Rails),Python可以用于Perl。
基本上归结为:
Content-Type
,Content-Disposition
,Content-length
?,X-Sendfile
或{{1}等等)。 可能有CPAN模块,网络框架插件就是这样做的,例如@Leon Timmermans mentioned Sys::Sendfile
in his answer。