7

我需要提供一个大文件 (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 所建议的那样),还是有办法完成我在这里尝试做的事情?

4

8 回答 8

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; 
}

应用 jrockways 建议

{ 
    open my $fh , '<', '/dev/sda5' ; 
    print $_ while ( sysread $fh, $_ , 8192 ); 
    close $fh; 
}

这实际上使性能翻了一番,……在某些情况下,我得到的吞吐量比 DD 做的 O_o 更好。

于 2009-02-21T02:52:16.313 回答
2

调用 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 不是一个好主意,最好从磁盘中读取块中的文件。但谁在乎,它是抽象的!)

于 2009-02-21T00:27:29.217 回答
2

你可以使用我的Sys::Sendfile模块。它应该是高效的(因为它在底层使用 sendfile),但不是完全可移植的(目前仅支持 Linux、FreeBSD 和 Solaris)。

于 2009-02-21T12:25:34.857 回答
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。有几种语法,但Perl6::Slurp似乎是当前选择的模块。

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


仔细观察 Content-Disposition 问题,我记得 IE 忽略 Content-Disposition 时遇到了类似的问题。不幸的是,我不记得解决方法。 IE在这里有很长的问题历史(旧页面,指的是IE 5.0、5.5和6.0)。但是,为了澄清,我想知道:

  1. 您使用哪种链接指向这个大文件(即,您使用的是普通a href="perl_script.cgi?filename.txt链接还是使用某种 Javascript)?

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

  3. 在最初的问题中,您写道“这不会导致浏览器在获取整个文件之前提示下载”,在评论中您写道:“在整个下载之前,我仍然没有收到文件的下载提示。” 这是否意味着文件会显示在浏览器中(因为它只是文本),在浏览器下载整个文件后,您会收到“您要在哪里保存此文件”提示或其他提示?

我有一种感觉,HTTP 标头有可能在某些时候被剥离,或者缓存控制标头正在被添加(这显然会导致麻烦)。

于 2009-02-21T00:28:53.113 回答
1

当您说“这不会导致浏览器提示下载”时,“浏览器”是什么?

不同的浏览器表现不同,IE特别任性,它会忽略header,根据读取文件的前几kb自行决定做什么。

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

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

于 2009-02-21T20:42:19.140 回答
1

不要使用for/foreach (<$input>),因为它会一次读取整个文件,然后对其进行迭代。改为使用while (<$input>)sysread解决方案很好,但在性能sendfile方面是最好的。

于 2009-02-25T11:12:19.573 回答
0

我通过告诉浏览器它是 application/octet-stream 类型而不是 text/plain 类型来成功完成它。显然大多数浏览器更喜欢显示 text/plain inline 而不是给用户一个下载对话框选项。

从技术上讲,它对浏览器撒谎,但它确实完成了工作。

于 2009-02-25T17:34:28.250 回答
0

提供大文件下载的最有效方式取决于您使用的网络服务器。

除了@Kent Fredric的X-Sendfile建议

File Downloads Done Right有一些链接描述了如何为Apachelighttpd(mod_secdownload:通过 url 生成的安全性)、nginx执行此操作。Perl 可以采用 PHP、Ruby (Rails)、Python 中的示例。

基本上可以归结为:

  1. 为您的网络服务器配置路径和权限。
  2. 为您的 Perl 应用程序中的重定向生成有效标头(Content-TypeContent-DispositionContent-length?X-SendfileX-Accel-Redirect)。

There are probably CPAN modules, web-frameworks plugins that do exactly that e.g., @Leon Timmermans mentioned Sys::Sendfile in his answer.

于 2009-02-25T21:23:04.800 回答