Skip to content

Código de status HTTP · Erros do cliente (4xx)

Erro 412 Precondition Failed

O erro 412 Precondition Failed significa que o pedido trazia uma condição, geralmente If-Match com um ETag, e o recurso no servidor não bate mais com ela. A causa mais comum é outra pessoa ter salvo o mesmo registro entre a sua leitura e a sua gravação.

Dados sobre este código de status
Classe4xx, Erros do cliente
Definido emRFC 9110 §15.5.13
Pode ir para o cache por padrãoSó com Cache-Control ou Expires explícitos
Pode repetir o pedidoNão às cegas; busque a versão atual, reaplique a alteração e repita com o novo ETag
Cabeçalhos relevantes
  • If-Match: cabeçalho do pedido; falha com 412 quando o ETag atual é outro
  • If-Unmodified-Since: cabeçalho do pedido; falha quando o recurso mudou depois dessa data
  • If-None-Match: em PUT ou POST, If-None-Match: * falha com 412 se o recurso já existe
  • ETag: envie o atual junto com o 412 para o cliente se sincronizar

O que significa o 412

A RFC 9110, seção 15.5.13, define o 412 como uma ou mais condições dos cabeçalhos do pedido darem falso no servidor. É o mecanismo por trás do controle de concorrência otimista: você lê o recurso, guarda o ETag e o devolve em If-Match junto com a alteração. Se alguém mudou o recurso no meio tempo, o ETag é outro e o servidor recusa com 412 em vez de sobrescrever o trabalho alheio sem avisar.

A seção 13.2.2 define a ordem de avaliação: primeiro If-Match, depois If-Unmodified-Since, depois If-None-Match. Um If-None-Match que falha em GET ou HEAD nem é erro; vira 304 Not Modified. Nos outros métodos vira 412, e é assim que If-None-Match: * transforma um PUT em "só cria se não existir". As gravações condicionais do Amazon S3 funcionam desse jeito e respondem 412 quando a chave do objeto já existe.

Causas comuns

Se você administra o servidor

  • Duas pessoas, ou duas abas, editaram o mesmo registro; o segundo salvamento leva um ETag que já ficou velho.
  • O cliente guardou o ETag de uma resposta antiga e o reutilizou depois que um processo em segundo plano atualizou o recurso.
  • Um upload do tipo "só cria" (If-None-Match: *) mirou numa chave que já existe, como nas gravações condicionais do S3 ou do Google Cloud Storage.
  • Um proxy ou camada de compressão reescreveu o ETag (o nginx transforma um ETag forte em fraco depois do gzip, por exemplo), então o If-Match nunca bate.

Como resolver

Se você administra o servidor

  • No cliente, trate o 412: faça GET de novo, mescle ou reaplique a alteração do usuário e reenvie com o novo ETag. Mostre um aviso de conflito quando não der para mesclar automaticamente.
  • Inclua o ETag atual na resposta 412 para o cliente saber o quanto está desatualizado.
  • Se o If-Match nunca bate, compare o ETag enviado pelo cliente com o que a origem gera; a comparação forte falha com ETags que começam com W/.

Como enviar um 412

Os exemplos mostram só a resposta. No handler de verdade, compare o cabeçalho If-Match com o ETag armazenado e devolva 412 quando forem diferentes. O http.ServeContent do Go já avalia If-Match e If-Unmodified-Since em GET.

Express (Node.js)
app.put('/documents/:id', (req, res) => {
  res.set('ETag', '"v8"');
  res.status(412).json({ error: 'Document changed since you loaded it' });
});
Next.js App Router (route handler)
// app/documents/[id]/route.ts
export async function PUT() {
  return Response.json(
    { error: 'Document changed since you loaded it' },
    { status: 412, headers: { 'ETag': '"v8"' } }
  );
}
Go net/http
mux.HandleFunc("PUT /documents/{id}", func(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("ETag", "\"v8\"")
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusPreconditionFailed) // 412
	w.Write([]byte(`{"error":"Document changed since you loaded it"}`))
})
Python FastAPI
from fastapi import FastAPI, HTTPException

app = FastAPI()

@app.put("/documents/{id}")
def update_document(id: str):
    raise HTTPException(status_code=412, detail="Document changed since you loaded it", headers={"ETag": "\"v8\""})

Costuma ser confundido com

412 vs 428
O 428 indica que o cliente não mandou pré-condição e o servidor exige uma; o 412 indica que mandou e ela falhou.
412 vs 409
O 409 é um conflito detectado pela lógica da aplicação; o 412 é um conflito detectado só pelos cabeçalhos condicionais do HTTP.
412 vs 304
Um If-None-Match que falha em GET gera 304, sinal de cache válido; num método de escrita a mesma falha gera 412.

Perguntas frequentes

Como o ETag evita que uma alteração sobrescreva outra?
O cliente devolve em If-Match o ETag que leu. O servidor só grava se o ETag atual for idêntico; se não for, responde 412, e um cliente desatualizado não consegue sobrescrever uma versão mais nova.
Devo usar 412 ou 409 para conflito de edição?
Use 412 quando o conflito é detectado por If-Match ou If-Unmodified-Since. Use 409 quando a sua lógica encontra o conflito, por exemplo um número de versão no corpo JSON que não confere.
Por que o S3 devolve 412 Precondition Failed?
O pedido trazia uma condição, como If-None-Match: * no PutObject ou If-Match com um ETag, e o objeto não atendeu: a chave já existia ou tinha mudado.

Revisado em por Arielton Oberek.