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
- Memory
Store - Process-local store, the default
- Stored
Response - A response kept for replay
Enums§
- Begin
- What the store says about a key
Traits§
- Idempotency
Store - Where idempotency keys live