HTTP Cookie
kuki.js
(~1 Кб)- Решаемые задачи
- Cookie в протоколе HTTP
- Получение и установка cookies на сервере
- Клиентский JavaScript API:
document.cookie
Решаемые задачи
Любая технология решает конкретные задачи с учётом имеющихся ограничений. Эти задачи и ограничения полностью определяют её облик, потому вопрос «зачем нужно» правильнее рассмотреть раньше вопроса «как работает».
Обычно среди отдаваемых HTTP-сервером ресурсов имеются статические страницы, графика, скрипты — они остаются одинаковыми для любых запросов любых пользователей. Также на сайте обычно имеются динамические ресурсы, которые могут отображаться по-разному в зависимости от состояния. Например, зарегистрированному пользователю выводится форма комментирования страницы, а администратору — ещё и кнопки удаления комментариев. Значит сервер должен помнить пользователя с момента входа в систему и уметь отличать его запросы от остальных. В качестве решения HTTP Cookie предоставляет простой механизм «пометки» пользователя: сервер передает метку web-браузеру, который включает её в последующие запросы.
Cookie в протоколе HTTP
HTTP Cookie — это механизм управления состоянием в транспортном протоколе HTTP [RFC 6265]. Он строится на передаче web-сервером именованных значений с дополнительными атрибутами клиенту и на возврате их на сервер во всех последующих запросах. Передаваемые имена со значениями и атрибутами называются cookies. На клиент они приходят в HTTP-ответе сервера в заголовках Set-Cookie
по числу передаваемых cookies, а на сервер возвращаются в запросе, упакованные в один заголовок Cookie
.
- # Заголовки очередного запроса к серверу
- # Содержат ранее полученные от сервера cookies
- GET / HTTP/1.1
- User-Agent: Opera/9.80 (Windows NT 6.1) Version/12.16
- Host: falstart.com
- Accept: text/html, */*;q=0.1
- Accept-Language: ru,ru-RU;q=0.9,en;q=0.8
- Accept-Encoding: gzip, deflate
- Cookie: sid=UDQLMHGRODVQ; name=user
- # Заголовки ответа сервера клиенту
- HTTP/1.1 200 OK
- Server: nginx/0.7.61
- Date: Thu, 03 Oct 2013 16:21:24 GMT
- Content-Type: text/html; charset=utf-8
- Transfer-Encoding: chunked
- Set-Cookie: sid=UDQLMHGRODVQ; path=/; expires=Sat, 05-Oct-2013 16:21:24 GMT
- Set-Cookie: name=user; path=/; expires=Sat, 05-Oct-2013 16:21:24 GMT
Атрибуты устанавливаются сервером для управления обработкой cookies на клиенте. Все они опциональные и имеют значения по умолчанию:
expires
— дата и время истечения срока действия. По умолчанию cookie действует до завершения сессии web-браузера.max-age
— время жизни cookie в секундах. Если указан, перекрывает значениеexpires
.domain
— ограничивает область действия cookie указанным доменом и всеми его поддоменами. Например, если указан доменabc.ru
, то cookie будет передаваться в запросах к хостамabc.ru
иforum.abc.ru
, а к хостуcde.ru
не будет. Хост может устанавливать cookie только для своего и родительских доменов. Например, хостforum.abc.ru
может установить cookie для доменовforum.abc.ru
иabc.ru
, но не может дляchat.abc.ru
иcde.ru
. Если атрибут не задан, значение cookie возвращается только хосту-источнику, ответом которого оно было установлено.path
— путь, ограничивающий область применения cookie. Состоит из компонентов-директорий, разделённых символом/
. Cookie включается в запросы, URI которых начинается с соответствующих компонентов пути. Если атрибут не задан, путь берётся из URI запроса.secure
— флаг, установленный вSecure
, разрешает передачу cookie только по безопасному каналу. В частности, при установленном флаге cookie не будет передаваться по протоколу HTTP, но будет по HTTPS. По умолчанию флаг не установлен.httponly
— флаг, установленный вHttpOnly
, ограничивает область использования cookie исключительно рамками протокола HTTP. При установленном флаге в частности будет невозможен доступ к cookie из JavaScript через API браузера. По умолчанию флаг не установлен.
На клиенте, в отличие от сервера, cookies идентифицируется не только именем, его дополняют значения domain
и path
: браузер вполне может хранить разные cookies с одинаковыми именами и передавать их серверу, даже одновременно. Будьте внимательны: повторная установка cookie с новым domain
или path
приведёт к созданию дубля, не меняйте эти атрибуты в течение времени жизни cookie.
Повторная установка cookie с таким же идентификатором приводит к замещению старого значения вместе со всеми атрибутами. Специальной процедуры для удаления cookie нет, для этого достаточно установить новое значение с expires
в прошлом или с нулевым max-age
.
При работе с cookies необходимо учитывать ряд ограничений. Строгих ограничений на их размер и число нет, RFC 6265 устанавливает только требования к web-браузеру: он должен хранить от 50 значений cookies на домен с размером каждого вместе с атрибутами от 4096 байт. Браузер вполне может хранить и больший объём значений, но безопаснее держаться в рамках гарантированных лимитов. При создании приложения следует учитывать сценарии неожиданного удаления, любой модификации или добавления новых значений cookies: пришедшие в cookies данные нисколько не безопаснее данных из web-форм.
Получение и установка cookies на сервере
Cookies доставляются в обычных HTTP-заголовках, поэтому в простейшем случае для работы с ними можно использовать соответствующий API языка программирования. Но поступать так следует только для изучения механизма или при отсутствии более подходящих средств. В остальных случаях ручной разбор и формирование заголовков проигрывают по качеству специализированным библиотечным реализациям.
По возможности используйте специализированные библиотеки, большинство из них реализуют логику кодирования и декодирования строк cookies в соответствии с рекомендациями RFC 6265 и обработку соответствующих HTTP-заголовков. Далее рассмотрим инструменты по работе с cookies, предоставляемые распространёнными языками программирования.
Так как cookies приходят на клиент в HTTP-заголовках, вполне естественно, что их установка должна выполняться раньше начала передачи тела ответа. Это ограничение справедливо для любых языков программирования.
Работа с cookie на Perl
В стандартную поставку Perl входит модуль CGI::Cookie
. Им можно пользоваться как напрямую, так и с помощью функции-обёртки cookie
из модуля CGI
, поддерживающей процедурный и объектно-ориентированный интерфейсы. В зависимости от аргументов функция разбирает входящие cookies или формирует исходящие. Модуль скрывает детали кодирования заголовков и предоставляет разработчику простой интерфейс. Полезная особенность — поддержка передачи в cookies Perl-объектов (массивов и хешей) помимо скалярных значений.
- #!/usr/bin/perl
- use strict;
- use CGI qw(cookie); # Импорт функции cookie
- use CGI::Fast;
- while (my $q = CGI::Fast->new) {
- # Сырое значение HTTP-заголовка Cookie
- # (не предназначено для ручного разбора)
- my $cookie_str = $ENV{HTTP_COOKIE};
- ## Получение декодированных скаляров и объектов из запроса
- # Вызов cookie как метода в ООП-стиле
- my $rus = $q->cookie('rus');
- my $counter = $q->cookie('counter') || 0;
- # Вызов cookie как функции в процедурном стиле
- my @array = cookie('array');
- @array = ('a', 'b&c') unless @array;
- # Подготовка значений для передачи клиенту
- my $out_counter = $q->cookie(
- -name => 'counter',
- -value => $counter + 1,
- -expires => '+3M', # Хранить 3 месяца с момента установки
- -httponly => 1
- );
- my $out_rus = $q->cookie('rus' => 'Ура!');
- my $out_array = cookie(
- -name => 'array',
- -value => [ @array, $counter ]
- );
- ## Включение cookies в заголовки ответа
- print $q->header( -cookie => [ $out_counter, $out_rus, $out_array ] );
- print "Cookie HTTP header: $cookie_str\n";
- print "rus: $rus, counter: $counter, array: @array\n";
- }
На пятый запуск скрипт вернёт ответ:
- # Фрагмент HTTP-заголовков
- Set-Cookie: counter=5; path=/; expires=Wed, 05-Mar-2014 17:39:10 GMT; HttpOnly
- Set-Cookie: rus=%D0%A3%D1%80%D0%B0%21; path=/
- Set-Cookie: array=a&b%26c&0&1&2&3&4; path=/
- # Тело ответа
- Cookie HTTP header: counter=4; rus=%D0%A3%D1%80%D0%B0%21; array=a&b%26c&0&1&2&3;
- rus: Ура!, counter: 4, array: a b&c 0 1 2 3
За полным описанием функциональности модуля обращайтесь документации и исходному коду.
Работа с cookie на PHP
PHP также предоставляет стандартный интерфейс для работы с cookies, хотя и менее удобный, чем в Perl. Для чтения входящих cookies по имени используется ассоциативный массив $_COOKIE
. Для установки — функция setcookie
с аргументами: имя
, значение
, атрибуты expires
, path
, domain
, secure
, httponly
. Все аргументы кроме имени являются опциональными.
Обратите внимание, что PHP некоторых версий по умолчанию экранирует специальные символы (в частности кавычки) во входных данных добавлением перед ними \
. Это сделано для защиты плохо написанного кода, формирующего из непроверенных данных SQL-запросы, от SQL-инъекций. Получить признак активности режима можно посредством вызова функции get_magic_quotes_gpc
, а выполнить обратное преобразование — с помощью stripslashes
. Хотя с версии PHP 5.4.0 этот бред был удалён, проверку лучше оставить для совместимости.
- <?php
- $cookie_str = $_SERVER['HTTP_COOKIE'];
- $rus = $_COOKIE['rus'];
- $counter = $_COOKIE['counter'];
- if (!$counter) $counter = 0;
- $array = $_COOKIE['array'];
- if (!$array) {
- $array = array('a', 'b&c');
- } else {
- if (get_magic_quotes_gpc())
- $array = stripslashes($array);
- $array = unserialize($array);
- }
- setcookie('counter', $counter + 1,
- time() + 3600*24*30*3, NULL, NULL, false, true);
- setcookie('rus', 'Ура!');
- $array[] = $counter;
- setcookie('array', serialize($array));
- echo "Cookie HTTP header: $cookie_str\n";
- echo "rus: $rus, counter: $counter, array: ".
- join(' ', $array);
- ?>
Вывод скрипта аналогичен приведенному ранее выводу Perl-скрипта за исключением различий в виде сериализованного массива.
Клиентский JavaScript API: document.cookie
Спецификация DOM Level 2 HTML определяет атрибут document.cookie
для доступа к cookies на стороне клиента. Механизм работы с ним очень простой и похож на работу с HTTP-заголовками на стороне сервера: для получения заголовка Cookie
нужно прочитать значение атрибута, а для установки заголовка Set-Cookie
присвоить его значение атрибуту.
- // Установка значений
- document.cookie = 'aaa=111;';
- document.cookie = 'bbb=222;';
- // Выведет aaa=111; bbb=222
- console.log(document.cookie);
- // Удаление значений
- document.cookie = 'aaa=; max-age=0;';
- document.cookie = 'bbb=; max-age=0;';
- /* Не устанавливайте cookies с одинаковыми именами,
- но разными атрибутами domain и path */
- document.cookie = 'ccc=ccc; domain=falstart.com;';
- document.cookie = 'ccc=bbb; domain=subdomain.falstart.com;';
- document.cookie = 'ccc=; max-age=0;';
- /* Вывод различается в зависимости от браузера:
- может вывести ccc=bbb; ccc=ccc или ccc=ccc */
- console.log(document.cookie);
Этот механизм является низкоуровневым, как и прямая работа с HTTP-заголовками на стороне сервера. Использовать его напрямую не рекомендуется по тем же причинам — из-за трудоёмкости разбора и формирования строк с учетом граничных случаев: форматирование даты и времени, экранирование спецсимволов, представление символов национальных алфавитов. Рекомендуется обернуть работу с атрибутом в библиотеку, предоставляющую разработчику более удобный интерфейс. Один из вариантов реализации рассмотрен далее.
Библиотека для работы с cookies на JavaScript
Прикреплённая к статье библиотека kuki.js
предоставляет альтернативное свойство document.kuki
для оперирования с cookies в объектно-ориентированном стиле.
- var kuki = document.kuki;
- // Установка значений
- kuki.set({ name: 'aaa', value : '111' });
- kuki.set({ name: 'bbb', value : '111',
- expires : new Date(3147, 1, 7)});
- kuki.set({ name : 'ccc', value : 'ccc',
- domain : 'falstart.com' });
- kuki.set({ name : 'ccc', value : 'bbb',
- domain : 'subdomain.falstart.com' });
- // Чтение
- console.log(kuki.get('aaa')); // 111
- console.log(kuki.has('bbb')); // true
- var ccc_values = kuki.gets('ccc');
- console.log('len: ' + ccc_values.length + ', values: ' +
- ccc_values); // len: 2, values: bbb,ccc
- // Удаление cookies
- kuki.delete({ name : 'ccc', domain : 'falstart.com' });
- kuki.delete({ name : 'ccc', domain : 'subdomain.falstart.com' });
- В общем случае агент пользователя (User Agent) не всегда является web-браузером, момент завершения сессии определяется его реализацией.
- Агент пользователя может запрещать установку cookie для доменов верхнего уровня (ru, org.ru, msk.ru и т.д.) из соображений безопасности.
- Прочитанное значение, в отличие от HTTP-заголовка
Cookie
на сервере, не будет содержать cookies, установленных с атрибутомhttponly
.