O HTTP (HyperText Transfer Protocol) foi projetado para oferecer suporte a um modelo de solicitação-resposta sem estado para transferência de dados de um servidor para um cliente. Sua primeira versão, 1.0, suportava uma proporção de solicitação para conexão puramente de 1:1 (ou seja, um par solicitação-resposta era suportado por conexão).
A versão 1.1 expandiu essa proporção para N:1, ou seja, muitas solicitações por conexão. Isso foi feito para lidar com a crescente complexidade das páginas da web, incluindo os muitos objetos e elementos que precisam ser transferidos do servidor para o cliente.
Com a adoção do 2.0, o HTTP continuou a oferecer suporte a um modelo de muitas solicitações por conexão. Suas mudanças mais radicais envolvem a troca de cabeçalhos e uma mudança de transferência baseada em texto para binária.
Em algum momento, o HTTP se tornou mais do que um simples mecanismo para transferir texto e imagens de um servidor para um cliente; ele se tornou uma plataforma para aplicativos. A onipresença do navegador, a natureza multiplataforma e a facilidade com que os aplicativos podem ser implantados sem o alto custo de suporte a vários sistemas operacionais e ambientes certamente eram atraentes. Infelizmente, o HTTP não foi projetado para ser um protocolo de transporte de aplicativos. Ele foi projetado para transferir documentos. Mesmo os usos modernos do HTTP, como o de APIs, pressupõem uma carga útil semelhante a um documento. Um bom exemplo disso é o JSON, um formato de dados de par chave-valor transferido como texto. Embora documentos e protocolos de aplicação sejam geralmente baseados em texto, a semelhança termina aí.
Os aplicativos tradicionais exigem alguma maneira de manter seu estado, enquanto os documentos não. Os aplicativos são criados com base em fluxos e processos lógicos, os quais exigem que o aplicativo saiba onde o usuário está no momento, e isso requer estado. Apesar da natureza inerentemente sem estado do HTTP, ele se tornou o protocolo de transporte de aplicativos de fato da web. No que é certamente um dos hacks mais amplamente aceitos e úteis da história técnica, o HTTP recebeu os meios pelos quais o estado poderia ser rastreado durante o uso de um aplicativo. É nesse "hack" que as sessões e os cookies entram em ação.
Sessões são a maneira pela qual servidores web e de aplicativos mantêm o estado. Esses blocos simples de memória são associados a cada conexão TCP feita a um servidor web ou de aplicativo e servem como armazenamento na memória para informações em aplicativos baseados em HTTP.
Quando um usuário se conecta a um servidor pela primeira vez, uma sessão é criada e associada a essa conexão. Os desenvolvedores então usam essa sessão como um local para armazenar bits de dados relevantes para o aplicativo. Esses dados podem variar de informações importantes, como o ID do cliente, a dados menos importantes, como a forma como você gostaria de ver a página inicial do site exibida.
O melhor exemplo de utilidade de sessão são os carrinhos de compras, porque quase todos nós já fizemos compras on-line em algum momento. Os itens em um carrinho de compras permanecem ao longo de uma "sessão" porque cada item no seu carrinho de compras é representado de alguma forma na sessão no servidor. Outro bom exemplo são os aplicativos de configuração ou personalização de produtos no estilo assistente. Esses "mini" aplicativos permitem que você navegue por um conjunto de opções e as selecione; no final, você geralmente fica chocado com o custo estimado de todos os recursos que você adicionou. Conforme você clica em cada "tela de opções", as outras opções escolhidas são armazenadas na sessão para que possam ser facilmente recuperadas, adicionadas ou excluídas.
Os aplicativos modernos são projetados para serem independentes de estado, mas suas arquiteturas podem não estar em conformidade com esse princípio. Os métodos modernos de escala geralmente dependem de padrões arquitetônicos como fragmentação, que requer solicitações de roteamento com base em alguns dados indexáveis, como nome de usuário ou número de conta. Isso requer um tipo de abordagem com estado, em que os dados indexáveis são transportados junto com cada solicitação para garantir o roteamento e o comportamento adequados do aplicativo. Nesse sentido, aplicativos e APIs modernos “sem estado” geralmente exigem cuidados e alimentação semelhantes aos de seus predecessores com estado.
O problema é que as sessões são vinculadas a conexões, e as conexões ficam inativas por muito tempo. Além disso, a definição de "muito longo" para conexões é bem diferente de quando aplicada a sessões. A configuração padrão para alguns servidores web, por exemplo, é fechar uma conexão quando ela estiver ociosa (ou seja, sem mais solicitações feitas) por 15 segundos. Por outro lado, uma sessão nesses servidores web, por padrão, permanecerá na memória por 300 segundos, ou 5 minutos. Obviamente, os dois estão em desacordo, porque, quando o tempo de conexão expira, de que serve a sessão se ela estiver associada à conexão?
Você pode pensar que pode simplesmente aumentar o valor do tempo limite de conexão para corresponder à sessão e resolver essa disparidade. Aumentar o tempo limite significa que você provavelmente gastará memória para manter uma conexão que pode ou não ser usada. Isso pode diminuir a capacidade total de usuários simultâneos do seu servidor e, por fim, prejudicar seu desempenho. E você certamente não quer diminuir o tempo limite da sessão para corresponder ao tempo limite da conexão, porque a maioria das pessoas leva mais de cinco minutos para pesquisar ou personalizar seu novo brinquedo.
Nota importante: Embora o HTTP/2 resolva alguns desses problemas, ele introduz outros relacionados à manutenção do estado. Embora não seja exigido pela especificação, os principais navegadores só permitem HTTP/2 sobre TLS/SSL. Ambos os protocolos exigem persistência para evitar o custo de desempenho da renegociação, o que, por sua vez, exige conhecimento de sessão, também conhecido como comportamento com estado.
Dessa forma, o que você acaba tendo são sessões que permanecem como memória no servidor mesmo depois que suas conexões associadas foram encerradas devido à inatividade, consumindo recursos valiosos e potencialmente irritando usuários para os quais seu aplicativo simplesmente não funciona.
Felizmente, esse problema é resolvido através do uso de cookies.
Cookies são pedaços de dados armazenados no cliente pelo navegador. Os cookies podem armazenar, e armazenam, todos os tipos de informações interessantes sobre você, seus aplicativos e os sites que você visita. O termo "cookie" é derivado de "magic cookie", um conceito bem conhecido na computação UNIX que inspirou tanto a ideia quanto o nome. Os cookies são criados e compartilhados entre o navegador e o servidor por meio do cabeçalho HTTP, Cookie.
Biscoito: JSESSIONID=9597856473431 Controle de cache: sem cache Host: 127.0.0.2:8080 Conexão: Mantenha-se vivo
O navegador sabe automaticamente que deve armazenar o cookie no cabeçalho HTTP em um arquivo no seu computador e rastreia os cookies por domínio. Os cookies para qualquer domínio são sempre passados ao servidor pelo navegador nos cabeçalhos HTTP, para que os desenvolvedores de aplicativos da web possam recuperar esses valores simplesmente solicitando-os no lado do servidor do aplicativo.
O problema da duração da sessão/conexão é resolvido através de um cookie. Quase todos os aplicativos web modernos geram um "ID de sessão" e o transmitem como um cookie. Isso permite que o aplicativo encontre a sessão no servidor mesmo depois que a conexão da qual a sessão foi criada for fechada. Por meio dessa troca de IDs de sessão, o estado é mantido mesmo para um protocolo sem estado como o HTTP. Mas o que acontece quando o uso de um aplicativo web supera a capacidade de um único servidor web ou de aplicativo? Normalmente, um balanceador de carga ou, nas arquiteturas atuais, um Application Delivery Controller (ADC), é introduzido para dimensionar o aplicativo de modo que todos os usuários fiquem satisfeitos com a disponibilidade e o desempenho.
Em aplicações modernas, cookies ainda podem ser usados, mas outros cabeçalhos HTTP se tornam essenciais. As chaves de API para autenticação e autorização geralmente são transportadas por meio de um cabeçalho HTTP, bem como outros cabeçalhos personalizados que transportam dados necessários para o roteamento e o dimensionamento adequado dos serviços de back-end. Usar o tradicional “cookie” para transportar esses dados ou algum outro cabeçalho HTTP é menos importante do que reconhecer sua importância para a arquitetura geral.
O problema com isso é que os algoritmos de balanceamento de carga geralmente se preocupam apenas com a distribuição de solicitações entre servidores. As técnicas de balanceamento de carga são baseadas em algoritmos padrões da indústria, como round robin, menos conexões ou tempo de resposta mais rápido. Nenhum deles é stateful, e é possível que o mesmo usuário tenha cada solicitação feita a um aplicativo distribuída para um servidor diferente. Isso torna inútil todo o trabalho feito para implementar o estado para HTTP, porque os dados armazenados na sessão de um servidor raramente são compartilhados com outros servidores no "pool".
É aqui que o conceito de persistência se torna útil.
Persistência — também conhecida como aderência — é uma técnica implementada por ADCs para garantir que solicitações de um único usuário sejam sempre distribuídas ao servidor no qual foram iniciadas. Alguns produtos e serviços de balanceamento de carga descrevem essa técnica como “sessões persistentes”, o que é um apelido completamente apropriado.
A persistência tem sido usada há muito tempo em sites com balanceamento de carga habilitado para SSL/TLS porque, uma vez concluído o processo de negociação — que exige muita computação — e as chaves trocadas, o desempenho seria significativamente prejudicado se o processo fosse reiniciado. Assim, os ADCs implementaram a persistência de sessão SSL para garantir que os usuários fossem sempre direcionados ao mesmo servidor ao qual se conectaram primeiro.
Ao longo dos anos, as implementações de navegadores exigiram o desenvolvimento de uma técnica para evitar a renegociação dispendiosa dessas sessões. Essa técnica é chamada de persistência baseada em cookies.
Em vez de depender do ID de sessão SSL/TLS, o balanceador de carga inseriria um cookie para identificar exclusivamente a sessão na primeira vez que um cliente acessasse o site e, então, faria referência a esse cookie em solicitações subsequentes para persistir a conexão com o servidor apropriado.
O conceito de persistência baseada em cookies tem sido aplicado a sessões de aplicativos, usando informações de ID de sessão geradas por servidores web e de aplicativos para garantir que as solicitações do usuário sejam sempre direcionadas ao mesmo servidor durante a mesma sessão. Sem esse recurso, os aplicativos que exigem balanceamento de carga precisariam encontrar outra maneira de compartilhar informações de sessão ou recorrer ao aumento do tempo limite de sessão e conexão a ponto de o número de servidores necessários para dar suporte à sua base de usuários crescer rapidamente e ficar incontrolável.
Embora a forma mais comum de persistência seja implementada usando IDs de sessão passadas no cabeçalho HTTP, os ADCs hoje também podem persistir em outros dados. Quaisquer dados que possam ser armazenados em um cookie ou derivados dos cabeçalhos IP, TCP ou HTTP podem ser usados para persistir uma sessão. De fato, quaisquer dados em uma mensagem de aplicativo que identifiquem exclusivamente o usuário podem ser usados por um ADC inteligente para manter uma conexão entre o navegador e um servidor.
O HTTP pode ser um protocolo sem estado, mas conseguimos forçar a adaptação do estado ao protocolo onipresente. Usando persistência e controladores de entrega de aplicativos, é possível arquitetar aplicativos web de alto desempenho e alta disponibilidade sem interromper a integração um tanto frágil de cookies e sessões necessária para manter o estado.
Esses recursos são o que dão estado ao HTTP, embora sua implementação e execução permaneçam sem estado. Sem cookies, sessões e persistência, certamente teríamos encontrado um protocolo com estado no qual construir nossos aplicativos. Em vez disso, os recursos e funcionalidades encontrados nos Controladores de Entrega de Aplicativos fazem a mediação entre navegadores (clientes) e servidores para fornecer essa funcionalidade, estendendo a utilidade do HTTP além de páginas da web estáticas e aplicativos tradicionais para arquiteturas modernas baseadas em microsserviços e a queridinha da economia digital, a API.