11use crate :: cache:: CacheHandle ;
22use 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 ;
1010use 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.
60181pub 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