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.