rust
50 lines · 8 steps
Optimistic concurrency with ETags in Axum
An Axum handler uses If-Match and ETag headers to reject stale writes and prevent lost updates.
Explained by
highlit
1use axum::{
2 extract::{Path, State},
3 http::{header, HeaderMap, StatusCode},
4 response::{IntoResponse, Response},
5 Json,
6};
7use serde::Deserialize;
8use std::sync::Arc;
9
10#[derive(Deserialize)]
11pub struct UpdateArticle {
12 pub title: String,
13 pub body: String,
14}
15
16pub async fn update_article(
17 State(repo): State<Arc<ArticleRepo>>,
18 Path(id): Path<i64>,
19 headers: HeaderMap,
20 Json(payload): Json<UpdateArticle>,
21) -> Response {
22 let if_match = match headers.get(header::IF_MATCH) {
23 Some(value) => value.to_str().unwrap_or_default().trim_matches('"').to_owned(),
24 None => {
25 return (StatusCode::PRECONDITION_REQUIRED, "If-Match header required").into_response()
26 }
27 };
28
29 let current = match repo.find(id).await {
30 Ok(Some(article)) => article,
31 Ok(None) => return StatusCode::NOT_FOUND.into_response(),
32 Err(_) => return StatusCode::INTERNAL_SERVER_ERROR.into_response(),
33 };
34
35 if current.version.to_string() != if_match {
36 return StatusCode::PRECONDITION_FAILED.into_response();
37 }
38
39 match repo
40 .update_if_version(id, current.version, &payload.title, &payload.body)
41 .await
42 {
43 Ok(Some(updated)) => {
44 let etag = format!("\"{}\"", updated.version);
45 ([(header::ETAG, etag)], Json(updated)).into_response()
46 }
47 Ok(None) => StatusCode::PRECONDITION_FAILED.into_response(),
48 Err(_) => StatusCode::INTERNAL_SERVER_ERROR.into_response(),
49 }
50}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Conditional requests via If-Match let you detect and reject writes based on stale data.
- 2Comparing a version token before and during the update guards against races between the check and the write.
- 3Returning the new ETag on success lets clients chain further conditional updates safely.
Related explainers
rust
use serde::Deserialize; #[derive(Debug, Deserialize)] #[serde(untagged)]
Parsing flexible JSON shapes with serde
deserialization
enums
json
Intermediate
6 steps
ruby
require "shellwords" require "open3" module Backup
Building safe shell commands in Ruby
shell-out
subprocess
command-injection
Intermediate
7 steps
rust
use std::cmp::{Ordering, Reverse}; use std::collections::BinaryHeap; use std::fs::File; use std::io::{self, BufRead, BufReader, BufWriter, Lines, Write};
K-way merge of sorted logs in Rust
binary-heap
k-way-merge
streaming-io
Intermediate
8 steps
go
package api import ( "crypto/sha256"
ETag conditional requests in Gin
http-caching
etag
conditional-requests
Intermediate
6 steps
rust
use axum::{ extract::{Path, State}, response::sse::{Event, KeepAlive, Sse}, };
Streaming import progress with SSE in Axum
server-sent-events
streams
watch-channel
Advanced
7 steps
rust
use std::f64::consts::PI; #[derive(Debug, Clone, Copy)] pub struct LatLng {
Building geographic bounding boxes in Rust
geospatial
value-types
option
Intermediate
7 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/optimistic-concurrency-with-etags-in-axum-explained-rust-7720/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.