@@ -5,27 +5,36 @@ use crate::runtime_ctx::RuntimeCtx;
5
5
use axum:: body:: Body ;
6
6
use axum:: extract:: { Request , State } ;
7
7
use axum:: handler:: Handler ;
8
- use axum:: middleware:: from_fn;
8
+ use axum:: middleware:: { from_fn, map_response , Next } ;
9
9
use axum:: response:: IntoResponse ;
10
10
use axum:: routing:: { any, any_service, get_service, MethodRouter } ;
11
- use axum:: { Extension , Router } ;
11
+ use axum:: { middleware, Extension , Router } ;
12
+ use axum_extra:: middleware:: option_layer;
12
13
use bsnext_guards:: route_guard:: RouteGuard ;
13
14
use bsnext_guards:: { uri_extension, OuterUri } ;
14
- use bsnext_input:: route:: { ListOrSingle , Route , RouteKind } ;
15
+ use bsnext_input:: route:: { ListOrSingle , ProxyRoute , Route , RouteKind } ;
15
16
use bsnext_input:: when_guard:: { HasGuard , JsonGuard , JsonPropGuard , WhenBodyGuard , WhenGuard } ;
16
17
use bytes:: Bytes ;
17
- use http:: header:: CONTENT_TYPE ;
18
+ use http:: header:: { ACCEPT , CONTENT_TYPE } ;
18
19
use http:: request:: Parts ;
19
20
use http:: uri:: PathAndQuery ;
20
21
use http:: { Method , Response , StatusCode , Uri } ;
21
22
use http_body_util:: BodyExt ;
22
23
use serde_json:: Value ;
23
24
use std:: collections:: HashMap ;
25
+ use std:: ffi:: OsStr ;
26
+ use std:: io;
24
27
use std:: ops:: ControlFlow ;
25
- use std:: path:: PathBuf ;
28
+ use std:: path:: { Path , PathBuf } ;
29
+ use std:: time:: Duration ;
30
+ use tokio:: fs:: { create_dir_all, File } ;
31
+ use tokio:: io:: { AsyncWriteExt , BufWriter } ;
32
+ use tokio_stream:: wrappers:: ReceiverStream ;
33
+ use tokio_stream:: StreamExt ;
26
34
use tower:: ServiceExt ;
35
+ use tower_http:: decompression:: DecompressionLayer ;
27
36
use tower_http:: services:: { ServeDir , ServeFile } ;
28
- use tracing:: { debug, trace, trace_span} ;
37
+ use tracing:: { debug, error , trace, trace_span} ;
29
38
30
39
pub struct RouteMap {
31
40
pub mapping : HashMap < String , Vec < Route > > ,
@@ -136,10 +145,28 @@ pub async fn try_one(
136
145
. when_body
137
146
. as_ref ( )
138
147
. is_some_and ( |body| NeedsJsonGuard ( body) . accept_req ( & req, & outer_uri) ) ;
148
+
149
+ let css_req = req
150
+ . headers ( )
151
+ . get ( ACCEPT )
152
+ . and_then ( |h| h. to_str ( ) . ok ( ) )
153
+ . map ( |c| c. contains ( "text/css" ) )
154
+ . unwrap_or ( false ) ;
155
+
156
+ let js_req = Path :: new ( req. uri ( ) . path ( ) )
157
+ . extension ( )
158
+ . is_some_and ( |ext| ext == OsStr :: new ( "js" ) ) ;
159
+ let mirror = if ( css_req || js_req) {
160
+ RouteHelper ( & route) . mirror ( ) . map ( |v| v. to_path_buf ( ) )
161
+ } else {
162
+ None
163
+ } ;
164
+
139
165
RouteCandidate {
140
166
index,
141
167
consume,
142
168
route,
169
+ mirror,
143
170
}
144
171
} )
145
172
. collect :: < Vec < _ > > ( ) ;
@@ -163,7 +190,18 @@ pub async fn try_one(
163
190
continue ' find_candidates;
164
191
}
165
192
166
- let method_router = to_method_router ( & path, & candidate. route . kind , & ctx) ;
193
+ trace ! ( mirror = ?candidate. mirror) ;
194
+
195
+ let method_router = match & candidate. mirror {
196
+ None => to_method_router ( & path, & candidate. route . kind , & ctx) ,
197
+ Some ( mirror_path) => to_method_router ( & path, & candidate. route . kind , & ctx)
198
+ . layer ( DecompressionLayer :: new ( ) )
199
+ . layer ( middleware:: from_fn_with_state (
200
+ mirror_path. to_owned ( ) ,
201
+ mirror_handler,
202
+ ) ) ,
203
+ } ;
204
+
167
205
let raw_out: MethodRouter = optional_layers ( method_router, & candidate. route . opts ) ;
168
206
let req_clone = match candidate. route . kind {
169
207
RouteKind :: Raw ( _) => Request :: from_parts ( parts. clone ( ) , Body :: empty ( ) ) ,
@@ -177,6 +215,7 @@ pub async fn try_one(
177
215
RouteKind :: Dir ( _) => Request :: from_parts ( parts. clone ( ) , Body :: empty ( ) ) ,
178
216
} ;
179
217
218
+ // MAKE THE REQUEST
180
219
let result = raw_out. oneshot ( req_clone) . await ;
181
220
182
221
match result {
@@ -210,11 +249,25 @@ pub async fn try_one(
210
249
StatusCode :: NOT_FOUND . into_response ( )
211
250
}
212
251
252
+ struct RouteHelper < ' a > ( pub & ' a Route ) ;
253
+
254
+ impl < ' a > RouteHelper < ' a > {
255
+ fn mirror ( & self ) -> Option < & Path > {
256
+ match & self . 0 . kind {
257
+ RouteKind :: Proxy ( ProxyRoute {
258
+ unstable_mirror, ..
259
+ } ) => unstable_mirror. as_ref ( ) . map ( |s| Path :: new ( s) ) ,
260
+ _ => None ,
261
+ }
262
+ }
263
+ }
264
+
213
265
#[ derive( Debug ) ]
214
266
struct RouteCandidate < ' a > {
215
267
index : usize ,
216
268
route : & ' a Route ,
217
269
consume : bool ,
270
+ mirror : Option < PathBuf > ,
218
271
}
219
272
220
273
impl RouteCandidate < ' _ > {
@@ -289,6 +342,52 @@ fn to_method_router(path: &str, route_kind: &RouteKind, ctx: &RuntimeCtx) -> Met
289
342
}
290
343
}
291
344
345
+ async fn mirror_handler (
346
+ State ( path) : State < PathBuf > ,
347
+ req : Request ,
348
+ next : Next ,
349
+ ) -> impl IntoResponse {
350
+ let ( mut sender, receiver) = tokio:: sync:: mpsc:: channel :: < Result < Bytes , io:: Error > > ( 100 ) ;
351
+ let as_stream = ReceiverStream :: from ( receiver) ;
352
+ let c = req. uri ( ) . clone ( ) ;
353
+ let p = path. join ( c. path ( ) . strip_prefix ( "/" ) . unwrap ( ) ) ;
354
+
355
+ let r = next. run ( req) . await ;
356
+ let s = r. into_body ( ) . into_data_stream ( ) ;
357
+
358
+ tokio:: spawn ( async move {
359
+ let s = s. throttle ( Duration :: from_millis ( 10 ) ) ;
360
+ tokio:: pin!( s) ;
361
+ create_dir_all ( & p. parent ( ) . unwrap ( ) ) . await . unwrap ( ) ;
362
+ let mut file = BufWriter :: new ( File :: create ( p) . await . unwrap ( ) ) ;
363
+
364
+ while let Some ( Ok ( b) ) = s. next ( ) . await {
365
+ match file. write ( & b) . await {
366
+ Ok ( _) => { }
367
+ Err ( e) => error ! ( ?e, "could not write" ) ,
368
+ } ;
369
+ // match file.write("\n".as_bytes()).await {
370
+ // Ok(_) => {}
371
+ // Err(e) => error!(?e, "could not new line"),
372
+ // };
373
+ match file. flush ( ) . await {
374
+ Ok ( _) => { }
375
+ Err ( e) => error ! ( ?e, "could not flush" ) ,
376
+ } ;
377
+ match sender. send ( Ok ( b) ) . await {
378
+ Ok ( _) => { }
379
+ Err ( e) => {
380
+ error ! ( ?e, "sender was dropped before reading was finished" ) ;
381
+ error ! ( "will break" ) ;
382
+ break ;
383
+ }
384
+ } ;
385
+ }
386
+ } ) ;
387
+
388
+ Body :: from_stream ( as_stream) . into_response ( )
389
+ }
390
+
292
391
struct QueryHasGuard < ' a > ( pub & ' a HasGuard ) ;
293
392
294
393
impl RouteGuard for QueryHasGuard < ' _ > {
0 commit comments