Skip to content
This repository was archived by the owner on Oct 21, 2025. It is now read-only.

Commit 0fc5dc4

Browse files
committed
etag headers
1 parent c3a0846 commit 0fc5dc4

File tree

3 files changed

+40
-3
lines changed

3 files changed

+40
-3
lines changed

Cargo.lock

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "api"
3-
version = "2.1.1"
3+
version = "2.1.2"
44
edition = "2024"
55

66
[dependencies]
@@ -23,3 +23,4 @@ reqwest = { version = "0.12.12", default-features = false, features = ["json"] }
2323
nestify = "0.3.3"
2424
sha2 = "0.10.8"
2525
sentry-tower = { version = "0.36.0", features = ["http"] }
26+
sha1 = "0.10.6"

src/main.rs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,16 @@ mod telemetry;
99

1010
use axum::{
1111
body::Body,
12-
http::{HeaderMap, Request, Response, StatusCode},
12+
extract::Request,
13+
http::{HeaderMap, StatusCode},
14+
middleware::Next,
15+
response::Response,
1316
routing::get,
1417
};
1518
use colored::Colorize;
1619
use sentry_tower::SentryHttpLayer;
1720
use serde_json::json;
21+
use sha1::Digest;
1822
use std::{sync::Arc, time::Instant};
1923
use tokio::sync::RwLock;
2024
use tower_http::{catch_panic::CatchPanicLayer, cors::CorsLayer, trace::TraceLayer};
@@ -65,6 +69,36 @@ fn handle_request(req: &Request<Body>, _span: &tracing::Span) {
6569
);
6670
}
6771

72+
async fn handle_etag(req: Request, next: Next) -> Result<Response, StatusCode> {
73+
let if_none_match = req.headers().get("If-None-Match").cloned();
74+
75+
let response = next.run(req).await;
76+
let mut hash = sha1::Sha1::new();
77+
78+
let (mut parts, body) = response.into_parts();
79+
let body_bytes = axum::body::to_bytes(body, usize::MAX).await.unwrap();
80+
81+
hash.update(body_bytes.as_ref());
82+
let hash = format!("{:x}", hash.finalize());
83+
84+
parts.headers.insert("ETag", hash.parse().unwrap());
85+
86+
if if_none_match == Some(hash.parse().unwrap()) {
87+
let mut cached_response = Response::builder()
88+
.status(StatusCode::NOT_MODIFIED)
89+
.body(Body::empty())
90+
.unwrap();
91+
92+
for (key, value) in parts.headers.iter() {
93+
cached_response.headers_mut().insert(key, value.clone());
94+
}
95+
96+
return Ok(cached_response);
97+
}
98+
99+
Ok(Response::from_parts(parts, Body::from(body_bytes)))
100+
}
101+
68102
#[tokio::main]
69103
async fn main() {
70104
let env = env::Env::parse();
@@ -142,6 +176,7 @@ async fn main() {
142176
})),
143177
)
144178
})
179+
.route_layer(axum::middleware::from_fn(handle_etag))
145180
.layer(CatchPanicLayer::custom(handle_panic))
146181
.layer(CorsLayer::very_permissive())
147182
.layer(TraceLayer::new_for_http().on_request(handle_request))

0 commit comments

Comments
 (0)