Для журнала "Мой компьютер". В журнале публиковалось под названием "Perl'ы для веб-мастера".

Perl'ы для веб-мастера

Евгений Гривастов (tw@tv-agent.net)

(...продолжение…)

Глава 6. Несколько счетчиков на одном скрипте.

Итак, мы создали простейший счетчик, который обсчитывает количество заходов на страницу. Теперь давайте научим его считать заходы на разные страницы. Для этого каждой странице присвоим свой идентификатор и укажем его в качестве параметра при вызове скрипта. Т.е. если, например, идентификатор страницы будет "first", то url вызова скрипта будет выглядеть следующим образом:

<!--#exec cgi="/cgi-bin/count.cgi?first"-->

Чтобы получить значение переданного в скрипт параметра, добавим строку

$pageid=$ENV{'QUERY_STRING'};

в начало скрипта (например второй строкой). Таким образом в переменной $pageid у нас окажется значение first. В нашей директории mystat давайте создадим два пустых файла: first.txt и firstip.txt - в первом будем хранить значение счетчика, а во втором - IP адрес последнего посчитанного посетителя (не забудьте назначить обоим файлам права доступа 666). Теперь осталось научить наш счетчик брать и записывать данные в нужные файлы. Для этого изменим значение двух переменных:

$namefile="/home2/your_domen/public_html/mystat/".$pageid.".txt";
$nameipfile="/home2/your_domen/public_html/mystat/".$pageid."ip.txt";

Знак "." (точка) означает соединение строк. Т.е. при значении переменной $pageid="first", в переменной $namefile получим значение "/home2/your_domen/public_html/mystat/first.txt", а в переменной $nameipfile - значение "/home2/your_domen/public_html/mystat/firstip.txt". Таким образом теперь если мы захотим для другой нашей страницы создать счетчик с идентификатором second, достаточно будет создать в директории mystat два файла: second.txt и secondip.txt, и вставить в страницу код вызова скрипта с значением second в качестве параметра:

<!--#exec cgi="/cgi-bin/count.cgi?second"-->

Глава 7. Повышение безопасности и автоматизация добавления новых счетчиков

Настало время подумать немного и о секьюрити нашего скрипта. Давайте для начала научим его хотя бы проверять, с какой страницы он вызван. И если вызов пришел не с родной страницы - просто завершать работу скрипта. Заодно спрячем от постороннего взгляда настояшие имена файлов счетчиков.

Прежде всего нам понадобиться еще один файл, в котором будем хранить идентификаторы счетчиков, url-ы страниц, на которых счетчики расположены и имена файлов, имеющих отношение к счетчикам. Назовем его counters.txt, поместим в директорию mystat и присвоим права доступа 666. Формат хранимых данных будет следующий:

идентификатор_счетчика|url_страницы|имя_файла_счетчика|имя_файла_последнего_IP

В одной стоке - данные по одному счетчику, разделенные символом "|".

Давайте для начала сделаем скрипт, который поможет нам легко через веб-форму добавлять добавлять новые счетчики. Создадим файл addcount.cgi, поместим в него следующий код:

#!/usr/bin/perl

$namefile="/home2/your_domen/public_html/mystat/counters.txt";

&GetFormInput;

$countid=$field{"countid"};
$url=$field{"url"};
$countfile=$field{"countfile"};
$countipfile=$field{"countipfile"};

open(FILE, ">>$namefile");
print FILE $countid.'|'.$url.'|'.$countfile.'|'.$countipfile."\n";
close(FILE);

exit;

sub GetFormInput {

(*fval) = @_ if @_ ;

local ($buf);
if ($ENV{'REQUEST_METHOD'} eq 'POST') {
read(STDIN,$buf,$ENV{'CONTENT_LENGTH'});
}
else {
$buf=$ENV{'QUERY_STRING'};
}
if ($buf eq "") {
return 0 ;
}
else {
@fval=split(/&/,$buf);
foreach $i (0 .. $#fval){
($name,$val)=split (/=/,$fval[$i],2);
$val=~tr/+/ /;
$val=~ s/%(..)/pack("c",hex($1))/ge;
$name=~tr/+/ /;
$name=~ s/%(..)/pack("c",hex($1))/ge;

if (!defined($field{$name})) {
$field{$name}=$val;
}
else {
$field{$name} .= ",$val";
}


}
}
return 1;
}

Здесь мы столкнулись с новым понятием - подпрограмма. Это то же самое, что в других языках программирования называется "функция" или "процедура". Объявляется подпрограмма с помощью ключевого слова sub. В нашем случае это объявление выглядит как sub GetFormInput. Далее в фигурных скобках идет собственно тело подпрограммы. Используемая в этом скрипте подпрограмма разбирает значения полей запроса, полученных от html формы. Вы можете использовать ее во всех своих скриптах, взаимодействующих с html формами. Вызывается эта подпрограмма в нашем скрипте с помощью строки:

&GetFormInput;

В результате ее работы создается хэш %field, содержащий значения полученных от формы полей связанные с именами этих полей.

Небольшое отступление: здесь необходимо объяснить, что такое хэш. Хэш - это как массив, только данные в нем связаны не с номерами позиции в массиве, а с другим скаляром, называемым ключем. Доступ к элементам хэша осуществляется таким образом: $hash{$key}=$value (в этом примере мы ассоциируем с ключем $key значение $value). Имя хэш-переменной начинается со знака % (процент) - %hash. Такое обращение к хэшу используется, когда надо обратится ко всему хэшу целиком. Чтобы получить значение, ассоциированое с ключем $key, надо обратиться к хэшу следующим образом - $hash{$key}.

Что делается внутри подпрограммы GetFormInput мы пока разбирать не будем, чтобы не удаляться от темы нашего разговора.

Итак, что делает наш скрипт. Прежде всего, помещаем в переменную $namefile путь на файл counters.txt. Далее идет вызов подпрограммы GetFormInput, в результате которой у нас появляется хэш %field, содержащий названия и значения полей, полученных от html формы. Поместим эти значения в соответствующие переменные ($countid, $url, $countfile, $countipfile). После этого мы открываем файл counters.txt для добавления в него записей (символы ">>") и записываем в него новую строку, содержащую нужные нам значения. Завершаем работу скрипта.

Для работы с вновьсозданным скриптом сделаем небольшую html форму. Код ее будет выглядеть так:

<form action="http://your_domen.com/cgi-bin/addcount.cgi" method="post">
ID счетчика: <input type="text" name="countid" size="20"><br>
URL страницы: <input type="text" name="url" size="20"><br>
Название файла счетчика: <input type="text" name="countfile" size="20"><br>
Название файла с последним IP: <input type="text" name="countipfile" size="20"><br>
<input type="submit" value="Создать счетчик">
</form>

Здесь значение http://your_domen.com/cgi-bin/addcount.cgi - это URL вызова скрипта. Осталось научить наш скрипт самостоятельно создавать файлы нового счетчика. Для этого добавим в скрипт (перед строкой exit;) следующие строки:

$dir="/home2/your_domen/public_html/mystat/";

open(FILE, ">$dir$countfile");
close(FILE);
open(FILE, ">$dir$countipfile");
close(FILE);
chmod (0666, $dir$countfile, $dir$countipfile);

В переменную $dir помещаем путь на директорию, где будут лежать файлы. После этого создаем оба файла (если они уже существуют, то будут очищены) и устанавливаем для них права доступа 666 (возможность чтение и записи).

Осталось научить наш счетчик использовать информацию, помещенную в файл counters.txt. Модернизируем его следующим образом:

#!/usr/bin/perl

$pageid=$ENV{'QUERY_STRING'};
$ip=$ENV{"REMOTE_ADDR"};
$ref=$ENV{"HTTP_REFERER"};
$countersfile="/home2/your_domen/public_html/mystat/counters.txt";

open (IPFILE, "$countersfile");
METKA: while ($stroka=<IPFILE>) {
chomp($stroka);
($countid, $url, $countfile, $countipfile)=split(/|/,$stroka);
last METKA if ($countid eq $pageid);
}
close(IPFILE);

exit if ($url ne $ref);

$namefile="/home2/your_domen/public_html/mystat/".$countfile;
$nameipfile="/home2/your_domen/public_html/mystat/".$countipfile;

open (IPFILE, "$nameipfile");
$ipold = <IPFILE>;
close(IPFILE);
chomp ($ipold);

if ($ipold eq $ip) {
exit;
}

open (IPFILE, "$nameipfile");
print IPFILE "$ip";
close(IPFILE);

open (COUNTFILE, "+<$namefile");
flock (COUNTFILE,2);
$count =readline(*COUNTFILE);
chomp ($count);
$count=$count+1;
seek (COUNTFILE,0,0);
truncate(COUNTFILE,0);
print COUNTFILE "$count";
close(COUNTFILE);

print "Content-Type: text/html\n\n";
print "$count";

exit;

Фактически изменению подверглась только первая половина скрипта. Получив из запроса значение ID счетчика, URL страницы, с которой счетчик был вызван и IP адрес посетителя, мы перебираем строки в файле counters.txt пока не найдем строку с данными нашего ID счетчика. Для этого сначала у прочитанной из файла строки удаляется конечный символ перевода строки (команда chomp($stroka)), потом строка разбивается на четыре значения с помощью команды split (указав в качестве разделителя символ "|"). Как только мы находим строку со значением ID, совпадающим с полученным в качестве параметра - цикл завершается командой last. Далее проверяем: если URL страницы, вызвавшей счетчик, не совпадает с URL-ом, закрепленным за счетчиком - просто завершаем работу скрипта. После этого работа продолжается так же, как и ранее.

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

#!/usr/bin/perl

$countersfile="/home2/your_domen/public_html/mystat/counters.txt";

open (IPFILE, "$countersfile");
while ($stroka=<IPFILE>) {
chomp($stroka);
($countid, $url, $countfile, $countipfile)=split(/|/,$stroka);
$namefile="/home2/your_domen/public_html/mystat/".$countfile;
open (COUNTFILE, "$namefile");
$reiting{$url}=readline(*COUNTFILE);
close(COUNTFILE);
}
close(IPFILE);

print "Content-Type: text/html\n\n";

foreach $url (sort keys %reiting) {
print "$url => $reiting{$url}<br>";
}

exit;

В этом скрипте не используется практически ничего нового, по сравнению с описанным ранее. Разве что способ перебора хэша и сортировки его элементов. В качестве ключей в хэше используем URL-ы страниц. Функция keys выделяет ключи хэша в отдельный массив, который мы сортируем с помощью функции sort и перебираем по очереди его элементы в цикле foreach, присваивая значения переменной $url.

Поместите этот скрипт в директорию cgi-bin сервера (не забудьте - в режиме txt!!!), назначим ему права 755 и вызовем через броузер (набрав URL скрипта). В результате, если у вас уже заведено несколько счетчиков для нескольких страниц - вы увидите список страниц со значениями счетчика для каждой из них.

Глава 8. Соберем статистику.

При вызове скрипта броузер передает ему, кроме явно заданных нами параметров, еще несколько. Некоторые из них мы уже использовали ($ENV{'QUERY_STRING'}, $ENV{"REMOTE_ADDR"}, $ENV{"HTTP_REFERER"}). Разберем, что есть что в этих загадочных словах.

QUERY_STRING - строка параметров, передаваемых в составе URL
REMOTE_ADDR - IP адрес агента, выполнившего запрос. Это может быть как адрес клиента, так и адрес proxy или любого другого промежуточного объекта соединения.
HTTP_REFERER - содержит значение поля "Referer:" заголовка запроса.

а вот эти значения мы еще не трогали, но они могут нам пригодиться для статистики посещений:

REMOTE_HOST - доменное имя агента, выполнившего запрос (если такого имени нет, то переменная содержит то же значение, что и REMOTE_ADDR).
HTTP_USER_AGENT - информация о броузере, обычно содержит название, версию.

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

Название файла статистики: <input type="text" name="countstatfile" size="20"><br>

Теперь немного изменим скрипт создания нового счетчика (addcount.cgi) следующим образом:

после строки

$countipfile=$field{"countipfile"};

добавим строку

$countstatfile=$field{"countstatfile"};

после строк

open(FILE, ">$dir$countipfile");
close(FILE);

добавим строки

open(FILE, ">$dir$countstatfile");
close(FILE);

и изменим строку

chmod (0666, $dir$countfile, $dir$countipfile);

следующим образом

chmod (0666, $dir$countfile, $dir$countipfile, $dir$countstatfile);

а строка

print FILE $countid.'|'.$url.'|'.$countfile.'|'.$countipfile."\n";

теперь будет выглядеть так

print FILE $countid.'|'.$url.'|'.$countfile.'|'.$countipfile.'|'.$countstatfile."\n";

Осталось модифицировать собственно скрипт счетчика, чтобы он заполнял файлы статистики соответственной информацией:

строку

($countid, $url, $countfile, $countipfile)=split(/|/,$stroka);

сделаем такой

($countid, $url, $countfile, $countipfile, $countstatfile)=split(/|/,$stroka);

и после строк

open (IPFILE, "$nameipfile");
print IPFILE "$ip";
close(IPFILE);

добавим следующие строки:

$namestatfile="/home2/your_domen/public_html/mystat/".$countstatfile;
open (STATFILE, ">>$namestatfile");
print STATFILE "$ENV{'REMOTE_HOST'}|$ENV{'HTTP_USER_AGENT'}";
close(STATFILE);

Теперь наш скрипт будет не только считать заходы, но и по каждому посчитанному заходу записывать в файл статистики хост или IP адрес посетителя и его броузер.

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

(…продолжение следует…)