---
title: "Compressão HTTP em Zig: gzip, Brotli, Accept-Encoding e Vary"
url: "https://ziglang.com.br/artigos/zig-http-compressao-gzip-brotli/"
markdown_url: "https://ziglang.com.br/artigos/zig-http-compressao-gzip-brotli.MD"
description: "Como usar compressão HTTP em serviços Zig com gzip ou Brotli: negociação por Accept-Encoding, Content-Encoding, Vary, streaming, proxy reverso e testes."
date: "2026-08-16"
author: ""
---

# Compressão HTTP em Zig: gzip, Brotli, Accept-Encoding e Vary

Como usar compressão HTTP em serviços Zig com gzip ou Brotli: negociação por Accept-Encoding, Content-Encoding, Vary, streaming, proxy reverso e testes.


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](/artigos/zig-nginx-proxy-reverso-load-balancing/). Para entender cache e validadores da representação, leia também [ETag, If-None-Match e 304 em Zig](/artigos/zig-http-cache-etag-if-none-match/).

## 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:

```http
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
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:

```http
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:

```zig
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:

```zig
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:

1. ler e validar o header;
2. escolher uma codificação permitida;
3. 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=0` como 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:

```text
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:

```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 `.gz` como 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 é:

```text
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:

```text
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:

```text
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:

```http
Vary: Accept-Encoding
```

Se a resposta já varia por outro campo, combine os valores:

```http
Vary: Origin, Accept-Encoding
```

Não substitua `Vary: Origin` criado pela política de [CORS em Zig](/artigos/zig-cors-api-http/). 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](/artigos/zig-upload-arquivos-validacao-streaming/) 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:

```zig
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/json` e 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](/artigos/zig-observabilidade-logs-prometheus/). 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:

```bash
curl --compressed -i https://api.exemplo.com.br/relatorio.json
```

Para inspecionar os bytes gzip sem decodificação automática:

```bash
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:

```bash
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:

```bash
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-Encoding` corresponde aos bytes;
- `Vary` contém `Accept-Encoding`;
- o JSON decodificado é idêntico semanticamente ao original;
- respostas pequenas e formatos já comprimidos ficam intactos;
- `204`, `304` e `HEAD` não ganham corpo indevido;
- o proxy não aplica compressão dupla.

## Checklist antes do deploy

- [ ] O parser respeita pesos `q` e recusas explícitas.
- [ ] Apenas content types permitidos são comprimidos.
- [ ] Existe tamanho mínimo baseado em medição.
- [ ] `Content-Length` representa 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 --compressed` e 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.
