Introdução ao HTTP/2

O HTTP/2 tornará nossos aplicativos mais rápidos, simples e robustos (uma rara combinação) ao permitir desfazer muitas das soluções alternativas HTTP/1.1 feitas anteriormente em nossos aplicativos e abordar essas preocupações dentro da própria camada de transporte. Melhor ainda, ele abre um mundo novo de oportunidades para otimizar aplicativos e melhorar o desempenho.

Os principais objetivos do HTTP/2 são reduzir a latência ativando a multiplexação completa de solicitação e resposta, minimizar a sobrecarga do protocolo com a compactação eficiente dos campos de cabeçalho HTTP e adicionar suporte à priorização de solicitações e ao push do servidor. Para implementar esses requisitos, há um grande elenco de suporte com outras melhorias de protocolo, como novos mecanismos de controle de fluxo, tratamento de erros e upgrade. No entanto, esses são os recursos mais importantes que todo desenvolvedor Web precisa entender e aproveitar nos aplicativos.

O HTTP/2 não modifica de maneira alguma a semântica do HTTP do aplicativo. Todos os conceitos principais, como métodos HTTP, códigos de status, URIs e campos de cabeçalho, permanecem em vigor. Em vez disso, o HTTP/2 modifica como os dados são formatados (enquadrados) e transportados entre o cliente e o servidor, os quais gerenciam todo o processo e oculta toda a complexidade dos nossos aplicativos dentro da nova camada de enquadramento. Como resultado, todos os aplicativos existentes podem ser enviados sem modificação.

Por que não usar HTTP/1.2?

Para atingir as metas de desempenho definidas pelo grupo de trabalho HTTP, o HTTP/2 introduz uma nova camada de enquadramento binário que não é compatível com versões anteriores dos servidores e clientes HTTP/1.x anteriores. Por isso, o incremento da versão do protocolo principal para HTTP/2.

Dito isso, a menos que você esteja implementando um servidor da Web (ou um cliente personalizado) trabalhando com soquetes TCP brutos, não haverá nenhuma diferença: todo o enquadramento novo e de baixo nível é executado pelo cliente e pelo servidor em seu nome. As únicas diferenças observáveis serão o desempenho aprimorado e a disponibilidade de novos recursos, como priorização de solicitações, controle de fluxo e push do servidor.

Um breve histórico do SPDY e do HTTP/2

O SPDY era um protocolo experimental, desenvolvido no Google e anunciado em meados de 2009. O objetivo principal era tentar reduzir a latência de carregamento de páginas da Web abordando algumas das limitações de desempenho conhecidas do HTTP/1.1. Especificamente, as metas do projeto descritas foram definidas da seguinte maneira:

  • Redução de 50% no tempo de carregamento da página (PLT, na sigla em inglês).
  • Evite a necessidade de alterações de conteúdo pelos criadores de sites.
  • Minimizar a complexidade da implantação e evitar mudanças na infraestrutura de rede.
  • Desenvolva esse novo protocolo em parceria com a comunidade de código aberto.
  • Coletar dados de desempenho reais para (in)validar o protocolo experimental.

Não muito depois do anúncio inicial, Mike Belshe e Roberto Peon, ambos engenheiros de software do Google, compartilharam os primeiros resultados, documentação e código-fonte para a implementação experimental do novo protocolo SPDY:

Até agora, só testamos o SPDY em condições de laboratório. Os resultados iniciais são muito encorajadores: quando fazemos o download dos 25 principais sites por conexões de rede doméstica simuladas, notamos uma melhoria significativa no desempenho: as páginas carregam até 55% mais rápido. (Blog do Chromium)

Em 2012, o novo protocolo experimental era compatível com o Chrome, Firefox e Opera, e um número crescente de sites, grandes (por exemplo, Google, Twitter, Facebook) e pequenos, estavam implantando o SPDY na infraestrutura. Com isso, o SPDY estava a caminho de se tornar um padrão real devido à crescente adoção do setor.

Observando essa tendência, o HTTP Working Group (HTTP-WG) deu início a um novo esforço para aproveitar as lições aprendidas com o SPDY, criá-las e melhorá-las e entregar um padrão "HTTP/2" oficial. Um novo termo de abertura foi elaborado, uma chamada aberta para propostas de HTTP/2 foi feita e, após muita discussão com o grupo de trabalho, a especificação SPDY foi adotada como ponto de partida para o novo protocolo HTTP/2.

Nos anos seguintes, SPDY e HTTP/2 continuaram coevoluindo em paralelo, atuando como uma ramificação experimental usada para testar novos recursos e propostas para o padrão HTTP/2. O que parece bom no papel pode não funcionar na prática, e vice-versa. Por isso, o SPDY ofereceu um caminho para testar e avaliar cada proposta antes da inclusão no padrão HTTP/2. No fim das contas, esse processo durou três anos e resultou em mais de uma dúzia de rascunhos intermediários:

  • Março de 2012: Chamada para propostas de HTTP/2
  • Novembro de 2012: primeiro rascunho de HTTP/2 (com base no SPDY)
  • Agosto de 2014: publicação do rascunho 17 do HTTP/2 e do rascunho 12 do HPACK
  • Agosto de 2014: última chamada do grupo de trabalho para HTTP/2
  • Fevereiro de 2015: o IESG aprovou rascunhos do HTTP/2 e do HPACK
  • Maio de 2015: o RFC 7540 (HTTP/2) e o RFC 7541 (HPACK) são publicados

No início de 2015, o IESG revisou e aprovou o novo padrão HTTP/2 para publicação. Pouco tempo depois, a equipe do Google Chrome anunciou a programação da suspensão do uso da extensão SPDY e NPN para TLS:

As principais mudanças do HTTP/2 em relação ao HTTP/1.1 se concentram no desempenho aprimorado. Alguns recursos importantes, como multiplexação, compactação de cabeçalho, priorização e negociação de protocolo, evoluíram do trabalho feito em um protocolo anterior aberto, mas não padrão, chamado SPDY. O Chrome oferece suporte ao SPDY desde o Chrome 6, mas como a maioria dos benefícios está presente no HTTP/2, é hora de se despedir. Planejamos remover o suporte ao SPDY no início de 2016 e também remover o suporte à extensão TLS chamada NPN para favorecer o ALPN no Chrome ao mesmo tempo. É altamente recomendável que os desenvolvedores de servidores migrem para HTTP/2 e ALPN.

Ficamos felizes em ter contribuído para o processo de padrões abertos que levou ao HTTP/2 e esperamos ver uma ampla adoção, considerando o amplo engajamento do setor em padronização e implementação. (Blog do Chromium) (em inglês)

A evolução conjunta do SPDY e do HTTP/2 permitiu que desenvolvedores de servidores, navegadores e sites ganhassem experiência real com o novo protocolo à medida que ele ia sendo desenvolvido. Por isso, o HTTP/2 é um dos melhores e mais testados padrões desde a sua concepção. Quando o HTTP/2 foi aprovado pelo IESG, havia dezenas de implementações de cliente e servidor completamente testadas e prontas para produção. Na verdade, apenas algumas semanas após a aprovação do protocolo final, muitos usuários já aproveitavam os benefícios, já que vários navegadores conhecidos (e muitos sites) implantaram a compatibilidade total com HTTP/2.

Design e objetivos técnicos

As versões anteriores do protocolo HTTP foram projetadas intencionalmente para simplificar a implementação: o HTTP/0.9 era um protocolo de uma linha para inicializar a World Wide Web; o HTTP/1.0 documentava as extensões populares do HTTP/0.9 em um padrão informativo; o HTTP/1.1 introduziu um padrão IETF oficial. Consulte a breve história do HTTP (em inglês). Assim, o HTTP/0.9-1.x oferece exatamente o que se propôs a fazer: o HTTP é um dos protocolos de aplicativo mais adotados da Internet.

Infelizmente, a simplicidade da implementação também teve um custo de desempenho do aplicativo: os clientes HTTP/1.x precisam usar várias conexões para conseguir simultaneidade e reduzir a latência, o HTTP/1.x não compacta cabeçalhos de solicitação e resposta, gerando tráfego de rede desnecessário; o HTTP/1.x não permite priorização eficaz de recursos, resultando em mau uso da conexão TCP subjacente e assim por diante.

Essas limitações não foram fatais, mas, à medida que os aplicativos da Web continuaram a crescer em escopo, complexidade e importância em nossas vidas cotidianas, impuseram um fardo crescente sobre os desenvolvedores e usuários da Web, que é exatamente essa lacuna que o HTTP/2 foi projetado para atender:

O HTTP/2 permite um uso mais eficiente dos recursos de rede e uma percepção reduzida de latência, introduzindo a compactação de campo de cabeçalho e permitindo várias trocas simultâneas na mesma conexão. Especificamente, ele permite intercalar mensagens de solicitação e resposta na mesma conexão e usa uma codificação eficiente para campos de cabeçalho HTTP. Ela também permite a priorização de solicitações, permitindo que as mais importantes sejam concluídas mais rapidamente, melhorando ainda mais o desempenho.

O protocolo resultante é mais amigável para a rede, porque menos conexões TCP podem ser usadas em comparação com HTTP/1.x. Isso significa menos concorrência com outros fluxos e conexões de maior duração, o que, por sua vez, leva a uma melhor utilização da capacidade de rede disponível. Por fim, o HTTP/2 também permite o processamento mais eficiente de mensagens com o enquadramento binário. (Protocolo de transferência de hipertexto versão 2, rascunho 17)

É importante observar que o HTTP/2 está estendendo, não substituindo, os padrões HTTP anteriores. A semântica HTTP do aplicativo é a mesma, e nenhuma alteração foi feita na funcionalidade oferecida ou nos conceitos principais, como métodos HTTP, códigos de status, URIs e campos de cabeçalho. Essas alterações estavam explicitamente fora do escopo da iniciativa HTTP/2. Dito isso, embora a API de alto nível permaneça a mesma, é importante entender como as alterações de baixo nível abordam as limitações de desempenho dos protocolos anteriores. Vamos fazer um tour rápido pela camada de enquadramento binário e pelas características dela.

Camada de enquadramento binário

No centro de todas as melhorias de desempenho do HTTP/2 está a nova camada de enquadramento binário, que dita como as mensagens HTTP são encapsuladas e transferidas entre o cliente e o servidor.

Camada de enquadramento binário HTTP/2

A "camada" se refere a uma opção de design para introduzir um novo mecanismo de codificação otimizado entre a interface do soquete e a API HTTP mais recente exposta aos aplicativos: a semântica HTTP, como verbos, métodos e cabeçalhos, não é afetada, mas a maneira como é codificada durante o trânsito é diferente. Ao contrário do protocolo HTTP/1.x de texto simples delimitado por nova linha, toda a comunicação HTTP/2 é dividida em mensagens e frames menores, cada um codificado no formato binário.

Como resultado, o cliente e o servidor precisam usar o novo mecanismo de codificação binária para se entenderem: um cliente HTTP/1.x não entende um servidor somente HTTP/2 e vice-versa. Felizmente, nossos aplicativos permanecem felizes com todas essas mudanças, já que o cliente e o servidor realizam todo o trabalho de enquadramento necessário em nosso nome.

Streams, mensagens e frames

A introdução do novo mecanismo de enquadramento binário muda a forma como os dados são trocados entre o cliente e o servidor. Para descrever esse processo, vamos conhecer a terminologia do HTTP/2:

  • Stream: um fluxo bidirecional de bytes dentro de uma conexão estabelecida, que pode transportar uma ou mais mensagens.
  • Mensagem: uma sequência completa de frames mapeados para uma mensagem lógica de solicitação ou resposta.
  • Frame: a menor unidade de comunicação em HTTP/2, cada uma contendo um cabeçalho de frame, que identifica, no mínimo, o stream a que o frame pertence.

A relação desses termos pode ser resumida da seguinte forma:

  • Toda a comunicação é realizada por uma única conexão TCP que pode transportar qualquer número de fluxos bidirecionais.
  • Cada stream tem um identificador exclusivo e informações de prioridade opcionais que são usadas para transportar mensagens bidirecionais.
  • Cada mensagem é uma mensagem HTTP lógica, como uma solicitação ou uma resposta, composta por um ou mais frames.
  • O frame é a menor unidade de comunicação que transporta um tipo específico de dados, por exemplo, cabeçalhos HTTP, payload da mensagem e assim por diante. Os frames de diferentes streams podem ser intercalados e, em seguida, remontados pelo identificador de stream incorporado no cabeçalho de cada frame.

Streams, mensagens e frames HTTP/2

Em resumo, o HTTP/2 divide a comunicação do protocolo HTTP em uma troca de frames de codificação binária, que são então mapeados para mensagens que pertencem a um determinado fluxo, todos multiplexados em uma única conexão TCP. Essa é a base que possibilita todos os outros recursos e otimizações de desempenho fornecidos pelo protocolo HTTP/2.

Multiplexação de solicitação e resposta

Com o HTTP/1.x, se o cliente quiser fazer várias solicitações paralelas para melhorar o desempenho, será preciso usar diversas conexões TCP (consulte Como usar várias conexões TCP). Esse comportamento é uma consequência direta do modelo de entrega HTTP/1.x, que garante que apenas uma resposta seja entregue por vez (enfileiramento de respostas) por conexão. E, pior ainda, isso resulta em bloqueio no início da fila e uso ineficiente da conexão TCP subjacente.

A nova camada de enquadramento binário no HTTP/2 remove essas limitações e permite a multiplexação completa de solicitação e resposta, permitindo que o cliente e o servidor dividam uma mensagem HTTP em frames independentes, intercalem-os e, em seguida, remontem-os na outra extremidade.

Multiplexação de solicitação e resposta HTTP/2 em uma conexão compartilhada

O snapshot captura vários streams em trânsito dentro da mesma conexão. O cliente está transmitindo um frame DATA (stream 5) ao servidor, enquanto o servidor transmite uma sequência intercalada de frames ao cliente para os streams 1 e 3. Como resultado, há três fluxos paralelos em trânsito.

A capacidade de dividir uma mensagem HTTP em frames independentes, intercalá-los e reagrupá-los na outra extremidade é a melhoria mais importante do HTTP/2. Na verdade, ele apresenta um efeito de propagação de vários benefícios de desempenho em toda a pilha de todas as tecnologias da Web, permitindo:

  • Intercalar várias solicitações em paralelo sem bloquear nenhuma delas.
  • Intercale várias respostas em paralelo sem bloquear nenhuma.
  • Use uma única conexão para entregar várias solicitações e respostas em paralelo.
  • Remova soluções alternativas de HTTP/1.x desnecessárias. Consulte Otimização para HTTP/1.x (link em inglês), como arquivos concatenados, sprites de imagem e fragmentação de domínio.
  • Ofereça tempos de carregamento de página mais curtos, eliminando a latência desnecessária e melhorando a utilização da capacidade de rede disponível.
  • E muito mais...

A nova camada de enquadramento binário no HTTP/2 resolve o problema de bloqueio "head-of-line" encontrado no HTTP/1.x e elimina a necessidade de várias conexões para permitir o processamento paralelo e o envio de solicitações e respostas. Como resultado, isso torna nossos aplicativos mais rápidos, simples e baratos de implantar.

Priorização de stream

Assim que uma mensagem HTTP pode ser dividida em muitos frames individuais e permitimos que frames de vários streams sejam multiplexados, a ordem em que os frames são intercalados e entregues pelo cliente e pelo servidor se torna uma consideração essencial de desempenho. Para facilitar isso, o padrão HTTP/2 permite que cada stream tenha um peso e uma dependência associados:

  • Cada stream pode receber um peso inteiro entre 1 e 256.
  • Cada stream pode ter uma dependência explícita em outro.

A combinação de dependências e pesos de stream permite que o cliente construa e comunique uma "árvore de priorização" que expressa como ele prefere receber respostas. Por sua vez, o servidor pode usar essas informações para priorizar o processamento de stream controlando a alocação de CPU, memória e outros recursos e, quando os dados de resposta estiverem disponíveis, a alocação de largura de banda para garantir a entrega ideal de respostas de alta prioridade para o cliente.

Dependências e pesos de stream HTTP/2

Uma dependência de stream em HTTP/2 é declarada fazendo referência ao identificador exclusivo de outro stream como o pai. Se o identificador for omitido, o stream será considerado dependente do "stream raiz". Declarar uma dependência de stream indica que, se possível, o stream pai precisa receber recursos alocados antes das dependências. Em outras palavras, "Processe e envie a resposta D antes da C".

Os streams que compartilham o mesmo pai (ou seja, streams irmãos) precisam receber recursos proporcionais ao peso deles. Por exemplo, se o stream A tiver um peso de 12 e o irmão B tiver um peso de 4, para determinar a proporção dos recursos que cada um desses streams receberá:

  1. Some todos os pesos: 4 + 12 = 16
  2. Divida o peso de cada stream pelo peso total: A = 12/16, B = 4/16

Assim, o stream A deve receber três quartos, e o stream B deve receber 1/4 dos recursos disponíveis. O stream B deve receber 1/3 dos recursos alocados ao stream A. Vamos analisar mais alguns exemplos práticos na imagem acima. Da esquerda para a direita:

  1. Nem o stream A nem o B especificam uma dependência pai e são considerados dependentes do "stream raiz" implícito. A tem um peso de 12 e B tem um peso de 4. Assim, com base em pesos proporcionais: o stream B precisa receber um terço dos recursos alocados para o stream A.
  2. O stream D é dependente do stream raiz, e C é dependente de D. Assim, D receberá a alocação completa de recursos antes de C. Os pesos não têm importância porque a dependência de C comunica uma preferência mais forte.
  3. O stream D precisa receber a alocação completa de recursos antes de C. O stream C deve receber a alocação total de recursos antes de A e B. O stream B precisa receber um terço dos recursos alocados ao stream A.
  4. O stream D precisa receber a alocação completa de recursos antes de E e C. E e C precisam receber alocação igual antes de A e B. A e B precisam receber alocação proporcional com base nos pesos.

Conforme ilustrado nos exemplos acima, a combinação de dependências e pesos de stream fornece uma linguagem expressiva para a priorização de recursos, que é um recurso essencial para melhorar o desempenho de navegação, em que temos muitos tipos de recursos com diferentes dependências e pesos. Melhor ainda, o protocolo HTTP/2 também permite que o cliente atualize essas preferências a qualquer momento, o que permite mais otimizações no navegador. Em outras palavras, podemos mudar dependências e realocar pesos em resposta à interação do usuário e outros indicadores.

Uma conexão por origem

Com o novo mecanismo de enquadramento binário em vigor, o HTTP/2 não precisa mais de várias conexões TCP para multiplexar streams em paralelo. Cada stream é dividido em muitos frames, que podem ser intercalados e priorizados. Como resultado, todas as conexões HTTP/2 são persistentes e apenas uma conexão por origem é necessária, o que oferece vários benefícios de desempenho.

Tanto para o SPDY quanto para o HTTP/2, o recurso destrutivo é a multiplexação arbitrária em um único canal bem controlado de congestionamento. É impressionante a importância disso e quão bem funciona. Uma ótima métrica em relação a isso que eu gosto é a fração de conexões criadas que transportam apenas uma única transação HTTP (e, portanto, fazem com que essa transação arca com toda a sobrecarga). Para HTTP/1, 74% das nossas conexões ativas transportam apenas uma transação. Conexões persistentes não são tão úteis quanto queremos. Mas no HTTP/2 esse número despeja para 25%. Essa é uma grande vitória para a redução de sobrecarga. (HTTP/2 está disponível no Firefox, Patrick McManus)

A maioria das transferências HTTP é curta e súbita, enquanto o TCP é otimizado para transferências de dados em massa e de longa duração. Ao reutilizar a mesma conexão, o HTTP/2 pode fazer um uso mais eficiente de cada conexão TCP e também reduzir significativamente a sobrecarga geral do protocolo. Além disso, o uso de menos conexões reduz a pegada de memória e processamento ao longo de todo o caminho de conexão (em outras palavras, clientes, intermediários e servidores de origem). Isso reduz o custo operacional geral e melhora a utilização e a capacidade da rede. Como resultado, a mudança para HTTP/2 não deve apenas reduzir a latência de rede, mas também ajudar a melhorar a capacidade e reduzir os custos operacionais.

Controle de fluxo

O controle de fluxo é um mecanismo para evitar que o remetente sobrecarregue o receptor com dados que talvez não queira ou possa processar: o receptor pode estar ocupado, sob carga pesada ou pode estar disposto a alocar uma quantidade fixa de recursos para um fluxo específico. Por exemplo, o cliente pode ter solicitado um grande stream de vídeo com alta prioridade, mas o usuário pausou o vídeo e agora quer pausar ou limitar a entrega do servidor para evitar a busca e o armazenamento em buffer de dados desnecessários. Como alternativa, um servidor proxy pode ter conexões downstream rápidas e upstream lentas e, da mesma forma, quer regular a rapidez com que o downstream fornece dados para corresponder à velocidade do upstream para controlar o uso de recursos e assim por diante.

Os requisitos acima fazem você lembrar do controle de fluxo TCP? Elas deveriam ser encontradas, já que o problema é praticamente o mesmo. Consulte Controle de fluxo. No entanto, como os streams HTTP/2 são multiplexados em uma única conexão TCP, o controle de fluxo TCP não é granular o suficiente e não fornece as APIs no nível do aplicativo necessárias para regular a entrega de streams individuais. Para resolver isso, o HTTP/2 fornece um conjunto de elementos básicos simples que permitem que o cliente e o servidor implementem o próprio controle de fluxo no nível da conexão e do stream:

  • O controle de fluxo é direcional. Cada receptor pode escolher definir o tamanho que quiser para a janela de cada stream e para toda a conexão.
  • O controle de fluxo é baseado em crédito. Cada receptor divulga a conexão inicial e a janela de controle de fluxo de stream (em bytes), que é reduzida sempre que o remetente emite um frame DATA e incrementada por um frame WINDOW_UPDATE enviado pelo receptor.
  • O controle de fluxo não pode ser desativado. Quando a conexão HTTP/2 é estabelecida, o cliente e o servidor trocam frames SETTINGS, que definem os tamanhos da janela de controle de fluxo em ambas as direções. O valor padrão da janela de controle de fluxo é definido como 65.535 bytes, mas o receptor pode definir um tamanho máximo grande para a janela (2^31-1 bytes) e mantê-lo enviando um frame WINDOW_UPDATE sempre que algum dado for recebido.
  • O controle de fluxo funciona de salto em salto, não de ponta a ponta. Ou seja, um intermediário pode usá-lo para controlar o uso de recursos e implementar mecanismos de alocação de recursos com base em critérios e heurística próprios.

O HTTP/2 não especifica nenhum algoritmo em particular para implementar o controle de fluxo. Em vez disso, ele fornece os elementos básicos simples e adia a implementação para o cliente e o servidor, que podem usá-la para implementar estratégias personalizadas a fim de regular o uso e a alocação de recursos, bem como implementar novos recursos de entrega que podem ajudar a melhorar o desempenho real e percebido (consulte Velocidade, desempenho e percepção humana) dos nossos aplicativos da Web.

Por exemplo, o controle de fluxo na camada do aplicativo permite que o navegador busque apenas uma parte de um recurso específico, coloque a busca em espera reduzindo a janela de controle de fluxo do stream para zero e a retome mais tarde. Em outras palavras, ele permite que o navegador busque uma visualização ou primeira verificação de uma imagem, exiba-a e permita que outras buscas de alta prioridade continuem e retome a busca assim que mais recursos críticos forem carregados.

Push de servidor

Outro recurso novo e eficiente do HTTP/2 é a capacidade do servidor de enviar várias respostas a uma única solicitação do cliente. Ou seja, além da resposta à solicitação original, o servidor pode enviar outros recursos ao cliente (Figura 12-5), sem que o cliente tenha que solicitá-los explicitamente.

O servidor inicia novos streams (promessas) para recursos de push

Por que precisaríamos desse mecanismo em um navegador? Um aplicativo da Web típico consiste em dezenas de recursos, todos descobertos pelo cliente examinando o documento fornecido pelo servidor. Como resultado, por que não eliminar a latência extra e deixar o servidor enviar os recursos associados com antecedência? O servidor já sabe de quais recursos o cliente precisará. Esse é o push do servidor.

Na verdade, se você já in-lineu um CSS, JavaScript ou qualquer outro recurso por meio de um URI de dados (consulte In-line de recursos), já tem experiência prática com o push de servidor. Ao embutir o recurso manualmente no documento, estamos, na verdade, enviando esse recurso para o cliente, sem esperar que o cliente o solicite. Com o HTTP/2, podemos alcançar os mesmos resultados, mas com mais benefícios de desempenho. Os recursos de push podem ser:

  • Armazenado em cache pelo cliente
  • Reutilizados em diferentes páginas
  • Multiplexado com outros recursos
  • Priorizado pelo servidor
  • Recusado pelo cliente

INTRODUÇÃO AO PUSH_PROMISE

Todos os streams de push do servidor são iniciados por frames PUSH_PROMISE, que sinalizam a intenção do servidor de enviar os recursos descritos ao cliente e precisam ser entregues antes dos dados de resposta que solicitam os recursos enviados. Essa ordem de entrega é fundamental: o cliente precisa saber quais recursos o servidor pretende enviar por push para evitar solicitações duplicadas para esses recursos. A estratégia mais simples para atender a esse requisito é enviar todos os frames PUSH_PROMISE, que contêm apenas os cabeçalhos HTTP do recurso prometido, antes da resposta do pai (ou seja, frames DATA).

Depois que o cliente recebe um frame PUSH_PROMISE, ele tem a opção de recusar o stream (por um frame RST_STREAM), se quiser. Isso pode ocorrer, por exemplo, porque o recurso já está armazenado em cache. Essa é uma melhoria importante em relação ao HTTP/1.x. Por outro lado, o uso do recurso inline, que é uma "otimização" conhecida para HTTP/1.x, é equivalente a um "push forçado": o cliente não pode recusar, cancelá-lo ou processar o recurso embutido individualmente.

Com o HTTP/2, o cliente permanece no controle total de como o push do servidor é usado. O cliente pode limitar o número de streams enviados simultaneamente, ajustar a janela de controle de fluxo inicial para controlar a quantidade de dados enviados quando o stream é aberto pela primeira vez ou desativar totalmente o push do servidor. Essas preferências são comunicadas pelos frames SETTINGS no início da conexão HTTP/2 e podem ser atualizadas a qualquer momento.

Cada recurso enviado é um fluxo que, ao contrário de um recurso embutido, permite que ele seja multiplexado, priorizado e processado individualmente pelo cliente. A única restrição de segurança aplicada pelo navegador é que os recursos enviados precisam obedecer à política de mesma origem: o servidor precisa ser autoritativo para o conteúdo fornecido.

Compactação de cabeçalho

Cada transferência HTTP carrega um conjunto de cabeçalhos que descrevem o recurso transferido e as propriedades dele. No HTTP/1.x, esses metadados são sempre enviados como texto simples e adicionam de 500 a 800 bytes de sobrecarga por transferência e, às vezes, kilobytes a mais, se cookies HTTP estiverem sendo usados. Consulte Como medir e controlar a sobrecarga do protocolo. Para reduzir essa sobrecarga e melhorar o desempenho, o HTTP/2 compacta os metadados do cabeçalho de solicitação e resposta usando o formato de compactação HPACK, que usa duas técnicas simples, mas poderosas:

  1. Ele permite que os campos de cabeçalho transmitidos sejam codificados por um código Huffman estático, o que reduz o tamanho da transferência individual.
  2. É necessário que o cliente e o servidor mantenham e atualizem uma lista indexada de campos de cabeçalho vistos anteriormente. Ou seja, estabelece um contexto de compactação compartilhado, que é usado como referência para codificar com eficiência os valores transmitidos anteriormente.

A codificação Huffman permite que os valores individuais sejam compactados quando transferidos, e a lista indexada de valores transferidos anteriormente permite codificar valores duplicados transferindo valores de índice que podem ser usados para procurar e reconstruir com eficiência as chaves e os valores de cabeçalho completos.

HPACK: compactação de cabeçalho para HTTP/2

Como uma otimização adicional, o contexto de compactação HPACK consiste em uma tabela estática e dinâmica: a tabela estática é definida na especificação e fornece uma lista de campos de cabeçalho HTTP comuns que todas as conexões provavelmente usarão (por exemplo, nomes de cabeçalho válidos). A tabela dinâmica é inicialmente vazia e é atualizada com base nos valores trocados dentro de uma determinada conexão. Como resultado, o tamanho de cada solicitação é reduzido com o uso da codificação Huffman estática para valores que não foram vistos antes e com a substituição de índices por valores que já estão presentes nas tabelas estáticas ou dinâmicas em cada lado.

Segurança e desempenho do HPACK

As primeiras versões do HTTP/2 e do SPDY usavam zlib, com um dicionário personalizado, para compactar todos os cabeçalhos HTTP. Isso resultou em uma redução de 85% a 88% no tamanho dos dados de cabeçalho transferidos e uma melhoria significativa na latência do tempo de carregamento da página:

No link DSL de menor largura de banda, em que o link de upload tem apenas 375 Kbps, a compactação de cabeçalhos de solicitação em particular levou a melhorias significativas no tempo de carregamento da página para determinados sites (ou seja, aqueles que emitiam um grande número de solicitações de recursos). Encontramos uma redução de 45 a 1.142 ms no tempo de carregamento de página simplesmente devido à compactação do cabeçalho. (Artigo sobre SPDY, chromium.org)

No entanto, no verão de 2012, um ataque de segurança "CRIMINOSO" foi publicado contra algoritmos de compressão TLS e SPDY, o que poderia resultar em sequestro de sessão. Como resultado, o algoritmo de compactação zlib foi substituído pelo HPACK, que foi projetado especificamente para resolver os problemas de segurança descobertos, ser eficiente e simples de implementar corretamente e, claro, permitir uma boa compactação dos metadados do cabeçalho HTTP.

Para ver todos os detalhes sobre o algoritmo de compactação HPACK, consulte IETF HPACK - Compactação de cabeçalho para HTTP/2 (link em inglês).

Leia mais