Как мне предоставить большой файл для скачивания с помощью Perl? - PullRequest
7 голосов
/ 21 февраля 2009

Мне нужно предоставить большой файл (500+ МБ) для загрузки из местоположения, которое недоступно для веб-сервера. Я нашел вопрос Обслуживание больших файлов с 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) или есть способ выполнить то, что я пытаюсь сделать здесь?

Ответы [ 8 ]

7 голосов
/ 21 февраля 2009

Если вы просто хотите отбросить ввод к выводу, это должно сработать.

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 в PHP, и это очень неудобно, Perls настолько прост в использовании, что вычеркивание функции «один размер подходит всем» кажется чрезмерным.

Кроме того, вы можете обратиться к поддержке X-SendFile и, в основном, отправить заголовок своему веб-серверу, чтобы сообщить ему, какой файл отправить: http://john.guen.in/past/2007/4/17/send_files_faster_with_xsendfile/ (при условии, конечно, что у него достаточно разрешений для доступа к файлу). , но файл просто НЕ обычно доступен через стандартный URI)

Редактировать Отмечено, что лучше лучше делать в цикле, я тестировал приведенный выше код на жестком диске, и он неявно пытается сохранить все это в невидимом временная переменная и съешь весь свой баран.

Альтернативное использование блоков

Следующий улучшенный код считывает данный файл в блоках по 8192 символа, что намного более эффективно использует память и обеспечивает пропускную способность, вполне сопоставимую с моей скоростью чтения с чистого диска. (Я также указал на / dev / full для приступов и хихиканья и получил здоровую пропускную способность 500 Мбит / с, и она не съела всех моих баранов, так что это должно быть хорошо)

{ 
    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.

2 голосов
/ 21 февраля 2009

Вы можете использовать мой Sys :: Sendfile модуль. Он должен быть очень эффективным (так как он использует sendfile изнутри), но не полностью переносимым (в настоящее время поддерживаются только Linux, FreeBSD и Solaris).

2 голосов
/ 21 февраля 2009

Функция readline называется readline (и также может быть записана как <>).

Я не уверен, какая у тебя проблема. Возможно что за петли не лениво оцениваются (что они не являются). Или, возможно, Tie :: File is что-то напортачило? Во всяком случае, идиоматический Perl для чтения файла строка за раз:

open my $fh, '<', $filename or die ...;
while(my $line = <$fh>){
   # process $line
}

Нет необходимости использовать Tie :: File.

Наконец, вы не должны заниматься такими вещами самостоятельно. это это работа для веб-фреймворка. Если бы вы использовали Катализатор (или HTTP :: Engine ), вы бы просто скажи:

open my $fh, '<', $filename ...
$c->res->body( $fh );

и фреймворк автоматически подаст данные в файл эффективно. (Использование stdio через readline не очень хорошая идея, это лучше читать файл блоками с диска. Но кого это волнует, так это отведенный!)

1 голос
/ 25 февраля 2009

Не используйте for/foreach (<$input>), потому что он читает весь файл сразу, а затем перебирает его. Вместо этого используйте while (<$input>). Решение sysread - это хорошо, но sendfile - лучший результат.

1 голос
/ 21 февраля 2009

Когда вы говорите: «Это не вызывает запрос браузера на загрузку» - что такое «браузер»?

Различные браузеры ведут себя по-разному, и IE особенно умышлен, он игнорирует заголовки и решает сам, что делать, основываясь на чтении первых нескольких килобайт файла.

Другими словами, я думаю, что ваша проблема может быть на стороне клиента, а не на стороне сервера.

Попробуйте обмануть "браузер" и сказать ему, что файл имеет тип application / octet-stream. Или почему бы просто не сжать файл, тем более что он такой огромный.

1 голос
/ 21 февраля 2009

Отвечая на (оригинальный) вопрос («Есть ли в Perl эквивалент readline() функции PHP ...?»), Ответ - «синтаксис угловой скобки»:

open my $fh, '<', '/path/to/file.txt';
while (my $line = <file>) {
    print $line;
}

Однако получить длину содержимого с помощью этого метода не всегда просто, поэтому я рекомендую остановиться на Tie::File.


Примечание

Использование:

for my $line (<$filehandle>) { ... }

(как я изначально писал) копирует содержимое файла в список и перебирает его. Использование

while (my $line = <$filehandle>) { ... }

нет. При работе с небольшими файлами разница невелика, но при работе с большими файлами она определенно может быть.


Отвечая на (обновленный) вопрос («Есть ли в Perl эквивалент readfile() функции PHP ...?»), Ответ - slurping . Существует пара синтаксисов , но Perl6::Slurp, кажется, является текущим выбранным модулем.

Подразумеваемый вопрос («почему браузер не запрашивает загрузку перед захватом всего файла?») Не имеет абсолютно никакого отношения к тому, как вы читаете файл, и все, что связано с тем, что браузер считает хорошая форма. Я предполагаю, что браузер видит mime-тип и решает, что он умеет отображать простой текст.


Если присмотреться к проблеме 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 будут удалены или что добавлен заголовок Cache-control (что, очевидно, может вызвать проблемы).

0 голосов
/ 26 февраля 2009

Наиболее эффективный способ предоставления большого файла для загрузки зависит от используемого вами веб-сервера.

В дополнение к @ Кент Фредрик X-Sendfile предложение :

Скачивание файлов выполнено правильно содержит несколько ссылок, описывающих, как это сделать для Apache , lighttpd (mod_secdownload: безопасность посредством генерации URL), Nginx . Есть примеры в PHP, Ruby (Rails), Python, которые могут быть приняты для Perl.

В основном это сводится к:

  1. Настройка путей и разрешений для вашего веб-сервера.
  2. Создание допустимых заголовков для перенаправления в приложении Perl (Content-Type, Content-Disposition, Content-length? , X-Sendfile или X-Accel-Redirect и т. Д.).

Возможно, есть модули CPAN, плагины веб-фреймворков, которые делают именно это, например, @ Леон Тиммерманс упомянул Sys::Sendfile в своем ответе .

0 голосов
/ 25 февраля 2009

Я успешно сделал это, сказав браузеру, что он имеет тип application / octet-stream вместо типа text / plain. Очевидно, что большинство браузеров предпочитают отображать текстовый / обычный текст вместо встроенного диалогового окна загрузки.

Это технически обманывает браузер, но он делает свою работу.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...