Иногда появляется необходимость ограничивать скорость скачивания файлов пользователями. Причин тому может быть множество (бесплатный и платный режимы, регистрация пользователя и т.д.), однако не всегда есть возможность приобрести сервер или настроить его должным образом. В данном топике предлагаю переложить задачу на плечи чистый PHP.
$speed — скорость скачивания.
Для того, чтобы пользователь скачивал файл с необходимой скоростью и не мог обойти ограничение при помощи программ обеспечивающих скачивание в несколько потоков, нам необходимо установить их лимит для одного посетителя. Для этого нам потребуется ввести дополнительную функцию, которая будет проверять наличие уже установленных соединений. В качестве СУБД предлагаю использовать MySQL.
Итак, создаем таблицу. Так и назовем — `file_session`. В данном примере нам необходимо всего одно поле `session_ip` которое будет содержать IP-адрес скачивающего. При наличии необходимого IP в БД отдаем true, в противном случае записываем его и отдаем false.
В последствии функцию можно модернизировать, добавив еще и проверку на залогиненность, наличие оплаты и т.д.
Полный код и рабочий пример скачивания без ограничения и с ограничением в 10 Кб/сек — файл 1 Мб.
P.S. Мой первый хабрапост (:
Функция:
function loadfile ($filename, $speed=false) {
// содержимое функции ниже
}
$filename — адрес файла, который собираемся отдавать.$speed — скорость скачивания.
Убираем лимиты и ограничения, запускаем буферизацию вывода:
// лимит времени выполнения
set_time_limit(0);
// продолжаем выполнятся при отключении пользователя
ignore_user_abort(true);
// старт буферизации
ob_start();
Собираем необходимые данные о файле:
// размер файла
$filesize = filesize($filename);
// время последнего изменения файла
$filetime = gmdate('r', filemtime($filename));
// генерируем Etag
$etag = md5($filename."=".$filesize."=".$filetime);
$etag = substr($etag, 0, 8).'-'.substr($etag, 8, 7).'-'.substr($etag, 15, 8);
Формируем и отдаем заголовки:
// Если «просят» отдать не весь файл, а только его часть (докачка), то так и «отвечаем»
if (isset($_SERVER['HTTP_RANGE'])) {
// разделяем строку $_SERVER['HTTP_RANGE'] на подстроки, чтобы иметь
// представление о необходимой пользователю части файла
$range = substr($_SERVER['HTTP_RANGE'], strpos($_SERVER['HTTP_RANGE'], '=')+1);
$from = (integer)(strtok($range, "-")); // с какого байта начать часть
$to = (integer)(strtok("-")); // каким байтом закончить
// Отдаем заголовки
header('HTTP/1.1 206 Partial Content');
header('Content-Range: bytes '.$from.'-'.($to-1).'/'.$filesize);
} else {
header('HTTP/1.1 200 Ok');
}
// В случае, если клиент не передал информацию о первом последнем байте, то присвоим
// самостоятельно.
if ($to==0) $to = $filesize;
if (empty($from)) $from = 0;
Ограничение потоков для одного пользователя:
Для того, чтобы пользователь скачивал файл с необходимой скоростью и не мог обойти ограничение при помощи программ обеспечивающих скачивание в несколько потоков, нам необходимо установить их лимит для одного посетителя. Для этого нам потребуется ввести дополнительную функцию, которая будет проверять наличие уже установленных соединений. В качестве СУБД предлагаю использовать MySQL.
Итак, создаем таблицу. Так и назовем — `file_session`. В данном примере нам необходимо всего одно поле `session_ip` которое будет содержать IP-адрес скачивающего. При наличии необходимого IP в БД отдаем true, в противном случае записываем его и отдаем false.
function is_active_user($clear = false) {
global $dbi; // это подключение к БД
// проверяем на наличие соединений от пользователя
$result = mysql_query ("SELECT `session_ip` FROM `file_session` WHERE `session_ip`='".$_SERVER['REMOTE_ADDR']."' LIMIT 1", $dbi);
if (mysql_num_rows($result)) {
// если установлено $clear, значит пользователь
//разорвал соединение. Удаляем запись.
if (!$clear) {
return true;
} else {
mysql_query ("DELETE FROM `file_session` WHERE session_ip='".$_SERVER['REMOTE_ADDR']."' LIMIT 1", $dbi);
}
} else {
// если запись отсутствует, то добавляем
mysql_query ("INSERT INTO `file_session` VALUES ('".$_SERVER['REMOTE_ADDR']."')", $dbi);
return false;
}
}
В последствии функцию можно модернизировать, добавив еще и проверку на залогиненность, наличие оплаты и т.д.
Отдаем файл:
// Отдаем заголовки
header('ETag: "' . $etag . '"');
header('Accept-Ranges: bytes');
header('Content-Length: ' . ($filesize-$from));
header('Content-Type: application/octet-stream');
header('Last-Modified: ' . gmdate('r', filemtime($filename)));
header('Content-Disposition: attachment; filename="' . $filename . '";');
// Проверяем, есть ли у пользователя
// активные потоки is_active_user()
// и не отключился ли он connection_status().
while(is_active_user() and !connection_status()) {
// спим пока у пользователя есть активные потоки
sleep(1);
}
// Открываем файл
$f = fopen($filename, 'rb');
// Устанавливаем указатель а нужную позицию
fseek($f, $from, SEEK_SET);
// Устанавливаем общий объем части и объявляем переменную хранящую скачанный объем
$size = $to - $from;
$isready = 0;
// Начинаем отдачу
while(!feof($f) and !connection_status() and ($isready<$size)) {
// Если ограничений нет, то считываем по 0.5 Мб и отдаем без задержек.
// Установлено ограничение? Считываем и отдаем ровно столько, сколько установлено ограничением и засыпаем на 1 секунду.
echo fread($f, !$speed?512000:$speed); // считываем и отдаем
flush(); ob_flush(); // очищаем буфер и выводим пользователю
if ($speed) sleep(1); // засыпаем
$isready += !$speed?512000:$speed; // обновляем счетчик
}
// Закрываем файл
fclose($f);
// Удаляем информацию о соединении из БД
is_active_user(true);
Вызов функции:
loadfile ("/files/moifilm.avi", 10240); // Скорость указываем в байтах
Итог:
Полный код и рабочий пример скачивания без ограничения и с ограничением в 10 Кб/сек — файл 1 Мб.
P.S. Мой первый хабрапост (: