Как отдать пользователю файл скриптом
Я уже затрагивал эту тему в одной из предыдущих заметок. Сегодня мы рассмотрим этот вопрос более подробно - не только как отдать файл на загрузку, но и как реализовать поддержку докачки.
Файл download.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: сохранить на диск, открыть или отменить скачивание. Заголовок
нужен для старых версий MS IE.
С поддержкой докачки:
$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);
?>
Чтобы получить от сервера не весь документ, а только оставшуюся часть, клиент должен отправить заголовок
где num - смещение в байтах от начала файла.
Сервер в свою очередь устанавливает переменную окружения HTTP_RANGE и должен отправить заголовок
который дает клиенту понять, что отдается часть контента. Далее сервер должен отдать клиенту ту часть контента, которую тот запрашивал с соответствующими заголовками:
Content-Disposition: attachment; filename=имя_файла
Last-Modified: время_модификации_файла
Accept-Ranges: bytes
Content-Length: длина_отдаваемой_части
Content-Range: от-до/размер
Поясню последний заголовок на примере: имеем файл размером 4000 байт, отдаем кусок с 5 по 288 байт. Тогда заголовок будет выглядеть так:
В общем случае, протокол HTTP 1.1 позволяет клиенту запросить только часть объекта (от и до). Если клиент посылает заголовок
это означает, что клиент хочет получить кусок документа, начиная с 2015 байта и длиной в 1 килобайт. Если сервер поддерживает докачку и документ не является динамическим, то будет выдана запрашиваемая часть. В противном случае сервер вернёт ошибку о том, что действие не поддерживается или начнёт выдавать документ полностью.