Как отдать пользователю файл скриптом

Я уже затрагивал эту тему в одной из предыдущих заметок. Сегодня мы рассмотрим этот вопрос более подробно - не только как отдать файл на загрузку, но и как реализовать поддержку докачки.

Файл download.php

<?php
if(isset($_SERVER['HTTP_USER_AGENT']) and strpos($_SERVER['HTTP_USER_AGENT'],'MSIE'))
  header('Content-Type: application/force-download');
else
  header('Content-Type: application/octet-stream');
header('Accept-Ranges: bytes');
header('Content-disposition: attachment; filename="pricelist.zip"');
?>

Тогда пользователь зашедший на download.php предстанет перед выбором, что делать с файлом pricelist.zip: сохранить на диск, открыть или отменить скачивание. Заголовок

Content-Type: application/force-download

нужен для старых версий MS IE.

С поддержкой докачки:

<?php

$filename = 'pricelist.zip';
 
// если файла нет
if (!file_exists($filename)) {
  header ("HTTP/1.0 404 Not Found");
  exit;
}
 
// получим размер файла
$fsize = filesize($filename);
// дата модификации файла для кеширования
$ftime = date("D, d M Y H:i:s T", filemtime($filename));
// смещение от начала файла
$range = 0;
 
// пробуем открыть
$handle = @fopen($filename, "rb");

// если не удалось
if (!$handle){
  header ("HTTP/1.0 403 Forbidden");
  exit;
}
 
// Если запрашивающий агент поддерживает докачку
if ($_SERVER["HTTP_RANGE"]) {
  $range = $_SERVER["HTTP_RANGE"];
  $range = str_replace("bytes=", "", $range);
  $range = str_replace("-", "", $range);
  // смещаемся по файлу на нужное смещение
  if ($range) fseek($handle, $range);
}
 
// если есть смещение
if ($range) {
  header("HTTP/1.1 206 Partial Content");
} else {
  header("HTTP/1.1 200 OK");
}
 
header("Content-Disposition: attachment; filename=\"{$filename}\"");
header("Last-Modified: {$ftime}");
header("Content-Length: ".($fsize-$range));
header("Accept-Ranges: bytes");
header("Content-Range: bytes {$range}-".($fsize - 1)."/".$fsize);
 
// подправляем под IE что б не умничал
if(isset($_SERVER['HTTP_USER_AGENT']) and strpos($_SERVER['HTTP_USER_AGENT'],'MSIE'))
  Header('Content-Type: application/force-download');
else
  Header('Content-Type: application/octet-stream');
 
while(!feof($handle)) {
  $buf = fread($handle,512);
  print($buf);
}
 
fclose($handle);
 
?>

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

Range: bytes=num-

где num - смещение в байтах от начала файла.

Сервер в свою очередь устанавливает переменную окружения HTTP_RANGE и должен отправить заголовок

HTTP/1.1 206 Partial Content

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

Content-Type: application/octet-stream
Content-Disposition: attachment; filename=имя_файла
Last-Modified: время_модификации_файла
Accept-Ranges: bytes
Content-Length: длина_отдаваемой_части
Content-Range: от-до/размер

Поясню последний заголовок на примере: имеем файл размером 4000 байт, отдаем кусок с 5 по 288 байт. Тогда заголовок будет выглядеть так:

Content-Range: 5-288/4000

В общем случае, протокол HTTP 1.1 позволяет клиенту запросить только часть объекта (от и до). Если клиент посылает заголовок

Range-Unit: 2015 | 1024

это означает, что клиент хочет получить кусок документа, начиная с 2015 байта и длиной в 1 килобайт. Если сервер поддерживает докачку и документ не является динамическим, то будет выдана запрашиваемая часть. В противном случае сервер вернёт ошибку о том, что действие не поддерживается или начнёт выдавать документ полностью.

Один комментарий

  1. Digalkammabap:

    Надеюсь, остальные записи окажутся такими же интересными :)

Оставьте свой отзыв