Skip to main content

Module idempotency

Module idempotency 

Source
Expand description

Idempotency keys for unsafe requests

Networks retry. A client that times out on POST /payments cannot know whether the payment happened, and resending may charge twice. With the Idempotency middleware the client sends an Idempotency-Key header (a random string it reuses for retries of the same operation) and the server runs the handler only once:

  • first request: handler runs, a successful (non-5xx, non-streaming) response is stored
  • retry with the same key and payload: the stored response is replayed with Idempotency-Replayed: true, the handler does not run again
  • retry while the first is still running: 409 Conflict + Retry-After
  • same key, different payload (method, path, query or body): 422
  • a 5xx, a streaming response or a handler that was cancelled releases the key, so a retry can try again

Keys are scoped to the caller: by default to the Authorization and Cookie headers, so one user cannot replay (and read) another user’s response. Change it with Idempotency::scope_by, e.g. to a user id.

use ullun::idempotency::Idempotency;
use ullun::prelude::*;

App::new()
    .middleware(Idempotency::new())
    .post("/payments", |_req: Request| async {
        Ok(Response::json(serde_json::json!({ "charged": true })))
    })
    .run("127.0.0.1:3000")
    .await
    .unwrap();

The default store lives in memory of one process. Implement IdempotencyStore on top of your database or Redis when several instances serve the same clients.

Structs§

Idempotency
Idempotency-key middleware, see the module documentation
MemoryStore
Process-local store, the default
StoredResponse
A response kept for replay

Enums§

Begin
What the store says about a key

Traits§

IdempotencyStore
Where idempotency keys live