Как стать автором
Поиск
Написать публикацию
Обновить

Ограничение скорости скачивания файлов средствами PHP

Время на прочтение3 мин
Количество просмотров17K
Иногда появляется необходимость ограничивать скорость скачивания файлов пользователями. Причин тому может быть множество (бесплатный и платный режимы, регистрация пользователя и т.д.), однако не всегда есть возможность приобрести сервер или настроить его должным образом. В данном топике предлагаю переложить задачу на плечи чистый PHP.

Функция:


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. Мой первый хабрапост (:
Теги:
Хабы:
Всего голосов 30: ↑26 и ↓4+22
Комментарии30

Публикации

Ближайшие события