Skip to content

Commit 90cf08b

Browse files
committed
unstable mirror
1 parent d89fb16 commit 90cf08b

File tree

5 files changed

+116
-8
lines changed

5 files changed

+116
-8
lines changed

crates/bsnext_core/src/handler_stack.rs

Lines changed: 106 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,36 @@ use crate::runtime_ctx::RuntimeCtx;
55
use axum::body::Body;
66
use axum::extract::{Request, State};
77
use axum::handler::Handler;
8-
use axum::middleware::from_fn;
8+
use axum::middleware::{from_fn, map_response, Next};
99
use axum::response::IntoResponse;
1010
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;
1213
use bsnext_guards::route_guard::RouteGuard;
1314
use bsnext_guards::{uri_extension, OuterUri};
14-
use bsnext_input::route::{ListOrSingle, Route, RouteKind};
15+
use bsnext_input::route::{ListOrSingle, ProxyRoute, Route, RouteKind};
1516
use bsnext_input::when_guard::{HasGuard, JsonGuard, JsonPropGuard, WhenBodyGuard, WhenGuard};
1617
use bytes::Bytes;
17-
use http::header::CONTENT_TYPE;
18+
use http::header::{ACCEPT, CONTENT_TYPE};
1819
use http::request::Parts;
1920
use http::uri::PathAndQuery;
2021
use http::{Method, Response, StatusCode, Uri};
2122
use http_body_util::BodyExt;
2223
use serde_json::Value;
2324
use std::collections::HashMap;
25+
use std::ffi::OsStr;
26+
use std::io;
2427
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;
2634
use tower::ServiceExt;
35+
use tower_http::decompression::DecompressionLayer;
2736
use tower_http::services::{ServeDir, ServeFile};
28-
use tracing::{debug, trace, trace_span};
37+
use tracing::{debug, error, trace, trace_span};
2938

3039
pub struct RouteMap {
3140
pub mapping: HashMap<String, Vec<Route>>,
@@ -136,10 +145,28 @@ pub async fn try_one(
136145
.when_body
137146
.as_ref()
138147
.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+
139165
RouteCandidate {
140166
index,
141167
consume,
142168
route,
169+
mirror,
143170
}
144171
})
145172
.collect::<Vec<_>>();
@@ -163,7 +190,18 @@ pub async fn try_one(
163190
continue 'find_candidates;
164191
}
165192

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+
167205
let raw_out: MethodRouter = optional_layers(method_router, &candidate.route.opts);
168206
let req_clone = match candidate.route.kind {
169207
RouteKind::Raw(_) => Request::from_parts(parts.clone(), Body::empty()),
@@ -177,6 +215,7 @@ pub async fn try_one(
177215
RouteKind::Dir(_) => Request::from_parts(parts.clone(), Body::empty()),
178216
};
179217

218+
// MAKE THE REQUEST
180219
let result = raw_out.oneshot(req_clone).await;
181220

182221
match result {
@@ -210,11 +249,25 @@ pub async fn try_one(
210249
StatusCode::NOT_FOUND.into_response()
211250
}
212251

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+
213265
#[derive(Debug)]
214266
struct RouteCandidate<'a> {
215267
index: usize,
216268
route: &'a Route,
217269
consume: bool,
270+
mirror: Option<PathBuf>,
218271
}
219272

220273
impl RouteCandidate<'_> {
@@ -289,6 +342,52 @@ fn to_method_router(path: &str, route_kind: &RouteKind, ctx: &RuntimeCtx) -> Met
289342
}
290343
}
291344

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+
292391
struct QueryHasGuard<'a>(pub &'a HasGuard);
293392

294393
impl RouteGuard for QueryHasGuard<'_> {

crates/bsnext_dto/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ impl From<RouteKind> for RouteKindDTO {
8585
proxy,
8686
proxy_headers: _outgoing_headers,
8787
rewrite_uri: _rewrite,
88+
..
8889
}) => RouteKindDTO::Proxy { proxy },
8990
RouteKind::Dir(DirRoute { dir, base }) => RouteKindDTO::Dir {
9091
dir,

crates/bsnext_input/src/route.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ impl Route {
125125
proxy: a.as_ref().to_string(),
126126
proxy_headers: None,
127127
rewrite_uri: None,
128+
unstable_mirror: None,
128129
}),
129130
..Default::default()
130131
}
@@ -213,6 +214,12 @@ pub struct ProxyRoute {
213214
pub proxy: String,
214215
pub proxy_headers: Option<BTreeMap<String, String>>,
215216
pub rewrite_uri: Option<bool>,
217+
pub unstable_mirror: Option<String>,
218+
}
219+
220+
#[derive(Debug, PartialEq, Hash, Clone, serde::Deserialize, serde::Serialize)]
221+
struct Mirror {
222+
pub dir: String,
216223
}
217224

218225
#[derive(Debug, PartialEq, Hash, Clone, serde::Deserialize, serde::Serialize)]

crates/bsnext_input/src/route_cli.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ impl TryInto<Route> for RouteCli {
3636
proxy: target,
3737
proxy_headers: None,
3838
rewrite_uri: None,
39+
unstable_mirror: None,
3940
}),
4041
opts: opts_to_route_opts(&opts),
4142
..std::default::Default::default()

crates/bsnext_tracing/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,8 @@ pub enum OtelOption {
7777

7878
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)]
7979
pub enum LogHttp {
80-
#[default]
8180
On,
81+
#[default]
8282
Off,
8383
}
8484

0 commit comments

Comments
 (0)