Pular para o conteúdo

Edição concorrente: ETag e If-Match

Duas pessoas editam o mesmo recurso. Sem pré-condição, a segunda apaga a mudança da primeira — calada. Com ela, recebe 412.

Ana abre o pedido 7 e começa a editar. Bia abre o mesmo pedido, muda o endereço e salva. Ana salva em seguida — e o endereço da Bia some. Nenhum erro, nenhum aviso: é a atualização perdida, e ela acontece em toda API que aceita PUT sem pré-condição.

A saída é o controle de concorrência otimista: cada leitura devolve uma etiqueta (ETag) da versão lida; a escrita manda a etiqueta de volta em If-Match; se o recurso mudou nesse meio-tempo, o servidor responde 412 em vez de sobrescrever.

dataforge
adopt Arcane.Kiln as Kiln

pedidos := {"7": {"endereco": "Rua A", "versao": 1}}
app := Kiln.app()

action ler(req):
    p := pedidos[req["params"]["id"]]
    yield Kiln.json(p, 200, {"ETag": Kiln.etiqueta(p["versao"])})

action salvar(req):
    p := pedidos[req["params"]["id"]] ?? void
    atual := void given p is void otherwise Kiln.etiqueta(p["versao"])
    falha := Kiln.precondicao(req, atual, yes)
    given falha is not void:
        yield falha
    p["endereco"] := req["body"]["endereco"]
    p["versao"] += 1
    yield Kiln.json(p, 200, {"ETag": Kiln.etiqueta(p["versao"])})

Kiln.get(app, "/pedidos/:id", ler)
Kiln.put(app, "/pedidos/:id", salvar)

etiqueta_da_ana := Kiln.test(app, "GET", "/pedidos/7")["headers"]["ETag"]
etiqueta_da_bia := Kiln.test(app, "GET", "/pedidos/7")["headers"]["ETag"]

bia := Kiln.test(app, "PUT", "/pedidos/7", {"endereco": "Rua B"}, {"If-Match": etiqueta_da_bia})
assert bia["status"] is 200

ana := Kiln.test(app, "PUT", "/pedidos/7", {"endereco": "Rua C"}, {"If-Match": etiqueta_da_ana})
assert ana["status"] is 412                      // a mudança da Bia não some
assert pedidos["7"]["endereco"] is "Rua B"

sem := Kiln.test(app, "PUT", "/pedidos/7", {"endereco": "Rua D"})
assert sem["status"] is 428                      // exigir := yes

Os quatro casos#

PedidoRespostaUso
If-Match com a etiqueta atualseguea edição normal
If-Match com outra etiqueta412alguém mudou no meio: leia de novo
If-None-Match: *412 se já existecriar com PUT sem sobrescrever
sem cabeçalho, com exigir := yes428a API não aceita escrita às cegas

É a mesma ideia do versao_esperada de um armazém de eventos, só que atravessando HTTP.