Skip to content

Commit 5074280

Browse files
erdemgokselerdemgoksel
authored andcommitted
feat: rename control endpoints to match CacheHandle API (v0.2.4)
1 parent 12b5ec1 commit 5074280

File tree

1 file changed

+145
-19
lines changed

1 file changed

+145
-19
lines changed

src/control.rs

Lines changed: 145 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
use crate::cache::CacheHandle;
22
use axum::{
3-
body::Body,
43
extract::State,
5-
http::{header, Request, StatusCode},
4+
http::{header, HeaderMap, StatusCode},
65
response::IntoResponse,
76
routing::post,
8-
Router,
7+
Json, Router,
98
};
9+
use serde::Deserialize;
1010
use std::sync::Arc;
1111

1212
#[derive(Clone)]
@@ -21,46 +21,172 @@ impl ControlState {
2121
}
2222
}
2323

24-
/// Handler for POST /refresh-cache endpoint
25-
async fn refresh_cache_handler(
26-
State(state): State<Arc<ControlState>>,
27-
req: Request<Body>,
28-
) -> Result<impl IntoResponse, StatusCode> {
29-
// Check authorization if auth_token is set
24+
#[derive(Deserialize)]
25+
struct PatternBody {
26+
pattern: String,
27+
}
28+
29+
#[derive(Deserialize)]
30+
struct PathBody {
31+
path: String,
32+
}
33+
34+
/// Returns `Err(UNAUTHORIZED)` when the request lacks a valid Bearer token.
35+
fn check_auth(state: &ControlState, headers: &HeaderMap) -> Result<(), StatusCode> {
3036
if let Some(required_token) = &state.auth_token {
31-
let auth_header = req
32-
.headers()
37+
let auth_header = headers
3338
.get(header::AUTHORIZATION)
3439
.and_then(|h| h.to_str().ok());
35-
3640
let expected = format!("Bearer {}", required_token);
37-
3841
if auth_header != Some(expected.as_str()) {
39-
tracing::warn!("Unauthorized refresh-cache attempt");
42+
tracing::warn!("Unauthorized control endpoint attempt");
4043
return Err(StatusCode::UNAUTHORIZED);
4144
}
4245
}
46+
Ok(())
47+
}
48+
49+
/// POST /invalidate_all — invalidate every cached entry across all servers.
50+
async fn invalidate_all_handler(
51+
State(state): State<Arc<ControlState>>,
52+
headers: HeaderMap,
53+
) -> Result<impl IntoResponse, StatusCode> {
54+
check_auth(&state, &headers)?;
4355

44-
// Trigger cache invalidation on all registered server caches
4556
for handle in &state.handles {
4657
handle.invalidate_all();
4758
}
4859
tracing::info!(
49-
"Cache invalidation triggered via control endpoint ({} server(s))",
60+
"invalidate_all triggered via control endpoint ({} server(s))",
5061
state.handles.len()
5162
);
63+
Ok((StatusCode::OK, "Cache invalidated"))
64+
}
5265

53-
Ok((StatusCode::OK, "Cache refresh triggered"))
66+
/// POST /invalidate — invalidate entries matching a wildcard pattern.
67+
///
68+
/// Body: `{ "pattern": "/api/*" }`
69+
async fn invalidate_handler(
70+
State(state): State<Arc<ControlState>>,
71+
headers: HeaderMap,
72+
Json(body): Json<PatternBody>,
73+
) -> Result<impl IntoResponse, StatusCode> {
74+
check_auth(&state, &headers)?;
75+
76+
for handle in &state.handles {
77+
handle.invalidate(&body.pattern);
78+
}
79+
tracing::info!(
80+
"invalidate('{}') triggered via control endpoint ({} server(s))",
81+
body.pattern,
82+
state.handles.len()
83+
);
84+
Ok((StatusCode::OK, "Pattern invalidation triggered"))
85+
}
86+
87+
/// POST /add_snapshot — fetch a path from upstream, cache it, and track it.
88+
///
89+
/// Only available when the proxy is running in `PreGenerate` mode.
90+
/// Body: `{ "path": "/about" }`
91+
async fn add_snapshot_handler(
92+
State(state): State<Arc<ControlState>>,
93+
headers: HeaderMap,
94+
Json(body): Json<PathBody>,
95+
) -> Result<impl IntoResponse, (StatusCode, String)> {
96+
check_auth(&state, &headers).map_err(|s| (s, String::new()))?;
97+
98+
for handle in &state.handles {
99+
handle
100+
.add_snapshot(&body.path)
101+
.await
102+
.map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?;
103+
}
104+
tracing::info!("add_snapshot('{}') triggered via control endpoint", body.path);
105+
Ok((StatusCode::OK, "Snapshot added".to_string()))
106+
}
107+
108+
/// POST /refresh_snapshot — re-fetch a cached snapshot path from upstream.
109+
///
110+
/// Only available when the proxy is running in `PreGenerate` mode.
111+
/// Body: `{ "path": "/about" }`
112+
async fn refresh_snapshot_handler(
113+
State(state): State<Arc<ControlState>>,
114+
headers: HeaderMap,
115+
Json(body): Json<PathBody>,
116+
) -> Result<impl IntoResponse, (StatusCode, String)> {
117+
check_auth(&state, &headers).map_err(|s| (s, String::new()))?;
118+
119+
for handle in &state.handles {
120+
handle
121+
.refresh_snapshot(&body.path)
122+
.await
123+
.map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?;
124+
}
125+
tracing::info!(
126+
"refresh_snapshot('{}') triggered via control endpoint",
127+
body.path
128+
);
129+
Ok((StatusCode::OK, "Snapshot refreshed".to_string()))
130+
}
131+
132+
/// POST /remove_snapshot — remove a path from the cache and snapshot list.
133+
///
134+
/// Only available when the proxy is running in `PreGenerate` mode.
135+
/// Body: `{ "path": "/about" }`
136+
async fn remove_snapshot_handler(
137+
State(state): State<Arc<ControlState>>,
138+
headers: HeaderMap,
139+
Json(body): Json<PathBody>,
140+
) -> Result<impl IntoResponse, (StatusCode, String)> {
141+
check_auth(&state, &headers).map_err(|s| (s, String::new()))?;
142+
143+
for handle in &state.handles {
144+
handle
145+
.remove_snapshot(&body.path)
146+
.await
147+
.map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?;
148+
}
149+
tracing::info!(
150+
"remove_snapshot('{}') triggered via control endpoint",
151+
body.path
152+
);
153+
Ok((StatusCode::OK, "Snapshot removed".to_string()))
154+
}
155+
156+
/// POST /refresh_all_snapshots — re-fetch every tracked snapshot from upstream.
157+
///
158+
/// Only available when the proxy is running in `PreGenerate` mode.
159+
async fn refresh_all_snapshots_handler(
160+
State(state): State<Arc<ControlState>>,
161+
headers: HeaderMap,
162+
) -> Result<impl IntoResponse, (StatusCode, String)> {
163+
check_auth(&state, &headers).map_err(|s| (s, String::new()))?;
164+
165+
for handle in &state.handles {
166+
handle
167+
.refresh_all_snapshots()
168+
.await
169+
.map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?;
170+
}
171+
tracing::info!(
172+
"refresh_all_snapshots triggered via control endpoint ({} server(s))",
173+
state.handles.len()
174+
);
175+
Ok((StatusCode::OK, "All snapshots refreshed".to_string()))
54176
}
55177

56178
/// Create the control server router.
57179
///
58180
/// `handles` contains one [`CacheHandle`] per named proxy server.
59-
/// A single `/refresh-cache` call invalidates all of them.
60181
pub fn create_control_router(handles: Vec<CacheHandle>, auth_token: Option<String>) -> Router {
61182
let state = Arc::new(ControlState::new(handles, auth_token));
62183

63184
Router::new()
64-
.route("/refresh-cache", post(refresh_cache_handler))
185+
.route("/invalidate_all", post(invalidate_all_handler))
186+
.route("/invalidate", post(invalidate_handler))
187+
.route("/add_snapshot", post(add_snapshot_handler))
188+
.route("/refresh_snapshot", post(refresh_snapshot_handler))
189+
.route("/remove_snapshot", post(remove_snapshot_handler))
190+
.route("/refresh_all_snapshots", post(refresh_all_snapshots_handler))
65191
.with_state(state)
66192
}

0 commit comments

Comments
 (0)