Xpoint
   [напомнить пароль]

CGI

Оглавление

Поддержка докачки файлов

Современные браузеры и многие другие HTTP-утилиты поддерживают "докачку" файлов. Если ваш скрипт осуществляют выдачу файлов "через себя", то имеет смысл реализовать поддержку такой "докачки" в своем скрипте, чтобы облегчить жизнь пользователям и съэкономить свой трафик.

В HTTP-протоколе "докачка" реализована с помощью заголовка Range (RFC2616), с помощью которого клиент указывает, какая порция данных его интересует.

Ниже приведен рабочий код, который дает представление, как осуществляется обработка этого заголовка в скрипте:

# предположим, что на данном этапе у нас уже имеется:
# $filepath - полный путь к файлу на диске

# Получаем размер файла
my $size = -s $filepath;

my $range = 0;
# Нижняя граница
my $rlow = 0;
# Верхняя граница
my $rhigh = $size - 1;

# Проверяем наличие заголовка Range и пытаемся его обработать.
# Вот примеры заголовка из RFC (пример для сущности размером 10000):
# o Первые 500 байт (смещения 0-499, включительно):
# bytes=0-499
# o Вторые 500 байт (смещения 500-999, включительно):
# bytes=500-999
# o Последние 500 байт (смещения 9500-9999, включительно):
# bytes=-500
# o или
# bytes=9500-

# Если размер неизвестен, пропускаем обработку заголовка Range
if ($size && $ENV{HTTP_RANGE} =~ /^bytes=(\d*)-(\d*)/)
{
  $range = 1;
  if ($2 eq '' && $1 > 0)
  {
    $rlow = $1;
  }
  elsif ($1 eq '' && $size - $2 > 0)
  {
    $rlow = $size - $2;
  }
  elsif ($1 <= $2)
  {
    $rlow = $1;
    $rhigh = $2;
  }
  else
  {
    $range = 0;
  }
}

# Выводим заголовки
if ($range)
{
  print "Status: 206 Partial Content\n";
  print "Content-Range: bytes $rlow-$rhigh/$size\n";
  $size = $rhigh - $rlow + 1;
}
print "Accept-Ranges: bytes\n";
print "Content-Length: $size\n" if ($size > 0);
print "Content-Type: $mime_type; name=$filename\n";
print "Content-Disposition: attachment; filename=$filename\n\n";

# Количество байт, читаемых за один присест
my $BufferSize = 1024;

my $buf = '';
$BufferSize = $size if ($BufferSize > $size);

# Вывод содержимого файла
open(DATAFILE, $filepath);
binmode(DATAFILE);
binmode(STDOUT);
seek(DATAFILE,$rlow,0);
while (read(DATAFILE,$buf,$BufferSize))
{
  my $delta = $rhigh - tell() + 1;
  print $buf;
  last if ($delta < 1);
  $BufferSize = $delta if ($BufferSize > $delta);
}
close DATAFILE;

Обрабатываем If-Modified-Since

Часто случается ситуация, когда файлы необходимо отдавать через скрипт, а не прямыми ссылками. Чтобы облегчить при этом жизнь пользователю, да и серверу тоже, надо обрабатывать HTTP заголовок If-Modified-Since (RFC2616). Ниже приведен рабочий код, который дает представление, как это делается:

# предположим, что на данном этапе у нас уже имеется:
# $file_path - полный путь к файлу на диске
# $file_mime - MIME тип этого файла
my $file_size = -s $file_path;
my $file_lastmod = (stat(_))[9];
print "Content-Length: $file_size\n";
require POSIX;
print "Last-Modified: ",POSIX::strftime('%a, %d %b %Y %T GMT', gmtime($file_lastmod)),"\n";
# Etag - это уникальный идентификатор контента, который может быть любым по вашему усмотрению.
# Главное, чтобы один и тот же контент обозначался всегда одним идентификатором, и один идентификатор
# не использовался для разного контента.
my $etag = $file_path;
$etag =~ s|\Q$ENV{DOCUMENT_ROOT}\E/?||;
$etag =~ s|[/.]|_|g;
print qq[Etag: "$etag"\n];
# Expires - один месяц для примера
print "Expires: ",POSIX::strftime('%a, %d %b %Y %T GMT', gmtime(time()+30*24*60*60)),"\n";
if ($ENV{HTTP_IF_MODIFIED_SINCE})
{
  # дата в заголовке приходит в HTTP формате:
  # HTTP_IF_MODIFIED_SINCE => Fri, 14 Jan 2005 15:46:46 GMT
  # надо её распарсить
  require Time::ParseDate;  
  my $since = Time::ParseDate::parsedate($ENV{HTTP_IF_MODIFIED_SINCE});
  # ну и, собственно, сама проверка
  if ($since >= $file_lastmod)
  {
    print "Status: 304 Not modified\n\n";
    exit;
  }
}
print "Content-Type: $file_mime\n\n";
# тут продолжаем обычный вывод содержимого...

Необходимо рассказать и о HTTP_IF_NONE_MATCH

Powered by POEM™ Engine Copyright © 2002-2005