Por muito tempo eu abstrai o conceito de cookies e sessões, e nunca cheguei a prestar muita atenção no seu funcionamento. Recentemente, trabalhando com uma infra mais preocupada com a segurança, disponibilidade e performance, tive a oportunidade de relembrar e me aprofundar em alguns conceitos e práticas.
O que já sabia é que os cookies são “persistências temporárias” feitas no lado do usuário, e sessões são persistências dependentes de cookies, mas realizadas no lado do servidor.
A minha felicidade é que até aí, nada mudou :)
O protocolo HTTP é stateless, ou seja, ele não mantém um estado/conexão. Toda a interação que o seu cliente fizer com um servidor web acarretará em uma nova requisição e resposta.
As requisições são independentes e possuem um tempo de vida (conexão, envio de mensagem, resposta, encerramento da conexão). O servidor web não é capaz de identificar se duas requisições vieram de um mesmo navegador, e o mesmo não faz nenhum gerenciamento em memória para que mensagens sejam compartilhadas entre requisições.
É para suprir esta necessidade que entram os cookies e sessões.
Através de cookies o servidor web é capaz de trocar informações de estado com o navegador do usuário. Desse modo, somos capazes de adicionar produtos a um carrinho de compras, sem perder estas informações ao mudar de página, sair do website ou até mesmo fechar o navegador.
Tecnicamente falando, um cookie é uma pequena quantidade de informação persistida temporariamente pelo navegador. Os navegadores normalmente limitam o tamanho dos cookies em até 4KB, e apagam cookies com a data de “validade vencida”.
Para entender como essa troca de informação é feita, vamos criar um cookie com o PHP:
<?php
// cookies.php
if (isset($_COOKIE['cookie_teste'])) {
echo 'Você JÁ passou por aqui!';
} else {
echo 'Você NUNCA passou por aqui.';
setcookie('cookie_teste', 'Algum valor...', time() + 3600);
}
?>
O código acima verifica se o cookie atendendo pelo identificador
cookie_teste
já existe, caso não exista, cria um cookie com
identificador cookie_teste
, valor Algum valor...
e com 1
hora de vida (a hora atual mais 3600 segundos).
Quando visitamos pela primeira vez o cookies.php
, temos a seguinte
resposta:
$ curl -I localhost/cookies.php
HTTP/1.1 200 OK
Date: Wed, 04 Apr 2012 00:35:33 GMT
Server: Apache/2.2.21 (Unix) mod_ssl/2.2.21 OpenSSL/0.9.8r DAV/2 PHP/5.3.8
X-Powered-By: PHP/5.3.8
Set-Cookie: cookie_teste=Algum+valor...; expires=Wed, 04-Apr-2012 01:35:33 GMT
Content-Type: text/html
Através da função setcookie
do PHP, estamos enviando um item
chamado Set-Cookie
no cabeçalho HTTP da resposta. É através deste
que o navegador entende que deve armazenar o valor Algum valor…
,
atendendo pelo identificador cookie_teste
, e que esta informação
expira em 1 hora (verifique a data da requisição e a data de validade do
cookie).
Na próxima vez que o navegador acessar esta URL, ele verificará se
possui algum cookie para aquele domínio e path, caso exista, ele
passa as informações do cookie no cabeçalho da requisição. Desse modo,
a nossa aplicação é capaz de perceber a existência de um cookie (no
caso do PHP, através do array global $_COOKIE
).
Abaixo, um exemplo de requisição utilizando o Google Chrome:
Se excluirmos os cookies, ou o tempo de expiração for atingido, o navegador deixa de anexar esta informação ao cabeçalho da requisição.
As sessões têm um princípio similar aos cookies, só que o armazenamento do estado é feito pelo servidor web, e não pelo navegador.
Por exemplo, quando construímos uma aplicação que necessita de autenticação, no momento em que o usuário efetuar o login, podemos até permitir que algumas informações sejam armazenadas em um cookie, mas dados mais “sensíveis”, como usuário e e-mail, são mais interessantes de serem guardadas em sessões. Isto, pois não é seguro que esse tipo de informação fique “viajando” pela web.
Mas se o HTTP é stateless, e o servidor web não tem como identificar que a requisição anterior veio do meu browser, como é que ele sabe que as informações que eu guardei em sessão são de fato minhas? Simples… através de cookies!
Quando iniciamos uma sessão, é enviado um cookie para o navegador, com um valor único que corresponde a sessão aberta no servidor web. Vamos ilustrar através do exemplo abaixo:
<?php
// sessions.php
session_start();
if (isset($_SESSION['usuario'])) {
echo "Bem vindo {$_SESSION['usuario']}!";
} else {
echo 'Você NUNCA passou por aqui.';
$_SESSION['usuario'] = 'João';
}
?>
O código acima inicia uma sessão através do método session_start
.
Na primeira visita, será criado um índice usuario
com o valor
João
. A resposta da nossa requisição será a seguinte:
$ curl -I localhost/sessions.php
HTTP/1.1 200 OK
Date: Wed, 04 Apr 2012 01:51:57 GMT
Server: Apache/2.2.21 (Unix) mod_ssl/2.2.21 OpenSSL/0.9.8r DAV/2 PHP/5.3.8
X-Powered-By: PHP/5.3.8
Set-Cookie: PHPSESSID=4h91dkp7pcp8184nil8rt9ok13; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Content-Type: text/html
Por partes:
Set-Cookie
foi retornado com um identificador
(PHPSESSID
) e um valor que corresponde a sessão aberta no
servidor (4h91dkp7pcp8184nil8rt9ok13
). O complemento path
está “dizendo” ao navegador que aquele cookie tem validade por
todo o domínio (ou seja, valerá inclusive para outros arquivos PHP
em outras subpastas). Quando não é informada a data de expiração, o
navegador manterá o cookie até o momento em que ele for fechado.Expires
, Cache-Control
e Pragma
) à nossa
resposta. Em resumo, o servidor está dizendo ao navegador para que
não armazene esta página em cache. Estes valores podem ser
alterados em tempo de desenvolvimento, ou através do php.ini.usuario
com o valor João
. Estas informações estão
disponíveis somente no lado do servidor.Quando visitarmos o sessions.php
novamente, o navegador informará ao
servidor que ele possui um cookie chamado PHPSESSID
. A partir daí
o PHP pega o valor deste cookie, recupera a sessão da memória (ou de
um banco de dados, ou arquivos em disco) e atribui este valor ao array
global $_SESSION
.
O nome do cookie varia de linguagem para linguagem e até mesmo de
framework para framework. Por exemplo, no Django ele é chamado
por padrão de sessionid
, no CodeIgniter é chamado de
ci_session
.
Gostei muito de me aprofundar um pouquinho mais neste assunto, e gostei mais ainda de poder traduzir este aprendizado através deste post.
É claro que há alguns cuidados com segurança quando o assunto é cookies e sessões, bem como considerações em relação ao uso de cache. Pretendo falar mais sobre esses temas em posts vindouros.
Até a próxima…