Скачивание файлов с докачкой

Многие интернет-проекты (неважно, что это: движки форумов, фотогалереи или что-то ещё) позволяют пользователям загружать файлы на сайт и скачивать их оттуда.

С закачкой проблем не возникает никогда.

А вот со скачиванием закачанных файлов зачастую бывает одна маленькая проблема: многие вебмастера хотят знать, сколько раз был скачан тот или иной файл. Что же они делают? Очень просто: дают ссылку не на собственно файл, а на некоторый скрипт, который выглядит примерно так:
<?php
// тут подсчёт скачиваний, разные действия
// а дальше: просто выдача всего содержимого файла, примерно так:
readfile ($filename);
?>

И всё бы ничего, но:
Файлы порой бывают очень большими,
а связь — очень некачественной.

Ну и что? А очень просто: выданный таким образом файл нельзя скачивать порциями. То есть ни один менеджер скачиваний (например, ReGet, FlashGet или Download Master и прочие) не смогут:
Скачивать файл в несколько потоков;
Приостановить скачивание в любой момент, а через некоторый промежуток времени начать скачивать файл с места остановки (а ведь в этом и суть докачки).

Что делать?

Вот и меня посетила такая мысль. Полчаса экспериментов — и у меня получилась очень хорошая функция. Отдаю её Вам:
function downloadFile($filename, $mimetype=’application/octet-stream’) {
if (!file_exists($filename)) die(’Файл не найден’);

$from=$to=0; $cr=NULL;

if (isset($_SERVER[’HTTP_RANGE’])) {
$range=substr($_SERVER[’HTTP_RANGE’], strpos($_SERVER[’HTTP_RANGE’], ‘=’)+1);
$from=strtok($range, ‘-’);
$to=strtok(’/'); if ($to>0) $to++;
if ($to) $to-=$from;
header(’HTTP/1.1 206 Partial Content’);
$cr=’Content-Range: bytes ‘ . $from . ‘-’ . (($to)?($to . ‘/’ . $to+1):filesize($filename));
} else header(’HTTP/1.1 200 Ok’);

$etag=md5($filename);
$etag=substr($etag, 0, 8) . ‘-’ . substr($etag, 8, 7) . ‘-’ . substr($etag, 15, 8);
header(’ETag: “‘ . $etag . ‘”‘);

header(’Accept-Ranges: bytes’);
header(’Content-Length: ‘ . (filesize($filename)-$to+$from));
if ($cr) header($cr);

header(’Connection: close’);
header(’Content-Type: ‘ . $mimetype);
header(’Last-Modified: ‘ . gmdate(’r', filemtime($filename)));
$f=fopen($filename, ‘r’);
header(’Content-Disposition: attachment; filename=”‘ . basename($filename) . ‘”;’);
if ($from) fseek($f, $from, SEEK_SET);
if (!isset($to) or empty($to)) {
$size=filesize($filename)-$from;
} else {
$size=$to;
}
$downloaded=0;
while(!feof($f) and !connection_status() and ($downloaded<$size)) {
echo fread($f, 512000);
$downloaded+=512000;
flush();
}
fclose($f);
}

Функция принимает два параметра: $filename — полный путь до файла, $mimetype — MIME-тип файла (если не знаете, что это такое — не указывайте второй параметр при вызове функции).

Вызов функции может быть например таким:
downloadFile(’file/archive.zip’, ‘application/zip’);
// Выдаём пользователю файл “file/archive.zip” и указываем MIME-тип

Или таким:
downloadFile(’i_want_to_break_free.mp3′, ‘audio/mpeg’);
// Выдаём файл “i_want_to_break_free.mp3″ и снова указываем MIME-тип

А можно и вот так:
downloadFile(’somefile.ext’);
// Здесь пользователю отдаётся файл “somefile.ext”, но MIME-тип не указывается.
// Впрочем, ничего страшного в этом нет.

Однако не забудьте вот о чём: функция посылает заголовки header. Значит до вызова функции не должен выводиться никакой текст.

После вызова функции лучше тоже ничего не выводить — некоторые менеджеры закачек не смотрят на то, что необходимые размеры скачаны и качают пока не закончится поток данных. А это приведёт к тому, что в нормальный файл попадёт всякий бред.

Я бы посоветовал сразу после вызова downloadFile() сделать вызов die();

Глупо было бы думать, что я один такой умный (хотя и хотелось бы) . Но Вы можете (вместо моей функции) использовать PEAR-библиотеку HTTP_Download, которая реализует всё то, что и моя функция, и много других возможностей (как-то: кэширование, сжатие данных «на лету» и прочее).

Автор:Евгений Неверов http://evgeny.neverov.name

Tags: Mime, Pear, php, докачка, файл

Добавить комментарий Февраль 28, 2008


Календарь

Июль 2010
Пн Вт Ср Чт Пт Сб Вс
« Апр    
 1234
567891011
12131415161718
19202122232425
262728293031  

Записи по месяцам

Записи по рубрикам

Бегун