Фальстарт

HTTP Cookie

Константин Александров, 16 декабря 2013
Загрузить kuki.js (~1 Кб)
  1. Решаемые задачи
  2. Cookie в протоколе HTTP
  3. Получение и установка cookies на сервере
    1. Работа с cookie на Perl
    2. Работа с cookie на PHP
  4. Клиентский JavaScript API: document.cookie
    1. Библиотека для работы с cookies на JavaScript

Решаемые задачи

Любая технология — это инструмент для решения конкретных задач с учётом практических ограничений. Эти задачи и ограничения полностью определяют технические детали реализации, потому вопрос «зачем нужно» правильнее рассмотреть раньше вопроса «как работает».

Обычно среди отдаваемых HTTP-сервером ресурсов имеются статические страницы, графика, скрипты — они остаются одинаковыми для любых запросов любых пользователей. Также на сайте обычно имеются динамические ресурсы, которые могут отображаться по-разному в зависимости от состояния. Например, зарегистрированному пользователю выводится форма комментирования страницы, а администратору — ещё и кнопки удаления комментариев. Значит сервер должен помнить пользователя с момента входа в систему и уметь отличать его запросы от остальных. В качестве решения HTTP Cookie предоставляет простой механизм «пометки» пользователя: сервер передает метку web-браузеру, который включает её в последующие запросы.

HTTP Cookie — это механизм управления состоянием в транспортном протоколе HTTP [RFC 6265]. Он строится на передаче web-сервером именованных значений с дополнительными атрибутами клиенту и на возврате их на сервер при выполнении последующих запросов. Передаваемые имена со значениями и атрибутами называются cookies. На клиент они приходят в HTTP-ответе сервера в заголовках Set-Cookie по числу передаваемых cookies, а на сервер возвращаются в запросе, упакованные в заголовок Cookie.

  1. # Заголовки очередного запроса к серверу
  2. # Содержат ранее полученные от сервера cookies
  3. GET / HTTP/1.1
  4. User-Agent: Opera/9.80 (Windows NT 6.1) Version/12.16
  5. Host: falstart.com
  6. Accept: text/html, */*;q=0.1
  7. Accept-Language: ru,ru-RU;q=0.9,en;q=0.8
  8. Accept-Encoding: gzip, deflate
  9. Cookie: sid=UDQLMHGRODVQ; name=user
  10. # Заголовки ответа сервера клиенту
  11. HTTP/1.1 200 OK
  12. Server: nginx/0.7.61
  13. Date: Thu, 03 Oct 2013 16:21:24 GMT
  14. Content-Type: text/html; charset=utf-8
  15. Transfer-Encoding: chunked
  16. Set-Cookie: sid=UDQLMHGRODVQ; path=/; expires=Sat, 05-Oct-2013 16:21:24 GMT
  17. Set-Cookie: name=user; path=/; expires=Sat, 05-Oct-2013 16:21:24 GMT

Атрибуты устанавливаются сервером для управления обработкой cookies на клиенте. Все они являются опциональными и имеют значения по умолчанию:

На клиенте, в отличие от сервера, 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-объектов (массивов и хешей) помимо скалярных значений.

  1. #!/usr/bin/perl
  2. use strict;
  3. use CGI qw(cookie); # Импорт функции cookie
  4. use CGI::Fast;
  5. while (my $q = CGI::Fast->new) {
  6. # Сырое значение HTTP-заголовка Cookie
  7. # (не предназначено для ручного разбора)
  8. my $cookie_str = $ENV{HTTP_COOKIE};
  9. ## Получение декодированных скаляров и объектов из запроса
  10. # Вызов cookie как метода в ООП-стиле
  11. my $rus = $q->cookie('rus');
  12. my $counter = $q->cookie('counter') || 0;
  13. # Вызов cookie как функции в процедурном стиле
  14. my @array = cookie('array');
  15. @array = ('a', 'b&c') unless @array;
  16. # Подготовка значений для передачи клиенту
  17. my $out_counter = $q->cookie(
  18. -name => 'counter',
  19. -value => $counter + 1,
  20. -expires => '+3M', # Хранить 3 месяца с момента установки
  21. -httponly => 1
  22. );
  23. my $out_rus = $q->cookie('rus' => 'Ура!');
  24. my $out_array = cookie(
  25. -name => 'array',
  26. -value => [ @array, $counter ]
  27. );
  28. ## Включение cookies в заголовки ответа
  29. print $q->header( -cookie => [ $out_counter, $out_rus, $out_array ] );
  30. print "Cookie HTTP header: $cookie_str\n";
  31. print "rus: $rus, counter: $counter, array: @array\n";
  32. }

На пятый запуск скрипт вернёт ответ:

  1. # Фрагмент HTTP-заголовков
  2. Set-Cookie: counter=5; path=/; expires=Wed, 05-Mar-2014 17:39:10 GMT; HttpOnly
  3. Set-Cookie: rus=%D0%A3%D1%80%D0%B0%21; path=/
  4. Set-Cookie: array=a&b%26c&0&1&2&3&4; path=/
  5. # Тело ответа
  6. Cookie HTTP header: counter=4; rus=%D0%A3%D1%80%D0%B0%21; array=a&b%26c&0&1&2&3;
  7. 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 этот бред был удалён, проверку лучше оставить для совместимости.

  1. <?php
  2. $cookie_str = $_SERVER['HTTP_COOKIE'];
  3. $rus = $_COOKIE['rus'];
  4. $counter = $_COOKIE['counter'];
  5. if (!$counter) $counter = 0;
  6. $array = $_COOKIE['array'];
  7. if (!$array) {
  8. $array = array('a', 'b&c');
  9. } else {
  10. if (get_magic_quotes_gpc())
  11. $array = stripslashes($array);
  12. $array = unserialize($array);
  13. }
  14. setcookie('counter', $counter + 1,
  15. time() + 3600*24*30*3, NULL, NULL, false, true);
  16. setcookie('rus', 'Ура!');
  17. $array[] = $counter;
  18. setcookie('array', serialize($array));
  19. echo "Cookie HTTP header: $cookie_str\n";
  20. echo "rus: $rus, counter: $counter, array: ".
  21. join(' ', $array);
  22. ?>

Вывод скрипта аналогичен приведенному ранее выводу Perl-скрипта за исключением различий в виде сериализованного массива.

Клиентский JavaScript API: document.cookie

Спецификация DOM Level 2 HTML определяет атрибут document.cookie для доступа к cookies на стороне клиента. Механизм работы с ним очень простой и похож на работу с HTTP-заголовками на стороне сервера: для получения заголовка Cookie нужно прочитать значение атрибута, а для установки заголовка Set-Cookie присвоить его значение атрибуту.

  1. // Установка значений
  2. document.cookie = 'aaa=111;';
  3. document.cookie = 'bbb=222;';
  4. // Выведет aaa=111; bbb=222
  5. console.log(document.cookie);
  6. // Удаление значений
  7. document.cookie = 'aaa=; max-age=0;';
  8. document.cookie = 'bbb=; max-age=0;';
  9. /* Не устанавливайте cookies с одинаковыми именами,
  10. но разными атрибутами domain и path */
  11. document.cookie = 'ccc=ccc; domain=falstart.com;';
  12. document.cookie = 'ccc=bbb; domain=subdomain.falstart.com;';
  13. document.cookie = 'ccc=; max-age=0;';
  14. /* Вывод различается в зависимости от браузера:
  15. может вывести ccc=bbb; ccc=ccc или ccc=ccc */
  16. console.log(document.cookie);

Этот механизм является низкоуровневым, как и прямая работа с HTTP-заголовками на стороне сервера. Использовать его напрямую не рекомендуется по тем же причинам — из-за трудоёмкости разбора и формирования строк с учетом граничных случаев: форматирование даты и времени, экранирование спецсимволов, представление символов национальных алфавитов. Рекомендуется обернуть работу с атрибутом в библиотеку, предоставляющую разработчику более удобный интерфейс. Один из вариантов реализации рассмотрен далее.

Библиотека для работы с cookies на JavaScript

Прикреплённая к статье библиотека kuki.js предоставляет альтернативное свойство document.kuki для оперирования с cookies в объектно-ориентированном стиле.

  1. var kuki = document.kuki;
  2. // Установка значений
  3. kuki.set({ name: 'aaa', value : '111' });
  4. kuki.set({ name: 'bbb', value : '111',
  5. expires : new Date(3147, 1, 7)});
  6. kuki.set({ name : 'ccc', value : 'ccc',
  7. domain : 'falstart.com' });
  8. kuki.set({ name : 'ccc', value : 'bbb',
  9. domain : 'subdomain.falstart.com' });
  10. // Чтение
  11. console.log(kuki.get('aaa')); // 111
  12. console.log(kuki.has('bbb')); // true
  13. var ccc_values = kuki.gets('ccc');
  14. console.log('len: ' + ccc_values.length + ', values: ' +
  15. ccc_values); // len: 2, values: bbb,ccc
  16. // Удаление cookies
  17. kuki.delete({ name : 'ccc', domain : 'falstart.com' });
  18. kuki.delete({ name : 'ccc', domain : 'subdomain.falstart.com' });

  1. В общем случае агент пользователя (User Agent) не всегда является web-браузером, момент завершения сессии определяется его реализацией.
  2. Агент пользователя может запрещать установку cookie для доменов верхнего уровня (ru, org.ru, msk.ru и т.д.) из соображений безопасности.
  3. Прочитанное значение, в отличие от HTTP-заголовка Cookie на сервере, не будет содержать cookies, установленных с атрибутом httponly.