Скорость работы разделяемой памяти делает ее идеальным выбором для хранения данных, к которым необходим частый доступ со стороны различных процессов веб-сервера, когда файлы или база данных работают слишком медленно. В Примере pc_Web_Abuse_Check class показан класс pc_Web_Abuse_Check, использующий разделяемую память для отслеживания соединений с веб-сайтом, для того чтобы отсечь пользователей, которые злоупотребляют веб-сайтом, бомбардируя его запросами.
Пример pc_Web_Abuse_Check class
class pc_Web_Abuse_Check {
var $sem_key;
var $shm_key;
var $shm_size;
var $recalc_seconds;
var $pageview_threshold;
var $sem;
var $shm;
var $data;
var $exclude;
var $block_message;
function pc_Web_Abuse_Check() {
$this -> sem_key = 5000;
$this -> shm_key = 5001;
$this -> shm_size = 16000;
$this -> recalc_seconds = 60;
$this -> pageview_threshold = 30;
$this -> exclude['/ok-to-bombard.html'] = 1;
$this -> block_message =<<<END
<html>
<head><title>403 Forbidden</title></head>
<body>
<h1>Forbidden</h1>
You have been blocked from retrieving pages from this site due to
abusive repetitive activity from your account. If you believe this
is an error, please contact
<a href="mailto:webmaster@example.com?subject=Site+Abuse"
>webmaster@example.com</a>.
</body>
</html>
END;
}
function get_lock( ) {
$this -> sem = sem_get($this -> sem_key,1,0600);
if (sem_acquire($this -> sem)) {
$this -> shm = shm_attach($this -> shm_key,$this -> shm_size,0600);
$this -> data = shm_get_var($this -> shm,'data');
} else {
error_log("Can't acquire semaphore $this -> sem_key");
}
}
function release_lock( ) {
if (isset($this -> data)) {
shm_put_var($this -> shm,'data',$this -> data);
}
shm_detach($this -> shm);
sem_release($this -> sem);
}
function check_abuse($user) {
$this -> get_lock( );
if ($this -> data['abusive_users'][$user]) {
// если пользователь находится в списке, освобождаем семафор и память
$this -> release_lock( );
// выводим страницу"you are blocked"
header('HTTP/1.0 403 Forbidden');
print $this -> block_message;
return true;
} else {
// фиксируем пользователя, находящегося на странице в настоящий момент
$now = time( );
if (! $this -> exclude[$_SERVER['PHP_SELF']]) {
$this -> data['user_traffic'][$user]++;
}
// (иногда) идем в начало списка и добавляем плохих людей
if (! $this -> data['traffic_start']) {
$this -> data['traffic_start'] = $now;
} else {
if (($now - $this -> data['traffic_start']) > $this ->recalc_seconds) {
while (list($k,$v) = each($this-> data['user_traffic'])) {
if ($v > $this -> pageview_threshold) {
$this -> data['abusive_users'][$k] = $v;
// регистрируем добавление пользователя
// в список злоумышленных пользователей
error_log("Abuse: [$k] (from ".$_SERVER['REMOTE_ADDR'].')');
}
}
$this -> data['traffic_start'] = $now;
$this -> data['user_traffic'] = array();
}
}
$this -> release_lock( );
}
return false;
}
}
Для того чтобы с этим классом можно было работать, в начале страницы вызовите метод check_abuse(), передав ему имя зарегистрированного пользователя:
// get_logged_in_user_name() – это функция, которая определяет,
// зарегистрировался ли пользователь
if ($user = get_logged_in_user_name()) {
$abuse = new pc_Web_Abuse_Check();
if ($abuse -> check_abuse($user)) {
exit;
}
}
Метод check_abuse() защищает исключительный доступ к сегменту
разделяемой памяти, в котором хранится информация о пользователях и трафике, записанная туда с помощью метода get_lock(). Если
пользователь уже находится в списке злоумышленных пользователей,
то метод освобождает блок в разделяемой памяти, выдает пользователю страницу ошибки и возвращает значение true. Страница ошибки
определяется в конструкторе класса.
Если пользователя нет в списке злоумышленных пользователей и текущая страница (сохраненная в элементе $_SERVER['PHP_SELF']) не входит в список страниц, не подлежащих проверке на злоупотребление,
то значение счетчика страниц, просмотренных пользователем, увеличивается. Список страниц, не подлежащих проверке, также определяется в конструкторе. Вызывая метод check_abuse() в начале каждой
страницы и помещая страницы, которые не считаются потенциально
подверженными злоупотреблению, в массив $exclude, вы тем самым
обеспечиваете, что злоумышленный пользователь увидит страницу
ошибки даже при просмотре страницы, которая не учитывается при
подсчете злоупотреблений. Это делает поведение сайта более уравновешенным.
Следующий раздел функции check_abuse() отвечает за добавление
пользователей в список злоумышленных пользователей. Если прошло
более $this -> recalc_seconds секунд с момента последнего добавления
пользователей в список злоумышленных пользователей, то метод
смотрит на счетчики посещения страниц каждого пользователя, и если какой-либо из них превышает значение $this -> pageview_threshold,то они добавляются в список злоумышленных пользователей, а в журнал ошибок помещается сообщение. Код, который устанавливает
$this -> data['traffic_start'], если он еще не установлен, выполняется
только при самом первом вызове функции check_abuse(). После добавления нового злоумышленного пользователя функция check_abuse() сбрасывает счетчик пользователей и счетчик просмотра страниц и
стартует новый интервал до момента следующего обновления списка
злоумышленных пользователей. После освобождения блокировки разделяемой памяти он возвращает false.
Вся информация, необходимая функции check_abuse() для проведения
вычислений, т. е. список злоумышленных пользователей, последние
значения счетчика просмотренных страниц и время последнего определения злоумышленных пользователей, запоминается в единственном ассоциативном массиве data. Это делает чтение и запись в разделяемую память более легкой по сравнению с хранением информации в
отдельных переменных, поскольку требуется только один вызов функции shm_get_var() и один вызов функции shm_put_var().
Класс pc_Web_Abuse_Check блокирует злоумышленных пользователей,
но не имеет никакой системы отчетности и не позволяет добавлять в
список или удалять из списка отдельных пользователей. Пример abuse-manage.php
содержит программу abuse-manage.php, позволяющую манипулировать данными злоумышленных пользователей.
Пример abuse-manage.php
// класс pc_Web_Abuse_Check определен в abuse-check.php
require 'abuse-check.php';
$abuse = new pc_Web_Abuse_Check();
$now = time();
// обрабатываем команды, если они есть
$abuse -> get_lock();
switch ($_REQUEST['cmd']) {
case 'clear':
$abuse -> data['traffic_start'] = 0;
$abuse -> data['abusive_users'] = array();
$abuse -> data['user_traffic'] = array();
break;
case 'add':
$abuse -> data['abusive_users'][$_REQUEST['user']] =
'web @ '.strftime('%c',$now);
break;
case 'remove':
$abuse -> data['abusive_users'][$_REQUEST['user']] = 0;
break;
}
$abuse -> release_lock();
// теперь значимая информация находится в $abuse -> data
print 'It is now <b>'.strftime('%c',$now).'</b><br>';
print 'Current interval started at <b>'.strftime('%c',
$abuse -> data['traffic_start']);
print '</b> ('.($now - $abuse -> data['traffic_start']).' seconds ago).<p>';
print 'Traffic in the current interval:<br>';
if (count($abuse -> data['user_traffic'])) {
print '<table border="1"><tr><th>User</th><th>Pages</th></tr>';
while (list($user,$pages) = each($abuse -> data['user_traffic'])) {
print "<tr><td>$user</td><td>$pages</td></tr>";
}
print "</table>";
} else {
print "<i>No traffic.</i>";
}
print '<p>Abusive Users:';
if ($abuse -> data['abusive_users']) {
print '<table border="1"><tr><th>User</th><th>Pages</th></tr>';
while (list($user,$pages) = each($abuse -> data['abusive_users'])) {
if (0 === $pages) {
$pages = 'Removed';
$remove_command = '';
} else {
$remove_command =
"<a href=\"$_SERVER[PHP_SELF]?cmd=remove&user=".urlencode($user)."\
">remove</a>";
}
print "<tr><td>$user</td><td>$pages</td><td>$remove_command</td></tr>";
}
print '</table>';
} else {
print "<i>No abusive users.</i>";
}
print<<<END
<form method="post" action="$_SERVER[PHP_SELF]">
<input type="hidden" name="cmd" value="add">
Add this user to the abusive users list:
<input type="text" name="user" value="">
<br>
<input type="submit" value="Add User">
</form>
<hr>
<form method="post" action="$_SERVER[PHP_SELF]">
<input type="hidden" name="cmd" value="clear">
<input type="submit" value="Clear the abusive users list">
END;
Пример abuse-manage.php выводит информацию о текущем значении счетчика количества посещений страницы пользователем и текущий список злоумышленных пользователей, как показано на рисунке. Он также позволяет добавлять в список или удалять из списка определенных пользователей и очищать весь список.
При удалении пользователей из списка вместо вызова:
unset($abuse -> data['abusive_users'][$_REQUEST['user']])
он устанавливает следующую переменную в 0:
$abuse -> data['abusive_users'][$_REQUEST['user']]
Это по-прежнему вынуждает функцию возвращать значение false, но
позволяет странице точно отметить, что пользователь находился в
списке злоумышленных пользователей, хотя и был удален из него. Это
полезно в случае, когда удаленный пользователь снова начинает доставлять проблемы.
Когда пользователь добавляется в список злоумышленных пользователей, вместо записи счетчика просмотренных страниц сценарий записывает время, когда пользователь был добавлен. Это полезно, когда
нужно выяснить, кто и почему вручную добавил пользователя в список злоумышленных пользователей.
Если вы помещаете pc_Web_Abuse_Check и эту страницу поддержки на вашем сервере, то обеспечьте защиту страницы поддержки с помощью пароля или каким-то другим способом от публичного доступа. Очевидно,
что от этого кода не очень много толку, если злоумышленные пользователи могут удалить себя из списка злоумышленных пользователей.