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

Допустим, у нас есть сайт, предоставляющий файлы для скачивания. Но для зарегистрированных пользователей нет никаких ограничений, а для остальных, чтобы не перегружать сервер, хотелось бы ввести некоторые ограничения. Причина для ограничения скорости скачивания может быть и иной: пользователь, оплативший скачивание без ограничений - получает файл на максимальной скорости, а тот, кто пожадничал - будет скачивать очень медленно. Посмотрим, как это можно реализовать с помощью PHP:

<?php
// лимит времени выполнения
set_time_limit(0);

// скорость скачивания - 128КБ в сек.
$speed = 1024*128;
// имя файла
$filename = 'doc.pdf';
// размер файла
$filesize = filesize($filename);
// смещение от начала файла
$range = 0;

while( is_already_download() ) {
  // спим пока у пользователя есть активные потоки
  sleep(1);
}

// открываем файл на чтение
$f = fopen($filename, 'rb');

if (isset($_SERVER['HTTP_RANGE'])) { // поддерживается ли докачка
  $range = $_SERVER['HTTP_RANGE'];
  $range = str_replace('bytes=', '', $range);
  $range = str_replace('-', '', $range);
  if ($range) fseek($f, $range);
}
 
// если есть смещение
if ($range) {
  header($_SERVER['SERVER_PROTOCOL'].' 206 Partial Content');
} else {
  header($_SERVER['SERVER_PROTOCOL'].' 200 OK');
}

header( 'Last-Modified: '.date('D, d M Y H:i:s T', filemtime($filename)) );
header('Content-Length: '.($filesize-$range));
header('Accept-Ranges: bytes');
header('Content-Range: bytes '.$range.'-'.($filesize - 1).'/'.$filesize);
header('Content-Type: application/pdf');
header('Content-Disposition: attachment; filename="'.$filename.'"');

while( !feof($f) ) {
  echo fread($f, $speed);
  flush();
  sleep(1); // засыпаем
}

// закрываем файл
fclose($f);

// удаляем информацию о соединении из БД
mysql_query("DELETE FROM `sessions` WHERE `session_ip`='".$_SERVER['REMOTE_ADDR']."' LIMIT 1");

function is_already_download() {
  // проверяем на наличие соединений от пользователя
  $res = mysql_query("SELECT `session_ip` FROM `sessions` WHERE `session_ip`='".$_SERVER['REMOTE_ADDR']."' LIMIT 1");
  if (mysql_num_rows($res)) {
    return true;
  } else { // если запись отсутствует, то добавляем
    mysql_query ("INSERT INTO `sessions` VALUES ('".$_SERVER['REMOTE_ADDR']."')");
    return false;
  }
}
?>

Однако пользователь без привилегий может воспользоваться менеджером закачки, который позволяет скачивать файл в несколько потоков. Таким образом, он сможет без труда обойти наше ограничение. На этот случай есть функция is_already_download(), которая проверяет наличие уже установленных соединений.

В данном случае мы используем таблицу БД sessions, в которй всего одно поле - IP-адрес скачивающего. При наличии IP-адреса скачивающего в таблице отдаем true, в противном случае записываем его и отдаем false.

Ссылки по теме:

Комментариев: 11

  1. Max Antonov:

    Хотелось бы узнать, а зачем применять php для того, что делается средствами практически любого современного веб-сервера?

    Если только забавы ради.
    Я люблю PHP, но для таких целей его применять… простите, мазохизм!

  2. LiC:

    1) зато прикольно
    2) не всегда есть возможность что-либо делать с настройками сервера. порой встечаются весьма “специфические” хостеры (особенно забугорные)

    автору - респект ))

  3. тима:

    не так сложно как кажется на первый взгляд. Внедрить довольно просто и быстро.

  4. Piter:

    Все-так ограничивать такие скачки нужно, а то смысл тогда всей регистрации пропадет.

  5. ShadX:

    Если привязываться к серверу *настройкам*, то при смене хостера будут грабли. А так решение небольшое и в принципе безгеморное …
    Надо будет попробовать …

  6. Вася:

    Что за убожество?

    0) {
    while(!feof($f)) {
    $time_start = microtime(true);
    echo fread($f, ceil($speed*$time_discret));
    flush();
    $time_end = microtime(true);
    $time = $time_end - $time_start;
    if($time_discret-$time > 0) usleep(($time_discret-$time)*1000000);
    }
    }
    else {
    while(!feof($f)) {
    echo fread($f, 1024);
    flush();
    }
    }
    fclose($f);
    } else {
    header($_SERVER["SERVER_PROTOCOL"] . ‘ 404 Not Found’);
    header(’Status: 404 Not Found’);
    }
    exit;
    }

    function thread_number(){
    $db=mysql_connect(”$dbhost”,”$dbuser”, “$dbpass”);

    mysql_select_db(”bla-bla” ,$db);
    $res = mysql_query(”SELECT `count` FROM `threads` WHERE `ip` = ‘$IP’;”, $db);
    if ($line = mysql_fetch_array($res, MYSQL_ASSOC)) return (int) $line['count'];
    return 0;
    }

    function thread_start(){
    $db=mysql_connect(”$dbhost”,”$dbuser”, “$dbpass”);

    mysql_select_db(”bla-bla” ,$db);

    if(thread_number() == 0) {
    mysql_query(”INSERT INTO `threads` (`ip`, `count`, `time`, `date`) VALUES (’$IP’, ‘1′, CURTIME(), CURDATE())”, $db);
    } else {
    mysql_query(”UPDATE `threads` SET `count` = `count` + 1 WHERE `ip` = ‘$IP’;”, $db);
    }
    }

    function thread_stop(){
    $db=mysql_connect(”$dbhost”,”$dbuser”, “$dbpass”);

    mysql_select_db(”bla-bla” ,$db);

    $number = thread_number();
    if($number == 1) {
    mysql_query(”DELETE FROM `threads` WHERE `ip` = ‘$IP’;”, $db);
    }
    else {
    mysql_query(”UPDATE `threads` SET `count` = `count` - 1 WHERE `ip` = ‘$IP’;”, $db);
    }
    }
    ?>

    PS: Не жалко. Когда-то давно взял из паблика и переделал пару строчек.

  7. Вася:

    Сайт отрезал половину скрипта… Жирный, видимо, слишком :).

  8. Вася:

    Вот, что он отрезал:
    0) {

  9. Вася:

    Ну, блин, жадный сайт какой-то, постоянно этот кусок проглатывает. Фиг с ним, кому нужно нагуглит, где-то в сети этот скрипт должен воляться.

  10. Alex:

    да это хорошее решение вот только еще надо сделать небольшую проверку $filename при закачке. дабы никому не вздумалось левый $name вставить, и скачать то что Вы совсем не раздаете :)))

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