Цитата мудреца

Голосование

Если бы Вы жили в обществе, где всё, что Вам нужно можно было бы получить бесплатно, пошли бы Вы работать для пользы этого общества?
 
Система Orphus. Если вы заметили ошибку на сайте, нажмите сюда.
Загружается, подождите...
Начало сайта Материалы сайта Программы PHP-скрипты

Версия для печати

Ограничение на скорость выдачи страниц одному пользователю

Здесь представлены некоторые мои разработки на PHP. Это в, основном, служебные скрипты, которые работают в составе других скриптов и не могут быть протестированы здесь непосредственно.

Ограничение на скорость выдачи страниц одному пользователю

Случилось мне однажды столкнуться с ситуацией, когда мой хостер предъявил мне претензию о том, что мой акаунт создаёт непомерно большую нагрузку на MySQL-сервер. Посмотрев логи, я заметил, что такую нагрузку создают программы-качалки, которые копируют сайт целиком на локальный компьютер. Во время обращения к странице происходит несколько sql-запросов к базе данных. А если учесть, что эти программы готовы скачивать сразу несколько страниц с сайта, то получается, что в секунду идёт от 3 до 10 запросов. При такой «атаке» серверу действительно приходится не сладко.


Решением я увидел ограничение доступа к сайту с одного ip-адреса чаще, чем один раз в 2 секунды. (Это значение можно регулировать). Проверка происходит без использования sql-сервера, поэтому идёт достаточно быстро.


Здесь я предлагаю php-скрипт, реализующий такую проверку. Подключение этого модуля нужно вставлять в самом начале каждой страницы.


Вторую версию скрипта породила ошибка, появляющаяся при одновременном обращении к файлу, хранящему информацию о последних посетителях. Во второй версии посещения записываются в виде файлов нулевой длины в специальную временную директорию. Кроме того, изменён способ учёта интервала: если раньше интервал считался от первого обращения, то теперь от последнего. Таким образом, если раньше при «бомбардировке» сайта раз в несколько секунд страница всё-таки отдавалась, то теперь нормальный показ возобновится лишь после того, как будет полностью выдержан установленный интервал времени.


Третья версия скрипта включает в себя два коренных изменения. Во-первых, если посетитель продолжает атаковать ваш сайт, то через определённое количество запросов его ip-адрес будет внесёт в «чёрный список» файла .htaccess, и ему будет полностью закрыт доступ к сайту. Запрет через .htaccess практически не отнимает у процессора время. Во-вторых, проверка захода поисковых роботов теперь ведётся не по полю HTTP_USER_AGENT, а по ip-адресу. Для этого есть две причины. Первая из них — это то, что, например, поисковая система Aport не всегда подписывается. Наверное, это из-за того, что они боятся, что для их робота будут выдаваться другие страницы, нежели для посетителя-человека. Интересно, что более популярные поисковики не опускаются до такой паранойи. Вторая причина в том, что некоторые программы-качалки ухитряются выдавать себя за поисковых роботов. А вот это уже серьёзно. Поэтому было принято решение пропускать мимо этого скрипта все запросы с ip-адресов, принадлежащим компаниям-поисковикам, т.к. нет уверенности в точных адресах роботов и в том, что эти адреса не будут меняться.



Код: Выделить всё
<?php
/*
*--------------------------------------------------------
* Модуль antiddos.php V3.1.2 Вт 16 Март 2010
* Copyright (C) Андрей Якушев, 2006. http://avy.ru
*--------------------------------------------------------
* Модуль предназначен для ограничения доступа к сайту или
* к страницам, где он включён.
* Принцип работы в том, что запоминается ip-адрес и время
* обращения с этого адреса. И если в течение заданного
* времени происходит обращение с того же адреса, то ему
* выдаётся ошибка 503.
* Если количество недопустимых обращений подряд превышает
* определённое число, ip-адрес закрывается через файл
* .htaccess
* Модуль необходимо подключать к скрипту самым первым.
* Этим обеспечивается быстрота его работы.
*--------------------------------------------------------
*/

/* Директория для временных файлов. Необходимо указать
   отдельную директорию, т.к. большое количество файлов
   в одной папке змедляет скорость обращения к ней. */
define ('AD_DIRNAME', $_SERVER['DOCUMENT_ROOT'] . '/tmp_path');
/* Время задержки в секуднах, в течение которого нельзя
   обращаться к сайту. */
define ('AD_DELAY', 2);
/* Количество запрещённых повторений, после которых ip-адрес
   будет забанен. Нужно обратить внимание на то, что некоторые
   программы чтения RSS-каналов считывают все ссылки, помещённые
   в канале сразу. Поэтому, если на сайте есть такие каналы,
   это число необходимо поставить больше, чем максимальное
   количество элементов в канале. */
define ('AD_TRYING', 35);

/*
*---------------------------------------------------------------
* Список поисковых роботов.
* Очень не хорошо, если поисковый робот будет натыкаться
* на ошибки на сайте. Ему это может сильно не понравиться.
* Поэтому пишем список ip-адресов роботов; добавляем или
* удаляем, что нужно.
* IP-адреса:
* Alta Vista - см. Yahoo
* Aport - 194.67.18.
* Gigabot - 66.231.188. (66.231.188.0/24)
* Google - 209.85.128.0 - 209.85.255.255 (209.85.128.0/17),
*    72.14.192.0 - 72.14.255.255 (72.14.192.0/18), 
*    66.249.64.0 - 66.249.95.255 (66.249.64.0/19),
*    64.68.80.0 - 64.68.87.255 (64.68.80.0/21),
*    66.102.0.0 - 66.102.15.255 (66.102.0.0/20),
*    64.233.160.0 - 64.233.175.255 (64.233.160.0/19),
*    216.239.32.0 - 216.239.63.255 (216.239.32.0/19)
* Mail.Ru - 195.239.211.0 (195.239.211.0/24),
*    94.100.181.128 - 94.100.181.255 (94.100.181.128/25)
* msnbot - 65.52.0.0 - 65.55.255.255 (65.52.0.0/14),
*     207.46.0.0 - 207.46.255.255 (207.46.0.0/16)
* Rambler - 81.19.64.0 - 81.19.66.255 (81.19.64.0/19)
* Yahoo - 74.6.0.0 - 74.6.255.255 (74.6.0.0/16),
*    69.147.64.0 - 69.147.127.255 (69.147.64.0/18)
*    72.30.64.0 - 72.30.255.255 (72.30.0.0/16)
*    67.195.0.0 - 67.195.255.255 (67.195.0.0/16)
* Yandex - 213.180.214.128 - 213.180.214.255 (213.180.192.0/19),
*    77.88.22.0 - 77.88.23.255 (77.88.0.0/18)
*    93.158.128.0 - 93.158.191.255 (93.158.128.0/18)
*    95.108.128.0 - 95.108.255.255 (95.108.128.0/17)
*    87.250.224.0 - 87.250.255.255 (87.250.224.0/19)
* LiveInternet - 88.212.202.0 - 88.212.202.63 (88.212.202.0/26)
* Ip-адреса и маски к ним кодируются в виде строки из 4 символов
* Это сделано из-за того, что невозможно в PHP использовать
* стандартными (простыми) процедурами 32-битное целое число
* без знака. А использование специальных библиотек усложнит
* работу и сделает скрипт зависимым от этих библиотек.
*---------------------------------------------------------------
*/
$ad_Robots_IP = array(
   'Aport'      => array(
      sprintf('%c%c%c%c', 194, 67, 18, 0),
      sprintf('%c%c%c%c', 255, 255, 255, 0)
   ),
   'Gigabot'   => array(
      sprintf('%c%c%c%c', 66, 231, 188, 0),
      sprintf('%c%c%c%c', 255, 255, 255, 0)
   ),
   'Google1'   => array(
      sprintf('%c%c%c%c', 209, 85, 128, 0),
      sprintf('%c%c%c%c', 255, 255, 128, 0)
   ),
   'Google2'   => array(
      sprintf('%c%c%c%c', 72, 14, 192, 0),
      sprintf('%c%c%c%c', 255, 255, 192, 0)
   ),
   'Google3'   => array(
      sprintf('%c%c%c%c', 66, 249, 64, 0),
      sprintf('%c%c%c%c', 255, 255, 224, 0)
   ),
   'Google4'   => array(
      sprintf('%c%c%c%c', 64, 68, 80, 0),
      sprintf('%c%c%c%c', 255, 255, 248, 0)
   ),
   'Google5'   => array(
      sprintf('%c%c%c%c', 66, 102, 0, 0),
      sprintf('%c%c%c%c', 255, 255, 240, 0)
   ),
   'Google6'   => array(
      sprintf('%c%c%c%c', 64, 233, 160, 0),
      sprintf('%c%c%c%c', 255, 255, 224, 0)
   ),
   'Google7'   => array(
      sprintf('%c%c%c%c', 216, 239, 32, 0),
      sprintf('%c%c%c%c', 255, 255, 224, 0)
   ),
   'Mail.Ru1'   => array(
      sprintf('%c%c%c%c', 195, 239, 211, 0),
      sprintf('%c%c%c%c', 255, 255, 255, 0)
   ),
   'Mail.Ru2'   => array(
      sprintf('%c%c%c%c', 94, 100, 181, 128),
      sprintf('%c%c%c%c', 255, 255, 255, 128)
   ),
   'msnbot1'   => array(
      sprintf('%c%c%c%c', 65, 52, 0, 0),
      sprintf('%c%c%c%c', 255, 252, 0, 0)
   ),
   'msnbot2'   => array(
      sprintf('%c%c%c%c', 207, 46, 0, 0),
      sprintf('%c%c%c%c', 255, 255, 0, 0)
   ),
   'Rambler'   => array(
      sprintf('%c%c%c%c', 81, 19, 64, 0),
      sprintf('%c%c%c%c', 255, 255, 224, 0)
   ),
   'Yahoo1'   => array(
      sprintf('%c%c%c%c', 74, 6, 0, 0),
      sprintf('%c%c%c%c', 255, 255, 0, 0)
   ),
   'Yahoo2'   => array(
      sprintf('%c%c%c%c', 69, 147, 64, 0),
      sprintf('%c%c%c%c', 255, 255, 192, 0)
   ),
   'Yahoo3'   => array(
      sprintf('%c%c%c%c', 72, 30, 0, 0),
      sprintf('%c%c%c%c', 255, 255, 0, 0)
   ),
   'Yahoo4'   => array(
      sprintf('%c%c%c%c', 67, 195, 0, 0),
      sprintf('%c%c%c%c', 255, 255, 0, 0)
   ),
   'Yandex1'   => array(
      sprintf('%c%c%c%c', 213, 180, 192, 0),
      sprintf('%c%c%c%c', 255, 255, 224, 0)
   ),
   'Yandex2'   => array(
      sprintf('%c%c%c%c', 77, 88, 0, 0),
      sprintf('%c%c%c%c', 255, 255, 192, 0)
   ),
   'Yandex3'   => array(
      sprintf('%c%c%c%c', 93, 158, 128, 0),
      sprintf('%c%c%c%c', 255, 255, 192, 0)
   ),
   'Yandex4'   => array(
      sprintf('%c%c%c%c', 95, 108, 128, 0),
      sprintf('%c%c%c%c', 255, 255, 128, 0)
   ),
   'Yandex5'   => array(
      sprintf('%c%c%c%c', 87, 250, 224, 0),
      sprintf('%c%c%c%c', 255, 255, 224, 0)
   ),
   'LiveInternet' => array(
      sprintf('%c%c%c%c', 88, 212, 202, 0),
     sprintf('%c%c%c%c', 255, 255, 255, 192)
   ),
);

/*
*----------------------------------------------------------
* Функция создаёт в указанной директории файл, начинающийся
* с буквы a (для отличия от других возможных файлов) и
* содержащий в имени ip-адрес клиента.
* Если количество обращений подряд с данного адреса
* превысило допустимый предел, адрес закрывается через файл
* .htaccess
* При желании может быть отослано письмо на специальный
* адрес, в котором будет передана информация о действиях
* с заблокированного адреса.
*----------------------------------------------------------
*/
function ad_WriteIP($counter)
{
   $counter++;
   if ($counter > AD_TRYING)
   {
      //Баним ip-адрес
      $f = fopen($_SERVER['DOCUMENT_ROOT'] . '/.htaccess', 'a');
      fwrite($f, "\ndeny from " . $_SERVER['REMOTE_ADDR']);
      fclose($f);
      // Открыть комментарии, если нужно уведомлять по почте
      /*
      //Получаем хост (в некоторых логах он может быть вместо ip
      $host = gethostbyaddr($_SERVER['REMOTE_ADDR']);
      $mess = 'Заблокирован адрес ' . $_SERVER['REMOTE_ADDR'] .
         ' (' . $host . ")\n\n";
      //Выполняем запрос к логам. Нужно указать путь и имя лог-файла
      exec('cat /home/your_dir/logs/access_log | egrep \'(' .
         str_replace('.', '\\.', $_SERVER['REMOTE_ADDR']) . ')|(' .
         str_replace('.', '\\.', $host) . ')\' | sort -k 4 >' .
         AD_DIRNAME . '/dump.txt');
      $mess .= file_get_contents(AD_DIRNAME . '/dump.txt');
      @ unlink(AD_DIRNAME . '/dump.txt');
      $email = 'my_box@myhost.ru'; //Укажите свой e-mail
      mail($email, 'IP-address was banned', $mess, "From: " . $email .
         "\nReply-To: " . $email .
         "\nContent-Type: text/plain; charset=Windows-1251" .
         "\nContent-Transfer-Encoding: 8bit");
      */
   }
   else
   {
      //Записываем файл с ip-адресом и количеством обращений
      $f = fopen(AD_DIRNAME . '/a' . $_SERVER['REMOTE_ADDR'] . '_' .
         $counter, 'w');
      fclose($f);
   }
}
/*
*----------------------------------------------------
* Проверка на отношение ip-адреса к сетям поисковиков
*----------------------------------------------------
*/
$ad_IsRobot = false;
$ad_IP = explode('.', $_SERVER['REMOTE_ADDR']);
$ad_IPMatch = sprintf('%c%c%c%c', $ad_IP[0], $ad_IP[1], $ad_IP[2], $ad_IP[3]);
foreach ($ad_Robots_IP as $ad_match)
{
   //Если на входящий адрес наложить маску операцией "и",
   //то он должен будет совпасть с начальным адресом сети.
   if (($ad_IPMatch & $ad_match[1]) == $ad_match[0])
   {
      $ad_IsRobot = true;
      break;
   }
}

/*
*---------------------------------------------------------
* Поисковые роботы не любят, когда к адресу страницы
* добавляется переменная сессии. Поэтому, если на сайте
* используются сессии, то их лучше включать, если агент -
* не робот.
* Если сессии не используются, то этот кусок можно убрать.
*---------------------------------------------------------
*/
/*
if (!$ad_IsRobot)
{
   session_start();
}
else
{
   /* Чтобы поисковый робот не сильно загружал сайт,
   делаем ему задержку */
   sleep(AD_DELAY);
}
*/

if (!$ad_IsRobot)
{
   /*** Чтение каталога и удаление старых файлов ***/
   $ad_dir      = opendir(AD_DIRNAME) or
      die('Отсутствует директория для временных файлов');
   $ad_forbid   = time() - AD_DELAY;
   /* IP-адрес в имени файла, начинающегося на букву a,
   а время обращения - время изменения файла */
   $ad_before_trying = 0;
   while (false !== ($ad_FName = readdir($ad_dir)))
   {
      if (ereg('^a[1-9]', $ad_FName))
      {
         if (@ filemtime(AD_DIRNAME . '/' . $ad_FName) < $ad_forbid){
            @ unlink(AD_DIRNAME . '/' . $ad_FName);
         }
         elseif (ereg('^a' . str_replace('.', '\\.',
            $_SERVER['REMOTE_ADDR']) . '_([0-9]+)$', $ad_FName, $ad_match))
         {
            //Если файл есть, то читаем, сколько раз к нму обращались
            $ad_before_trying = intval($ad_match[1]);
            @ unlink(AD_DIRNAME . '/' . $ad_FName);
         }
      }
   }
   closedir($ad_dir);
   /*** Выводить или не выводить сообщение об ошибке ***/
   if ($ad_before_trying > 0)
   {
      /* Если обращение было недавно, то выводим сообщение об ошибке */
      header ('HTTP/1.0 503 Service Unavailable');
      header ('Status: 503 Service Unavailable');
      header ('Retry-After: ' . $ad_delay * 3);
?>
<!doctype html public "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>Ошибка 503</title>
<meta http-equiv="Content-Type" content="text/html; charset=Windows-1251" />
</head>
<body>
<h1>Ошибка 503 (Service Unavailable)</h1>
<p>Сервер не может в данный момент выдать запрашиваемую Вами страницу.
Попробуйте вызвать эту страницу позже (клавиша F5).</p>
</body>
</html>
<?php
      ad_WriteIP($ad_before_trying);   // Перед выходом записываем ip
      exit;
   }else{
      ad_WriteIP($ad_before_trying);
   }
}
?>
Ответить

Пред.След.

Вернуться в PHP-скрипты



Кто сейчас на сайте

Зарегистрированные пользователи: Сандра МунЛайт, psbot [Picsearch], Yahoo [Bot], Yandex [bot]