Para adicionar compressão HTTP a um serviço Zig, negocie o formato pelo header Accept-Encoding, comprima apenas respostas textuais que tenham tamanho suficiente e envie Content-Encoding: gzip ou br junto com Vary: Accept-Encoding. Na maioria dos ambientes de produção, a melhor decisão é deixar gzip ou Brotli no Nginx, Caddy, CDN ou load balancer e manter o binário Zig responsável pela resposta original.
A compressão dentro da aplicação faz sentido quando o serviço é exposto diretamente, produz um stream que não passa por uma borda capaz de comprimir ou precisa de uma política específica por rota. Em qualquer desenho, não comprima JPEG, PNG, ZIP e outros formatos já comprimidos; não envie gzip para um cliente que o recusou; e não reutilize o Content-Length do corpo original.
Resposta rápida
| Decisão | Recomendação inicial |
|---|---|
| Onde comprimir | proxy, CDN ou load balancer |
| Formatos bons candidatos | JSON, HTML, CSS, JavaScript, XML, SVG e texto |
| Tamanho mínimo | comece entre 1 e 2 KiB e meça |
| Formatos a evitar | JPEG, PNG, WebP, MP4, ZIP, gzip e PDF já otimizado |
| Negociação | interpretar Accept-Encoding e pesos q |
| Header da resposta | Content-Encoding: gzip ou br |
| Cache | Vary: Accept-Encoding |
| Corpo comprimido dinâmico | prefira streaming quando puder ficar grande |
| Segurança | limite entrada, saída, tempo de CPU e descompressão |
| Validação | curl --compressed, inspeção de headers e benchmark |
Se a aplicação já roda atrás de uma borda, veja primeiro o guia de Zig por trás de Nginx. Para entender cache e validadores da representação, leia também ETag, If-None-Match e 304 em Zig.
O que é negociado em Accept-Encoding
O cliente usa Accept-Encoding para declarar quais codificações de conteúdo consegue decodificar. Uma requisição comum pode trazer:
GET /relatorio.json HTTP/1.1
Host: api.exemplo.com.br
Accept-Encoding: br, gzip, deflate
O servidor escolhe uma alternativa aceita e responde, por exemplo:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Encoding: gzip
Vary: Accept-Encoding
Content-Length: 1842
...bytes do fluxo gzip...
Content-Encoding descreve uma transformação aplicada ao corpo. O cliente precisa desfazê-la antes de interpretar o JSON. Isso é diferente de Content-Type, que continua informando o formato semântico do conteúdo.
Se o cliente não envia Accept-Encoding, a escolha segura é entregar a representação sem compressão. Se envia gzip;q=0, está recusando gzip. Um parser que apenas procura a substring gzip tomaria a decisão errada.
Também existe identity, que representa o corpo sem transformação:
Accept-Encoding: gzip;q=0.8, identity;q=1
Nesse exemplo, o cliente prefere a resposta original. Os pesos q variam de 0 a 1; valores maiores indicam preferência maior, e zero significa “não aceitável”. Em produção, a função de negociação deve ser pequena, testável e independente da API concreta de std.http.Server.
Modele a escolha sem acoplar ao servidor HTTP
Uma enumeração explícita torna impossível confundir o formato escolhido:
const ContentCoding = enum {
identity,
gzip,
br,
};
const EncodingPreference = struct {
coding: ContentCoding,
quality: u16, // 0 a 1000
};
O parser pode converter q=0.8 em 800, evitando ponto flutuante para uma decisão simples. A política recebe as preferências e as capacidades reais do servidor:
const CompressionSupport = struct {
gzip: bool = true,
br: bool = false,
};
fn chooseEncoding(
preferences: []const EncodingPreference,
support: CompressionSupport,
) ?ContentCoding {
// Escolha conceitual: maior quality entre formatos suportados.
// Retorne null se nenhuma representação aceitável existir.
_ = preferences;
_ = support;
return .identity;
}
Esse esqueleto separa três responsabilidades:
- ler e validar o header;
- escolher uma codificação permitida;
- aplicar a transformação ao corpo.
Não misture as três dentro do handler. A biblioteca padrão de Zig continua evoluindo antes do 1.0, mas essa fronteira permanece estável mesmo se os tipos de request, response ou writer mudarem.
Regras mínimas para o parser
Accept-Encoding parece simples, porém recebe entrada não confiável. Uma implementação defensiva deve:
- impor limite ao tamanho do header;
- separar itens por vírgula;
- remover espaços opcionais;
- comparar tokens sem diferenciar maiúsculas de minúsculas;
- aceitar parâmetros conhecidos, principalmente
q; - rejeitar ou ignorar pesos inválidos de forma consistente;
- tratar
q=0como recusa; - entender
*como curinga, sem fazê-lo superar uma recusa explícita; - definir o comportamento quando nenhum formato é aceitável.
Exemplos que precisam de teste:
gzip
gzip, br
gzip;q=0.5, br;q=1
gzip;q=0, identity;q=1
*;q=0.8, gzip;q=0
identity;q=0, gzip;q=0
GZip ; q=0.7
No último caso em que todas as opções disponíveis têm peso zero, o servidor pode responder 406 Not Acceptable. Para APIs comuns, é raro chegar a esse estado, mas a decisão deve ser explícita em vez de cair silenciosamente em gzip.
Quando vale a pena comprimir
Compressão não é uma propriedade global do servidor. Ela é uma decisão sobre uma representação específica.
JSON costuma comprimir bem porque nomes de campos, aspas, pontuação e estruturas se repetem. HTML, CSS, JavaScript, XML, SVG e texto também são bons candidatos. Uma resposta JSON de 100 KiB pode cair para uma fração do tamanho original, reduzindo banda e tempo de transferência em redes lentas.
Corpos muito pequenos são diferentes. Comprimir 180 bytes pode consumir CPU, adicionar metadados do formato e produzir pouco ganho. Por isso proxies usam um tamanho mínimo, frequentemente em torno de 1 KiB. O valor ideal depende do tráfego, do hardware e da latência dos clientes; trate o limite como hipótese para benchmark, não como constante universal.
Também evite recomprimir formatos que já fazem compressão interna:
- JPEG, PNG, WebP e AVIF;
- MP3, AAC, MP4 e WebM;
- ZIP, gzip, zstd e outros arquivos compactados;
- fontes WOFF e WOFF2;
- PDFs já otimizados.
A tentativa pode aumentar o corpo e desperdiçar CPU. Use uma allowlist de content types textuais em vez de tentar manter uma lista infinita de exceções.
Proxy reverso ou aplicação Zig?
Prefira a borda na maioria dos casos
Nginx, Caddy, Cloudflare e outros proxies já implementam negociação, tipos permitidos, tamanho mínimo, cache e algoritmos otimizados. Essa opção mantém o código Zig menor e concentra a política HTTP em um componente operacional conhecido.
Exemplo de configuração gzip no Nginx:
gzip on;
gzip_min_length 1024;
gzip_comp_level 5;
gzip_vary on;
gzip_types
application/json
application/javascript
application/xml
image/svg+xml
text/css
text/plain;
O upstream Zig devolve JSON normal. O Nginx examina a requisição, comprime quando a política permite e adiciona os headers. Antes de copiar a configuração, confirme a versão e os módulos disponíveis no seu ambiente.
Brotli normalmente exige suporte específico no proxy ou no provedor de CDN. Ele tende a produzir arquivos menores, especialmente para conteúdo textual estático, mas pode custar mais CPU em níveis altos. Para conteúdo dinâmico, níveis moderados ou gzip podem oferecer melhor relação entre latência e tamanho.
Comprima no Zig quando houver motivo claro
A aplicação pode assumir a compressão quando:
- não existe proxy reverso;
- o protocolo entre serviços exige o corpo comprimido;
- a resposta é gerada por streaming e a borda não atende ao caso;
- a política depende de autorização, rota ou custo de negócio;
- você produz arquivos
.gzcomo artefatos, e não apenas respostas web; - medir e controlar o uso de CPU no próprio processo é parte do requisito.
Mesmo nesses casos, mantenha um único componente responsável. Se o Zig já enviou Content-Encoding: gzip, o proxy não deve comprimir novamente. Compressão dupla produz um corpo que clientes comuns não esperam e dificulta cache, métricas e troubleshooting.
Compressão em memória ou por streaming
Para uma resposta pequena e com limite conhecido, o fluxo mais simples é:
gerar JSON original
-> comprimir em buffer com limite
-> enviar headers
-> enviar bytes comprimidos
A vantagem é conhecer o tamanho final e poder enviar Content-Length. A desvantagem é manter simultaneamente o corpo original e o comprimido, aumentando pico de memória e latência até o primeiro byte.
Para relatórios, exports ou respostas grandes, prefira streaming:
gerador de conteúdo
-> writer de compressão
-> writer da resposta HTTP
-> socket
Assim, os bytes são comprimidos conforme são produzidos. Em HTTP/1.1, o servidor pode usar transferência em chunks quando não conhece o tamanho final. Em versões e APIs diferentes do Zig, a composição concreta dos writers muda; consulte o módulo std.compress e a implementação instalada antes de fixar nomes de funções no código de produção.
Independentemente da API, imponha limites:
- máximo de bytes originais gerados;
- máximo de bytes comprimidos;
- prazo para terminar a resposta;
- cancelamento quando o cliente desconecta;
- limite de memória do buffer;
- concorrência máxima de compressões caras.
Streaming não elimina custo. Ele apenas evita que o corpo inteiro precise existir em memória.
Content-Length, chunked e respostas sem corpo
Depois de comprimir, Content-Length precisa representar os bytes comprimidos, não o tamanho do JSON original. Se o tamanho final é desconhecido, não reutilize o header anterior; use o mecanismo de streaming suportado pelo servidor.
Algumas respostas não devem carregar corpo:
204 No Content;304 Not Modified;- respostas a
HEAD, que descrevem a representação sem transferir seu corpo.
A interação de HEAD com compressão merece contrato claro. Idealmente, os headers descrevem o que uma requisição GET equivalente produziria, mas gerar todo o corpo apenas para calcular o tamanho comprimido anula o benefício de HEAD. Uma alternativa é omitir Content-Length quando ele não estiver disponível sem custo, mantendo os demais metadados coerentes.
Vary é obrigatório para caches corretas
Estas duas respostas não são intercambiáveis:
cliente A aceita gzip -> corpo gzip
cliente B não aceita -> corpo original
Sem Vary: Accept-Encoding, uma CDN pode armazenar a primeira e entregar bytes gzip ao cliente B sem o header ou sem capacidade de decodificação. Também pode armazenar a versão grande e desperdiçar banda para clientes que aceitam compressão.
Envie:
Vary: Accept-Encoding
Se a resposta já varia por outro campo, combine os valores:
Vary: Origin, Accept-Encoding
Não substitua Vary: Origin criado pela política de CORS em Zig. Centralizar a montagem de headers evita que um middleware apague a decisão de outro.
ETags também precisam representar a variante correta. Um hash calculado sobre bytes comprimidos será diferente do hash do corpo original. Você pode gerar ETags distintas por representação ou usar uma estratégia semântica cuidadosamente documentada. O que não pode acontecer é devolver 304 com base em um validador que aponta para outra variante.
Segurança: compressão não é apenas performance
Não comprima segredos junto com entrada controlada
Ataques de compressão exploram diferenças no tamanho do corpo para inferir segredos quando uma resposta mistura dados confidenciais com texto escolhido pelo atacante. O risco depende do protocolo e da capacidade de repetir medições, mas a regra prudente é não colocar tokens, cookies, chaves ou dados sigilosos refletidos em uma resposta comprimida controlável.
Para endpoints sensíveis, considere desativar compressão, remover reflexão de entrada e aplicar proteção contra CSRF e cache indevido. Cache-Control: no-store não corrige sozinho um vazamento por tamanho.
Cuidado ao descomprimir entrada
Compressão de resposta e descompressão de upload são problemas diferentes. Um corpo pequeno comprimido pode expandir para gigabytes. Se a API aceita Content-Encoding no request ou arquivos compactados, limite:
- bytes recebidos;
- bytes após descompressão;
- proporção máxima de expansão;
- número de arquivos e profundidade de arquivos aninhados;
- CPU e tempo de processamento.
Nunca confie apenas no Content-Length comprimido. O guia de upload em streaming e validação mostra a mesma mentalidade: limite durante a leitura, não depois de já consumir toda a entrada.
Política prática por rota
Uma função de decisão pode considerar status, método, tipo e tamanho:
const ResponseMeta = struct {
status: u16,
content_type: []const u8,
content_length: ?usize,
already_encoded: bool,
sensitive: bool,
};
fn shouldCompress(meta: ResponseMeta) bool {
if (meta.already_encoded or meta.sensitive) return false;
if (meta.status == 204 or meta.status == 304) return false;
if (meta.content_length) |size| {
if (size < 1024) return false;
}
return isCompressibleContentType(meta.content_type);
}
isCompressibleContentType deve usar comparação normalizada e uma allowlist. Cuidado com parâmetros como ; charset=utf-8: comparar o content type inteiro com application/json falharia. Separe o media type antes do ponto e vírgula.
Uma política inicial razoável:
- comprimir
application/jsone tipos textuais a partir de 1 KiB; - não comprimir respostas autenticadas que misturam segredo e reflexão;
- não comprimir corpos já codificados;
- deixar Brotli para a borda, salvo requisito específico;
- registrar métricas por codificação, sem gravar conteúdo.
Métricas que mostram se funcionou
A taxa de compressão sozinha não basta. Colete, por rota e content type:
- bytes antes da compressão;
- bytes enviados;
- tempo gasto comprimindo;
- codificação escolhida;
- respostas sem compressão e motivo;
- uso de CPU do processo;
- latência até o primeiro byte e latência total.
Uma resposta que cai de 20 KiB para 5 KiB, mas adiciona 80 ms de CPU num endpoint interno de baixa largura de banda, pode ser uma troca ruim. Já reduzir um JSON público de 500 KiB para 60 KiB provavelmente ajuda usuários móveis, desde que a CPU suporte o volume.
Integre esses sinais aos logs e métricas Prometheus em Zig. Não use labels com URL completa, identificador de usuário ou valores de Accept-Encoding; isso cria cardinalidade alta. Prefira rota normalizada e enumeração curta como gzip, br e identity.
Como testar com curl
O curl pode pedir uma resposta comprimida e descompactá-la para exibição:
curl --compressed -i https://api.exemplo.com.br/relatorio.json
Para inspecionar os bytes gzip sem decodificação automática:
curl -sS \
-H 'Accept-Encoding: gzip' \
-D /tmp/headers.txt \
-o /tmp/resposta.gz \
https://api.exemplo.com.br/relatorio.json
file /tmp/resposta.gz
gzip -t /tmp/resposta.gz
Compare com a resposta original:
curl -sS \
-H 'Accept-Encoding: identity' \
-o /tmp/resposta.json \
https://api.exemplo.com.br/relatorio.json
wc -c /tmp/resposta.json /tmp/resposta.gz
Teste também recusas e variações:
curl -i -H 'Accept-Encoding: gzip;q=0, identity;q=1' URL
curl -i -H 'Accept-Encoding: br, gzip;q=0.5' URL
curl -i -H 'Accept-Encoding: *;q=0' URL
Confirme que:
Content-Encodingcorresponde aos bytes;VarycontémAccept-Encoding;- o JSON decodificado é idêntico semanticamente ao original;
- respostas pequenas e formatos já comprimidos ficam intactos;
204,304eHEADnão ganham corpo indevido;- o proxy não aplica compressão dupla.
Checklist antes do deploy
- O parser respeita pesos
qe recusas explícitas. - Apenas content types permitidos são comprimidos.
- Existe tamanho mínimo baseado em medição.
-
Content-Lengthrepresenta o corpo realmente enviado. - Toda variante negociada inclui
Vary: Accept-Encoding. - ETag e cache distinguem representações corretamente.
- Respostas já comprimidas não passam por outra compressão.
- Endpoints sensíveis foram avaliados contra vazamento por tamanho.
- Streaming possui limites de memória, tempo e concorrência.
-
curl --compressede testes de recusa passam. - Métricas mostram bytes economizados e CPU consumida.
Conclusão
Compressão HTTP em Zig deve ser tratada como uma negociação de representação, não como um botão global de performance. A implementação correta escolhe apenas formatos aceitos pelo cliente, comprime conteúdo que realmente se beneficia, preserva a semântica de cache com Vary e evita custos ou riscos em respostas pequenas, já comprimidas ou sensíveis.
Para a maioria das APIs, configure gzip ou Brotli no proxy e mantenha o serviço Zig simples. Quando a aplicação precisar controlar o fluxo, isole parsing, política e writer de compressão, imponha limites e teste cada variante. O melhor resultado não é o menor arquivo possível: é uma redução mensurável de banda e latência sem transformar CPU, memória ou cache em uma nova fonte de incidentes.