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

Голосование

Как Вы относитесь к мату?
 
Система 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.1 Вт 15 Сентябрь 2009
* 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)
* 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)
   ),
   'msnbot'   => array(
      sprintf('%c%c%c%c', 65, 52, 0, 0),
      sprintf('%c%c%c%c', 255, 252, 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-скрипты



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

Зарегистрированные пользователи: Yahoo [Bot]