Работа с cookies в CGI-скриптах на Perl для начинающих


Прислал: Андрей Черный [ 22.12.2005 @ 13:27 ]
Раздел:: [ Статьи по Perl ]


Введение

Важными, и очень ценимыми посетителями, качествами серьезного сайта являются удобство использования и, в некотором смысле, индивидуальный подход к пользователю. Поэтому большинство крупных сайтов и порталов предоставляет пользователям такую интересную возможность, как индивидуальную настройку интерфейса "под себя".

Скажем, пользователь поисковой машины может настроить формат вывода результатов - количество результатов на странице, их вид и т.п., да и вообще вид интерфейса. Форум может запомнить дату последнего захода пользователя и при следующем заходе пометить темы, в которых есть "свежие" для него сообщения.

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

Где же лучше и рациональнее всего хранить параметры, индивидуальные для каждого пользователя? Правильно, на машине самого пользователя! :-) И для этого применяется такой механизм, как cookies.

Кроме этого, cookies широко применяется в различных веб-интерфейсах, в т.ч. требующих аутентификации пользователя и в скриптах партнерских программ - для "пометки" клиента, пришедшего на сайт в 1-й раз.

Что такое cookies?

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

Cookies - это данные в виде пар "ключ=значение", которые сервер выдает браузеру "на хранение", а затем, при следующем заходе браузера, получает от него обратно. Причем, в целях безопасности, эти данные могут быть получены в общем случае только тем сервером, который их выдал, или тем, для которого они выдавшим сервером предназначены (если настройки браузера это не запрещают). Таким образом, в cookies пользователя сервер может сохранить только то, что сам уже "знает".

В большинстве браузеров эти данные хранятся в текстовых файлах в зашифрованном виде. Хотя серверу нет никакой надобности "знать", как конкретно физически хранятся cookies у клиента.

Особенностью cookies является также то, что они доступны как для серверных скриптов, так и для клиентских (например, JavaScript).

Как работают cookies. Формат cookie.

На уровне протокола HTTP обмен куками между броузером и сервером происходит следующим образом.

При выдаче HTTP-ответа сервер может установить в браузер cookie с помощью поля заголовка ответа 'Set-Cookie'. Если требуется установить несколько cookies, то в заголовке ответа должны быть соответственно несколько полей Set-Cookie - отдельное для каждой cookie.

В дальнейшем, если эта cookie еще осталась в базе данных браузера (о времени их жизни см. ниже), при следующих запросах этого ресурса (и других ресурсов сервера, входящих в зону действия установленного cookie), браузер выдает эту cookie в поле заголовка HTTP-запроса 'Cookie'. Выдается только сама cookie (NAME=VALUE), без параметров. Если для данного ресурса действительны несколько cookies, то они выдаются в единственном поле 'Cookie' через '; ';

CGI-скриптам это поле запроса доступно через переменную среды CGI 'HTTP_COOKIE'

Полный формат устанавливаемой cookie выглядит следующим образом:

NAME=VALUE; [expires=DATE;] [path=PATH;] [domain=DOMAIN_NAME;] [secure]

Обязательной частью является собственно устанавливаемая cookie

NAME=VALUE

где NAME - имя ключа и VALUE - значение ключа.

Параметры cookie:

expires - срок годности. С помощью параметра expires сервер может указать "срок годности" (вернее, "последнюю дату годности") cookie. Если этот параметр не указан, то cookie существует в браузере до закрытия его окна. Это удобно, например, при доступе к сервисам, требующим аутентификации пользователя - в cookies записываются реквизиты доступа пользователя, пользователь "гуляет" по веб-интерфейсу с этими куками, затем закрывает окно - и реквизиты доступа удаляются с компьютера. А также - для любых других параметров, действительных только для данного сеанса - например, в файл-менеджерах удобно хранить в cookies текущий путь к файлам.

Дата и время должны быть указаны в виде:

Wdy, DD-Mon-YYYY HH:MM:SS GMT

Где Wdy - день недели и Mon - месяц (в английских трехбукенных сокращениях), DD - день месяца (два знака), YYYY - год, HH, MM, SS - часы, минуты и секунды (по 2 знака). Причем обратите внимание, что дата-время окончания действия cookie указываются "по Гринвичу".

Например,

Wed, 01-Jun-2005 23:00:00

domain - доменное имя, для которого действительна cookie. Например, domain=yourdomain.com;

По умолчанию - доменное имя выдавшего cookie сервера (сайта).

path - путь на сервере. Этот параметр устанавливает (ограничивает) область на веб-сервере, для которой действительна данная cookie. При поиске cookies для данного конкретного URL-а браузер сравнивает эту строку с началом пути на сервере. Скажем, если областью действия cookie должен быть весь сайт (от корневой папки и ниже), то path должен быть "/"; если же область действия cookies должна быть ограничена папкой cgi-bin, то path=/cgi-bin/.

Еще один момент - поскольку соответствие области действия cookie текущему запрашиваемому браузером ресурсу определяется по тому, соответствует ли начало текущего пути на сервере параметру path cookie, то, скажем, при path=/perl/ область действия cookie ограничена папкой perl на сервере, а при path=/perl cookie будет действовать также для всех папок и файлов, полный путь к которым начинается на "/perl" : скажем, для папок perl_doc, perl_scripts и файла perl.html, находящихся в корневой папке.

Если параметр path не указан, то областью действия cookie будет папка на сервере, в которой работает этот скрипт, и ниже, т.е. по умолчанию path=<папка_скрипта>

secure - указывает, что данная кука содержит конфиденциальную информацию, которая должна передаваться только в случае работы по протоколу HTTPS. При незащищенной работе по "обычному" протоколу HTTP она не передается.

Обработка принятых cookies

Рассмотрим пример: форум, в котором пользователь может настраивать количество сообщений, отображаемых на странице, и порядок их сортировки (от более ранних к более поздним или наоборот). Эти параметры сохраняются скриптом настройки вида форума в cookies quan и sort соответственно.



#Значения наших параметров по умолчанию
$param_quan=10;
$param_sort=0;

#Обрабатываем cookies
$cookie_data=$ENV{'HTTP_COOKIE'};
@c=split ('; ',$cookie_data);
foreach $itm(@c){
($c_n,$c_v)=split('=', $itm);
$COOKIE{$c_n}=$c_v;
};

#Если параметры установлены в cookie, они "переписывают"
#значения по умолчанию
if (defined ($COOKIE{'quan'})){$param_quan=$COOKIE{'quan'};};
if (defined ($COOKIE{'sort'})){$param_sort=$COOKIE{'sort'};};

Разработчикам скриптов, использующих cookies, следует иметь в виду, что клиент (браузер) в общем случае не гарантирует, что устанавливаемая сервером cookie будет принята, а если она даже была принята, то - сохранность в течение указанного сервером срока годности, так как:

  • Пользователь может отключить поддержку cookies или использовать HTTP-Firewall, блокирующий обмен куками. Также в большинстве современных броузеров есть возможность включить подтверждение пользователем каждой устанавливаемой куки. Хотя вряд ли найдутся такие люди, которые пользуются этим постоянно. :-)

  • Броузеры поддерживают ограниченное количество хранимых cookies и ограниченный их размер. Максимальное общее количество одновременно хранимых cookies - 300, для одного сайта (домена второго уровня) - 20, размер каждой не должен превышать 4 кб (если устанавливаемая cookie длиннее 4кб, она будет "урезана" до 4-х кб).

  • Пользователь может взять - да и удалить (очистить) cookies на своем компьютере.

Экспериментируем с cookies

Для того, чтобы, так сказать, "прочувствовать живьем" работу с cookies, можно применить простой CGI-скрипт, который умеет показывать переданные ему куки и устанавливать новые.

Например, такой:

#!/usr/bin/perl

$r=$ENV{'QUERY_STRING'};

#Получаем параметры (если они есть)
@var=split('&',$r);

foreach $itm(@var)
{
($qname, $qvalue)=split('=',$itm);
$qvalue=~tr/+/ /;
$qvalue=~ s/%(..)/pack("c",hex($1))/ge;
$PARAM{$qname}=$qvalue;
};

if ($PARAM{'action'} ne 'set')

{

$a=$ENV{'HTTP_COOKIE'};
print "Content-Type: text/html\n\n";
print "Ваша кука: $a";
print "<HR><FORM action=\"$ENV{'SCRIPT_NAME'}\">
<INPUT type=hidden name=action value=set>
Установить:<INPUT type=text size=60 name=c>
<INPUT type=submit>

</FORM>\n";}
else
{print "Content-Type: text/html\n";
print "Set-Cookie: $PARAM{'c'}\n\n";
print "Попытка установить куку.<HR>
<A href=\"$ENV{'SCRIPT_NAME'}\">Просмотр</A>\n";};

Этот скрипт не претендует на рациональность и художественное совершенство, т.к. был написан для себя, "на коленке", за 10 минут. Он позволяет работать с cookies на низком уровне.

При запуске без параметров скрипт выдает пользователю поле 'Cookie' HTTP-запроса (переданные ему cookies), если оно есть, и форму установки нового cookie. Набранная строчка передается браузеру "как есть" в поле 'Set-Cookie'. Затем Вы можете вернуться и просмотреть переданные cookies заново :-)

Для экспериментов с областью действия cookies также неплохо бы обзавестись еще и "JavaScript-вариантом" этого скрипта - для тех папок на сервере, в которых не работают CGI-скрипты. Вот он:


<HTML>
<BODY><B>Ваши cookies для этого URL:</B>
<SCRIPT language="JavaScript">
document.write(document.cookie);
function sc(){
window.alert ('Устанавливаем cookie:\n '+document.scform.cookie.value);
document.cookie=document.scform.cookie.value;}
</SCRIPT>

<HR>
<FORM name=scform><B>Установить cookie:</B> <INPUT type="text" size=80 name="cookie" value=""><BR>
<INPUT type="submit" onClick="sc();">

</FORM>
</BODY>
</HTML>

Задание срока действия для cookies

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

Предположим, что по условиям партнерской программы некого интернет-магазина, покупатель считается пришедшим "через партнера", если он совершил покупку в течение месяца (30-ти дней) после первого перехода с партнерского сайта на сайт интернет-магазина.

Значит, при первом посещении интернет-магазина скрипт учета партнерской программы должен установить в браузер покупателя cookie, содержащую ID партнера сроком на 1 месяц (а при совершении покупок должно проверяться наличие и значение этого cookie и соответственно производиться - или не производиться - начисления партнерам).

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

Такие расчеты проще всего делать с датой и временем, представленным не в обычном виде (часы, минуты, секунды, дни и т.д.) а с датой и временем в том виде, в котором их выдает функция time языка perl. Эта функция возвращает количество секунд, прошедших с 0 часов 0 минут 0 секунд (по Гринвичу) 1 января 1970 года до настоящего момента (понятное дело, что число это немаленькое :-) )

Для получения даты и времени конца действия cookie достаточно прибавить к этому числу количество секунд в нужном нам промежутке времени (в нашем примере это 30 дней, в которых 60*60*24*30=2592000 секунд), а затем преобразовать полученное число в "удобоваримый" для браузера формат и выдать браузеру cookie. Преобразовывать время в "человечский формат" мы будем с помощью функции gmtime, т.к. нам нужно передать броузеру конечное время не в местном значении, а в значении по Гринвичу.

Код, реализующий эту функцию, может быть таким:

#Количество секунд в 30-ти днях
$d=2592000;

#Английские трехбуквенные обозначения
#дней недели и месяцев
@weekdays=('Sun','Mon','Tue','Wed','Thu','Fri','Sat');
@months=('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec');

$t=time;
$t+=$d;

#Получаем значение конечных даты и времени в "обычном" виде
($SS,$MM,$HH,$DD,$MON,$YY,$WD)=gmtime($t);
$YY=$YY+1900;
$exp_date="$weekdays[$WD], $DD-$months[$MON]-$YY $HH:$MM:$SS GMT";

#Вставляем '0' перед числами <10, если они есть
$z='0';
$exp_date=~s/(\D)(\d{1})(\D)/$1$z$2$3/g;
print "$exp_date\n";
<P align=justify>
Полученное значение $exp_date можно передать клиенту
в устанавливаемой cookie параметром expires.

<P align=justify>
$p_id="vasya";
$cookie="partner=$p_id\; path=/\; expires=$exp_date\;";
print "Content-Type: text/html\n";
print "Set-Cookie: $cookie\n\n";
#и т. д.

Правильность работы этого фрагмента можно проверить либо перестановкой даты и времени, либо через месяц :-)

Пожелания

И в конце - мои пожелания к тем, кто решит использовать работу с cookies в своих скриптах. Они основаны на опыте работы с такими скриптами в качестве пользователя.

Негативная репутация cookies связана не только с особенностями этого механизма, сколько с неграмотным его использованием в некоторых скриптах.

Как-то я наткнулся на "интересный" сервис бесплатной веб-почты, который после первого входа пользователя на ящик запоминал в cookies пользователя имя и пароль, и при последующих обращениях к главной странице ("открытии сайта") пользователь сразу же попадал на свой эккаунт! Понятно, что это удобно, но при "таких делах" кто угодно, севший за этот комьютер, сможет читать почту этого ящика безо всякого хакерства :-)

1. Не записывайте пароли и другую конфиденциальную информацию в cookies "со сроком действия" (которые не удаляются при закрытии посетителем окна браузера), без явного желания пользователя. Использование многими форумами cookies для хранения реквизитов доступа пользователя очень удобно для последнего (не надо входить каждый раз), но надо учитывать и то, что пользователь может работать за чужим или общественным компьютером (скажем, у друга или в интернет-клубе), поэтому очень хорошим вариантом является предусматривание в форме входа "птички" типа "запомнить имя и пароль на этом компьютере" (желательно, по умолчанию не активной). Также желательна возможность удаления (обнуления) уже установленных cookies пользователем. Удобство от использования cookies не должно достигаться за счет ущерба безопасности.

2. Если Вы используете cookies для хранения имени пользователя, у последнего все-таки должна быть возможность войти под другим именем, не очищая cookies в браузере :-)

3. Если скрипт не может корректно работать при отключенных cookies в браузере, желательно предупредить об этом пользователя. Можно также проверять, включены или отключены cookies, путем установки "контрольной" cookie и последующего ее чтения, и если они отключены, выдать соответствующие инструкции.


©Андрей Черный, 2005
"CGI-Scripts.info: все о CGI-скриптах"
//www.cgi-scripts.info/