如何使用Perl提供一个供下载的大文件?

如何使用Perl提供一个供下载的大文件?,perl,file-io,download,Perl,File Io,Download,我需要从web服务器无法访问的位置下载一个大文件(500+MB)。我发现了这个问题,这与我的情况相同,但我使用的是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

我需要从web服务器无法访问的位置下载一个大文件(500+MB)。我发现了这个问题,这与我的情况相同,但我使用的是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;
使用Tie::File;

打开我的$fh,readline函数称为
readline
(也可以写成

我不确定你有什么问题。也许循环就是这样 没有懒散地评估(事实并非如此)。或者,也许Tie::File是 把事情搞砸了?无论如何,用于读取文件的惯用Perl 一次一行是:

open my $fh, '<', $filename or die ...;
while(my $line = <$fh>){
   # process $line
}
打开我的$fh,回答(原始)问题(“Perl是否具有与PHP的
readline()
函数等效的功能…?”),答案是“尖括号语法”:

(正如我最初写的)将文件的内容复制到一个列表中,并对其进行迭代。使用

while (my $line = <$filehandle>) { ... }
while(my$line=){…}
没有。在处理小文件时,差异并不显著,但在处理大文件时,差异肯定是显著的


回答(更新的)问题(“Perl是否具有与PHP的
readfile()
function…?”)等价的功能?”答案是。有一个,但是
Perl6::Slurp
似乎是当前选择的模块

隐含的问题(“为什么浏览器在抓取整个文件之前不提示下载?”)与您在文件中的阅读方式完全无关,而与浏览器认为良好的形式有关。我猜浏览器会看到mime类型并决定它知道如何显示纯文本


更仔细地看一下内容配置问题,我记得IE忽略内容配置也有类似的问题。不幸的是,我记不起解决办法了。(旧页,参考IE 5.0、5.5和6.0)。然而,为了澄清,我想知道:

  • 您使用什么样的链接指向这个大文件(即,您使用的是普通的
    a href=“perl\u script.cgi?filename.txt
    链接还是某种Javascript)

  • 您实际使用什么系统来提供文件?例如,Web服务器是否自己连接到另一台没有Web服务器的计算机,然后将文件复制到Web服务器,然后将文件发送给最终用户,或者用户是否直接连接到没有Web服务器的计算机

  • 在最初的问题中,您写道“这不会导致浏览器在获取整个文件之前提示下载”,在评论中,您写道“在下载整个文件之前,我仍然无法获得文件的下载提示”。这是否意味着该文件会显示在浏览器中(因为它只是文本),在浏览器下载整个文件后,您会收到“您想将此文件保存在何处”提示或其他提示


  • 我有一种感觉,HTTP头有可能在某个点被剥离,或者缓存控制头被添加(这显然会造成麻烦)。

    如果您只想将输入转换为输出,这应该可以做到

    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");
    }
    
    应用jrockways建议
    {
    打开我的$fh,“你可以使用我的模块。它应该是高效的(因为它在引擎盖下使用sendfile),但不是完全可移植的(目前只支持Linux、FreeBSD和Solaris)。

    当你说“这不会导致浏览器提示下载”——什么是“浏览器”

    不同的浏览器表现不同,IE特别故意,它会忽略标题,并根据读取文件的前几kb自行决定做什么

    换句话说,我认为您的问题可能在客户端,而不是服务器端


    试着对“浏览器”撒谎,告诉它文件的类型是application/octet stream。或者为什么不直接压缩文件,特别是因为它太大了。

    不要使用
    for/foreach()
    ,因为它一次读取整个文件,然后在上面迭代。使用
    while()
    取而代之。
    sysread
    解决方案很好,但是
    sendfile
    性能最好。

    我成功地告诉浏览器它是application/octet-stream类型,而不是text/plain类型。显然,大多数浏览器更喜欢以内联方式显示text/plain,而不是给用户一个下载对话框选择权


    从技术上讲,这是在欺骗浏览器,但它确实起到了作用。

    为大型文件提供下载服务的最有效方式取决于您使用的web服务器

    除了: 有一些链接描述如何为(mod_secdownload:security via url generation)执行此操作。有一些PHP、Ruby(Rails)、Python中的示例可用于Perl

    基本上可以归结为:

  • 配置web服务器的路径和权限
  • 在Perl应用程序中为重定向生成有效的头(
    内容类型
    内容处置
    内容长度
    ?,
    X-Sendfile
    X-Accel-redirect
    等)

  • 可能有一些CPAN模块、web框架插件可以做到这一点,例如,

    +1正确回答隐含的问题(至少是我想说的方式)对不起,我想说的是readfile(),正如我链接的问题中所建议的。问题已更新。比较
    for()
    对不起,我想说的是readfile(),正如我链接的问题中所建议的。问题已更新。我尝试先使用一个不带Tie::Cache的while循环,其行为与Tie::Cache相同。在下载整个文件之前,我仍然没有收到文件的下载提示。那么,最好让您的web框架来处理它。试试HTTP::Engine,它与CGI一起工作。我认为我需要完全
    for my $line (<$filehandle>) { ... }
    
    while (my $line = <$filehandle>) { ... }
    
    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");
    }
    
    { 
        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; 
    }