@browsersync/bslive 0.0.7 → 0.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.toml +31 -2
- package/bin.js +3 -2
- package/bslive/Cargo.toml +16 -1
- package/bslive/build.rs +1 -1
- package/bslive/src/lib.rs +125 -4
- package/bsnext/Cargo.toml +21 -0
- package/bsnext/src/main.rs +116 -0
- package/crates/bsnext_client/Cargo.toml +8 -0
- package/crates/bsnext_client/build.rs +53 -0
- package/crates/bsnext_client/generated/dto.ts +147 -0
- package/crates/bsnext_client/generated/schema.ts +237 -0
- package/crates/bsnext_client/inject/dist/index.js +6540 -0
- package/crates/bsnext_client/inject/package.json +12 -0
- package/crates/bsnext_client/inject/src/console.ts +25 -0
- package/crates/bsnext_client/inject/src/index.ts +73 -0
- package/crates/bsnext_client/package-lock.json +2145 -0
- package/crates/bsnext_client/package.json +27 -0
- package/crates/bsnext_client/src/lib.rs +11 -0
- package/crates/bsnext_client/tsconfig.json +22 -0
- package/crates/bsnext_client/ui/dist/index.css +78 -0
- package/crates/bsnext_client/ui/dist/index.js +997 -0
- package/crates/bsnext_client/ui/index.html +18 -0
- package/crates/bsnext_client/ui/input.yml +19 -0
- package/crates/bsnext_client/ui/package.json +18 -0
- package/crates/bsnext_client/ui/src/components/bs-debug.ts +27 -0
- package/crates/bsnext_client/ui/src/components/bs-header.ts +33 -0
- package/crates/bsnext_client/ui/src/components/bs-icon.ts +101 -0
- package/crates/bsnext_client/ui/src/components/bs-server-detail.ts +21 -0
- package/crates/bsnext_client/ui/src/components/bs-server-identity.ts +24 -0
- package/crates/bsnext_client/ui/src/components/bs-server-list.ts +39 -0
- package/crates/bsnext_client/ui/src/index.ts +39 -0
- package/crates/bsnext_client/ui/styles/base.css.ts +17 -0
- package/crates/bsnext_client/ui/styles/reset.css +52 -0
- package/crates/bsnext_client/ui/styles/style.css +29 -0
- package/crates/bsnext_client/ui/svg/wordmark-white.svg +38 -0
- package/crates/bsnext_core/Cargo.toml +44 -0
- package/crates/bsnext_core/src/common_layers.rs +62 -0
- package/crates/bsnext_core/src/dir_loader.rs +230 -0
- package/crates/bsnext_core/src/dto.rs +341 -0
- package/crates/bsnext_core/src/handlers/mod.rs +1 -0
- package/crates/bsnext_core/src/handlers/proxy.rs +95 -0
- package/crates/bsnext_core/src/lib.rs +12 -0
- package/crates/bsnext_core/src/meta/mod.rs +5 -0
- package/crates/bsnext_core/src/not_found/mod.rs +1 -0
- package/crates/bsnext_core/src/not_found/not_found_service.rs +35 -0
- package/crates/bsnext_core/src/panic_handler.rs +32 -0
- package/crates/bsnext_core/src/raw_loader.rs +196 -0
- package/crates/bsnext_core/src/server/actor.rs +92 -0
- package/crates/bsnext_core/src/server/error.rs +41 -0
- package/crates/bsnext_core/src/server/handler_change.rs +85 -0
- package/crates/bsnext_core/src/server/handler_listen.rs +163 -0
- package/crates/bsnext_core/src/server/handler_patch.rs +42 -0
- package/crates/bsnext_core/src/server/handler_routes_updated.rs +27 -0
- package/crates/bsnext_core/src/server/handler_stop.rs +38 -0
- package/crates/bsnext_core/src/server/mod.rs +10 -0
- package/crates/bsnext_core/src/server/router/assets.rs +39 -0
- package/crates/bsnext_core/src/server/router/mod.rs +123 -0
- package/crates/bsnext_core/src/server/router/pub_api.rs +39 -0
- package/crates/bsnext_core/src/server/router/snapshots/bsnext_core__server__router__tests__test__handlers.snap +9 -0
- package/crates/bsnext_core/src/server/router/tests.rs +209 -0
- package/crates/bsnext_core/src/server/signals.rs +11 -0
- package/crates/bsnext_core/src/server/state.rs +23 -0
- package/crates/bsnext_core/src/servers_supervisor/actor.rs +199 -0
- package/crates/bsnext_core/src/servers_supervisor/file_changed_handler.rs +41 -0
- package/crates/bsnext_core/src/servers_supervisor/get_servers_handler.rs +24 -0
- package/crates/bsnext_core/src/servers_supervisor/input_changed_handler.rs +21 -0
- package/crates/bsnext_core/src/servers_supervisor/mod.rs +6 -0
- package/crates/bsnext_core/src/servers_supervisor/start_handler.rs +86 -0
- package/crates/bsnext_core/src/servers_supervisor/stop_handler.rs +26 -0
- package/crates/bsnext_core/src/ws/mod.rs +164 -0
- package/crates/bsnext_example/Cargo.toml +10 -0
- package/crates/bsnext_example/src/basic.rs +51 -0
- package/crates/bsnext_example/src/lib.rs +26 -0
- package/crates/bsnext_example/src/lit.rs +37 -0
- package/crates/bsnext_example/src/md.rs +18 -0
- package/crates/bsnext_fs/Cargo.toml +22 -0
- package/crates/bsnext_fs/src/actor.rs +123 -0
- package/crates/bsnext_fs/src/buffered_debounce.rs +166 -0
- package/crates/bsnext_fs/src/filter.rs +39 -0
- package/crates/bsnext_fs/src/inner_fs_event_handler.rs +94 -0
- package/crates/bsnext_fs/src/lib.rs +141 -0
- package/crates/bsnext_fs/src/remove_path_handler.rs +46 -0
- package/crates/bsnext_fs/src/stop_handler.rs +15 -0
- package/crates/bsnext_fs/src/stream.rs +167 -0
- package/crates/bsnext_fs/src/test/mod.rs +213 -0
- package/crates/bsnext_fs/src/watch_path_handler.rs +67 -0
- package/crates/bsnext_fs/src/watcher.rs +349 -0
- package/crates/bsnext_input/Cargo.toml +25 -0
- package/crates/bsnext_input/src/input_test/mod.rs +185 -0
- package/crates/bsnext_input/src/input_test/snapshots/bsnext_input__input_test__deserialize_3_headers.snap +29 -0
- package/crates/bsnext_input/src/input_test/snapshots/bsnext_input__input_test__deserialize_3_headers_control.snap +25 -0
- package/crates/bsnext_input/src/lib.rs +153 -0
- package/crates/bsnext_input/src/md.rs +541 -0
- package/crates/bsnext_input/src/paths.rs +64 -0
- package/crates/bsnext_input/src/route.rs +189 -0
- package/crates/bsnext_input/src/route_manifest.rs +186 -0
- package/crates/bsnext_input/src/server_config.rs +88 -0
- package/crates/bsnext_input/src/target.rs +7 -0
- package/crates/bsnext_input/src/watch_opt_test/mod.rs +68 -0
- package/crates/bsnext_input/src/yml.rs +1 -0
- package/crates/bsnext_output/Cargo.toml +16 -0
- package/crates/bsnext_output/src/json.rs +11 -0
- package/crates/bsnext_output/src/lib.rs +25 -0
- package/crates/bsnext_output/src/pretty.rs +147 -0
- package/crates/bsnext_resp/Cargo.toml +13 -0
- package/crates/bsnext_resp/src/js/snippet.html +1 -0
- package/crates/bsnext_resp/src/js/ws.js +1 -0
- package/crates/bsnext_resp/src/lib.rs +79 -0
- package/crates/bsnext_system/Cargo.toml +29 -0
- package/crates/bsnext_system/src/args.rs +47 -0
- package/crates/bsnext_system/src/lib.rs +227 -0
- package/crates/bsnext_system/src/monitor.rs +241 -0
- package/crates/bsnext_system/src/monitor_any_watchables.rs +133 -0
- package/crates/bsnext_system/src/start_kind/snapshots/bsnext_system__start_kind__start_from_paths__test__test-2.snap +12 -0
- package/crates/bsnext_system/src/start_kind/snapshots/bsnext_system__start_kind__start_from_paths__test__test.snap +30 -0
- package/crates/bsnext_system/src/start_kind/start_from_example.rs +49 -0
- package/crates/bsnext_system/src/start_kind/start_from_inputs.rs +67 -0
- package/crates/bsnext_system/src/start_kind/start_from_paths.rs +51 -0
- package/crates/bsnext_system/src/start_kind.rs +56 -0
- package/crates/bsnext_system/src/startup.rs +51 -0
- package/crates/bsnext_tracing/Cargo.toml +10 -0
- package/crates/bsnext_tracing/src/lib.rs +127 -0
- package/examples/basic/input.yml +5 -0
- package/examples/basic/public/index.html +15 -0
- package/examples/basic/public/reset.css +52 -0
- package/examples/basic/public/script.js +1 -0
- package/examples/basic/public/styles.css +3 -0
- package/examples/kitchen-sink/a-file.md +1 -0
- package/examples/kitchen-sink/api/1.json +1 -0
- package/examples/kitchen-sink/api/2.json +3 -0
- package/examples/kitchen-sink/app.js +1 -0
- package/examples/kitchen-sink/input.html +185 -0
- package/examples/kitchen-sink/input.yml +133 -0
- package/examples/kitchen-sink/styles-2.css +3 -0
- package/examples/lit/index.html +21 -0
- package/examples/lit/input.yml +5 -0
- package/examples/lit/lit.js +82 -0
- package/examples/lit/package-lock.json +62 -0
- package/examples/lit/package.json +15 -0
- package/examples/md-single/frontmatter.md +35 -0
- package/examples/md-single/md-single.md +35 -0
- package/examples/openai/.nvm +1 -0
- package/examples/openai/build.mjs +21 -0
- package/examples/openai/index.html +13 -0
- package/examples/openai/input.yml +59 -0
- package/examples/openai/package-lock.json +720 -0
- package/examples/openai/package.json +21 -0
- package/examples/openai/src/index.js +21 -0
- package/examples/openai/sse/01.txt +8 -0
- package/examples/proxy-simple/input.yml +9 -0
- package/examples/single/input.yaml +26 -0
- package/index.d.ts +2 -0
- package/index.js +2 -1
- package/package.json +17 -17
- package/run.sh +6 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
use axum::extract::Request;
|
|
2
|
+
use axum::middleware::Next;
|
|
3
|
+
|
|
4
|
+
use axum::response::{IntoResponse, Response};
|
|
5
|
+
|
|
6
|
+
use http::header::CONTENT_TYPE;
|
|
7
|
+
use http::{HeaderValue, StatusCode};
|
|
8
|
+
use mime_guess::mime;
|
|
9
|
+
|
|
10
|
+
use crate::handlers::proxy::AnyAppError;
|
|
11
|
+
|
|
12
|
+
use bsnext_client::html_with_base;
|
|
13
|
+
use tracing::{span, Level};
|
|
14
|
+
|
|
15
|
+
pub async fn not_found_loader(req: Request, next: Next) -> Result<Response, AnyAppError> {
|
|
16
|
+
let span = span!(parent: None, Level::INFO, "not_found", path = req.uri().path());
|
|
17
|
+
let _guard = span.enter();
|
|
18
|
+
|
|
19
|
+
let r = next.run(req).await;
|
|
20
|
+
if r.status().as_u16() != 404 {
|
|
21
|
+
return Ok(r);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
let markup = html_with_base("/__bs_assets/ui/");
|
|
25
|
+
|
|
26
|
+
Ok((
|
|
27
|
+
StatusCode::NOT_FOUND,
|
|
28
|
+
[(
|
|
29
|
+
CONTENT_TYPE,
|
|
30
|
+
HeaderValue::from_static(mime::TEXT_HTML_UTF_8.as_ref()),
|
|
31
|
+
)],
|
|
32
|
+
markup,
|
|
33
|
+
)
|
|
34
|
+
.into_response())
|
|
35
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
use axum::http::{
|
|
2
|
+
header::{self},
|
|
3
|
+
Response, StatusCode,
|
|
4
|
+
};
|
|
5
|
+
use bytes::Bytes;
|
|
6
|
+
use http_body_util::Full;
|
|
7
|
+
use std::any::Any;
|
|
8
|
+
|
|
9
|
+
pub fn handle_panic(err: Box<dyn Any + Send + 'static>) -> Response<Full<Bytes>> {
|
|
10
|
+
let details = if let Some(s) = err.downcast_ref::<String>() {
|
|
11
|
+
s.clone()
|
|
12
|
+
} else if let Some(s) = err.downcast_ref::<&str>() {
|
|
13
|
+
s.to_string()
|
|
14
|
+
} else {
|
|
15
|
+
"Unknown panic message".to_string()
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
let body = serde_json::json!({
|
|
19
|
+
"error": {
|
|
20
|
+
"kind": "panic",
|
|
21
|
+
"details": details,
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
let body = serde_json::to_string(&body).unwrap();
|
|
26
|
+
|
|
27
|
+
Response::builder()
|
|
28
|
+
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
|
29
|
+
.header(header::CONTENT_TYPE, "application/json")
|
|
30
|
+
.body(Full::from(body))
|
|
31
|
+
.unwrap()
|
|
32
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
use std::convert::Infallible;
|
|
2
|
+
|
|
3
|
+
use axum::extract::{Request, State};
|
|
4
|
+
use axum::middleware::Next;
|
|
5
|
+
use axum::response::{Html, IntoResponse, Response, Sse};
|
|
6
|
+
use axum::routing::any;
|
|
7
|
+
use axum::{middleware, Json, Router};
|
|
8
|
+
use http::header::CONTENT_TYPE;
|
|
9
|
+
|
|
10
|
+
use crate::meta::MetaData;
|
|
11
|
+
use crate::server::state::ServerState;
|
|
12
|
+
use axum::body::Body;
|
|
13
|
+
use axum::response::sse::Event;
|
|
14
|
+
use bsnext_input::route::{DirRoute, ProxyRoute, RouteKind};
|
|
15
|
+
use bsnext_resp::response_modifications_layer;
|
|
16
|
+
use bytes::Bytes;
|
|
17
|
+
use http::StatusCode;
|
|
18
|
+
use http_body_util::BodyExt;
|
|
19
|
+
use std::sync::Arc;
|
|
20
|
+
use std::time::Duration;
|
|
21
|
+
|
|
22
|
+
use tokio_stream::StreamExt;
|
|
23
|
+
use tower::ServiceExt;
|
|
24
|
+
|
|
25
|
+
use crate::common_layers::add_route_layers;
|
|
26
|
+
use tracing::{span, Level};
|
|
27
|
+
|
|
28
|
+
// use futures_util::stream::{self, Stream};
|
|
29
|
+
|
|
30
|
+
pub async fn raw_loader(
|
|
31
|
+
State(app): State<Arc<ServerState>>,
|
|
32
|
+
req: Request,
|
|
33
|
+
next: Next,
|
|
34
|
+
) -> impl IntoResponse {
|
|
35
|
+
let span = span!(parent: None, Level::INFO, "raw_loader", path = req.uri().path());
|
|
36
|
+
let _guard = span.enter();
|
|
37
|
+
|
|
38
|
+
let routes = app.routes.read().await;
|
|
39
|
+
let mut temp_router = matchit::Router::new();
|
|
40
|
+
// let mut app = Router::new();
|
|
41
|
+
|
|
42
|
+
// which route kinds can be hard-matched first
|
|
43
|
+
for route in routes.iter() {
|
|
44
|
+
let path = route.path();
|
|
45
|
+
match &route.kind {
|
|
46
|
+
RouteKind::Html { .. }
|
|
47
|
+
| RouteKind::Json { .. }
|
|
48
|
+
| RouteKind::Raw { .. }
|
|
49
|
+
| RouteKind::Sse { .. } => {
|
|
50
|
+
let existing = temp_router.at_mut(path);
|
|
51
|
+
if let Ok(prev) = existing {
|
|
52
|
+
*prev.value = route.clone();
|
|
53
|
+
tracing::trace!("updated mutable route at {}", path)
|
|
54
|
+
} else if let Err(err) = existing {
|
|
55
|
+
match temp_router.insert(path, route.clone()) {
|
|
56
|
+
Ok(_) => {
|
|
57
|
+
tracing::trace!(path, "+")
|
|
58
|
+
}
|
|
59
|
+
Err(_) => {
|
|
60
|
+
tracing::error!("❌ {:?}", err.to_string())
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
_ => {}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
drop(routes);
|
|
70
|
+
|
|
71
|
+
let matched = temp_router.at(req.uri().path());
|
|
72
|
+
|
|
73
|
+
let Ok(matched) = matched else {
|
|
74
|
+
return next.run(req).await;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
let route = matched.value;
|
|
78
|
+
let _params = matched.params;
|
|
79
|
+
|
|
80
|
+
let mut app = Router::new();
|
|
81
|
+
match &route.kind {
|
|
82
|
+
RouteKind::Sse { sse } => {
|
|
83
|
+
let raw = sse.to_owned();
|
|
84
|
+
app = app.route(
|
|
85
|
+
req.uri().path(),
|
|
86
|
+
any(|| async move {
|
|
87
|
+
let l = raw
|
|
88
|
+
.lines()
|
|
89
|
+
.map(|l| l.to_owned())
|
|
90
|
+
.map(|l| l.strip_prefix("data:").unwrap_or(&l).to_owned())
|
|
91
|
+
.filter(|l| !l.trim().is_empty())
|
|
92
|
+
.collect::<Vec<_>>();
|
|
93
|
+
|
|
94
|
+
tracing::trace!(lines.count = l.len(), "sending EventStream");
|
|
95
|
+
|
|
96
|
+
let stream = tokio_stream::iter(l)
|
|
97
|
+
.throttle(Duration::from_millis(500))
|
|
98
|
+
.map(|chu| Event::default().data(chu))
|
|
99
|
+
.map(Ok::<_, Infallible>);
|
|
100
|
+
|
|
101
|
+
Sse::new(stream)
|
|
102
|
+
}),
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
RouteKind::Raw { raw } => {
|
|
106
|
+
tracing::trace!("-> served Route::Raw {} {} bytes", route.path, raw.len());
|
|
107
|
+
let moved = raw.clone();
|
|
108
|
+
let p = req.uri().path().to_owned();
|
|
109
|
+
app = app.route(
|
|
110
|
+
req.uri().path(),
|
|
111
|
+
any(|| async move { text_asset_response(&p, &moved) }),
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
RouteKind::Html { html } => {
|
|
115
|
+
tracing::trace!("-> served Route::Html {} {} bytes", route.path, html.len());
|
|
116
|
+
let moved = html.clone();
|
|
117
|
+
app = app.route(req.uri().path(), any(|| async { Html(moved) }));
|
|
118
|
+
}
|
|
119
|
+
RouteKind::Json { json } => {
|
|
120
|
+
tracing::trace!("-> served Route::Json {} {}", route.path, json);
|
|
121
|
+
let moved = json.to_owned();
|
|
122
|
+
app = app.route(req.uri().path(), any(|| async move { Json(moved) }));
|
|
123
|
+
}
|
|
124
|
+
RouteKind::Dir(DirRoute { dir: _ }) => {
|
|
125
|
+
unreachable!("should never reach RouteKind::Dir")
|
|
126
|
+
}
|
|
127
|
+
RouteKind::Proxy(ProxyRoute { proxy: _ }) => {
|
|
128
|
+
unreachable!("should never reach RouteKind::Proxy")
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
app = add_route_layers(app, route);
|
|
133
|
+
|
|
134
|
+
app.layer(middleware::from_fn(tag_raw))
|
|
135
|
+
.layer(middleware::from_fn(response_modifications_layer))
|
|
136
|
+
.oneshot(req)
|
|
137
|
+
.await
|
|
138
|
+
.into_response()
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async fn tag_raw(req: Request, next: Next) -> Result<impl IntoResponse, (StatusCode, String)> {
|
|
142
|
+
let (mut parts, body) = next.run(req).await.into_parts();
|
|
143
|
+
if parts.status.as_u16() == 200 {
|
|
144
|
+
parts.extensions.insert(MetaData::ServedRaw);
|
|
145
|
+
}
|
|
146
|
+
Ok(Response::from_parts(parts, body))
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
#[allow(dead_code)]
|
|
150
|
+
async fn print_request_response(
|
|
151
|
+
req: Request,
|
|
152
|
+
next: Next,
|
|
153
|
+
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
|
154
|
+
let res = next.run(req).await;
|
|
155
|
+
|
|
156
|
+
let (parts, body) = res.into_parts();
|
|
157
|
+
let bytes = buffer_and_print("response", body).await?;
|
|
158
|
+
let lines = bytes.chunks(150).map(|c| c.to_owned()).collect::<Vec<_>>();
|
|
159
|
+
|
|
160
|
+
let stream = tokio_stream::iter(lines)
|
|
161
|
+
.throttle(Duration::from_millis(500))
|
|
162
|
+
.map(Ok::<_, Infallible>);
|
|
163
|
+
|
|
164
|
+
let res = Response::from_parts(parts, Body::from_stream(stream));
|
|
165
|
+
|
|
166
|
+
Ok(res)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async fn buffer_and_print<B>(direction: &str, body: B) -> Result<Bytes, (StatusCode, String)>
|
|
170
|
+
where
|
|
171
|
+
B: axum::body::HttpBody<Data = Bytes>,
|
|
172
|
+
B::Error: std::fmt::Display,
|
|
173
|
+
{
|
|
174
|
+
let bytes = match body.collect().await {
|
|
175
|
+
Ok(collected) => collected.to_bytes(),
|
|
176
|
+
Err(err) => {
|
|
177
|
+
return Err((
|
|
178
|
+
StatusCode::BAD_REQUEST,
|
|
179
|
+
format!("failed to read {direction} body: {err}"),
|
|
180
|
+
));
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
if let Ok(body) = std::str::from_utf8(&bytes) {
|
|
185
|
+
tracing::debug!("▶️ {direction} body = {body:?}");
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
Ok(bytes)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
pub fn text_asset_response(path: &str, css: &str) -> Response {
|
|
192
|
+
let mime = mime_guess::from_path(path);
|
|
193
|
+
let aas_str = mime.first_or_text_plain();
|
|
194
|
+
let cloned = css.to_owned();
|
|
195
|
+
([(CONTENT_TYPE, aas_str.to_string())], cloned).into_response()
|
|
196
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
use crate::dto::ClientEvent;
|
|
2
|
+
use crate::server::handler_stop::Stop;
|
|
3
|
+
use crate::server::signals::ServerSignals;
|
|
4
|
+
use crate::server::state::ServerState;
|
|
5
|
+
use actix::{ActorContext, AsyncContext, Running};
|
|
6
|
+
use axum_server::Handle;
|
|
7
|
+
use bsnext_input::route_manifest::RoutesManifest;
|
|
8
|
+
use bsnext_input::server_config::ServerConfig;
|
|
9
|
+
use std::net::SocketAddr;
|
|
10
|
+
use std::sync::Arc;
|
|
11
|
+
use tokio::sync::oneshot::Sender;
|
|
12
|
+
use tokio::sync::{broadcast, oneshot};
|
|
13
|
+
use tracing::{span, Level};
|
|
14
|
+
|
|
15
|
+
pub struct ServerActor {
|
|
16
|
+
pub config: ServerConfig,
|
|
17
|
+
pub routes_manifest: RoutesManifest,
|
|
18
|
+
pub signals: Option<ServerSignals>,
|
|
19
|
+
pub app_state: Option<Arc<ServerState>>,
|
|
20
|
+
pub addr: Option<SocketAddr>,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
impl ServerActor {
|
|
24
|
+
pub fn new_from_config(config: ServerConfig) -> Self {
|
|
25
|
+
let routes_manifest = RoutesManifest::new(&config.routes);
|
|
26
|
+
Self {
|
|
27
|
+
config,
|
|
28
|
+
signals: None,
|
|
29
|
+
app_state: None,
|
|
30
|
+
addr: None,
|
|
31
|
+
routes_manifest,
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
pub fn install_signals(&mut self) -> (Sender<()>, Handle, broadcast::Sender<ClientEvent>) {
|
|
35
|
+
// todo: make this an enum for more messages
|
|
36
|
+
let (client_sender, client_receiver) = broadcast::channel::<ClientEvent>(5);
|
|
37
|
+
let (shutdown_complete, shutdown_complete_receiver) = oneshot::channel();
|
|
38
|
+
let axum_server_handle = Handle::new();
|
|
39
|
+
let axum_server_handle_clone = axum_server_handle.clone();
|
|
40
|
+
|
|
41
|
+
self.signals = Some(ServerSignals {
|
|
42
|
+
complete_mdg_receiver: Some(shutdown_complete_receiver),
|
|
43
|
+
axum_server_handle: Some(axum_server_handle),
|
|
44
|
+
client_sender: Some(client_sender.clone()),
|
|
45
|
+
client_receiver: Some(client_receiver),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
(shutdown_complete, axum_server_handle_clone, client_sender)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
impl actix::Actor for ServerActor {
|
|
53
|
+
type Context = actix::Context<Self>;
|
|
54
|
+
|
|
55
|
+
fn started(&mut self, _ctx: &mut Self::Context) {
|
|
56
|
+
tracing::trace!(actor.name = "ServerActor", actor.lifecyle = "started");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
fn stopping(&mut self, ctx: &mut Self::Context) -> Running {
|
|
60
|
+
let span = span!(
|
|
61
|
+
Level::TRACE,
|
|
62
|
+
"stopping",
|
|
63
|
+
actor.name = "ServerActor",
|
|
64
|
+
actor.lifecyle = "stopping",
|
|
65
|
+
identity = ?self.config.identity
|
|
66
|
+
);
|
|
67
|
+
let _guard = span.enter();
|
|
68
|
+
let addr = ctx.address();
|
|
69
|
+
let s = ctx.state();
|
|
70
|
+
|
|
71
|
+
// this handles crashes. If the server actor crashes we want to ensure we're
|
|
72
|
+
// cleaning up any server we've started. So in a crash we return 'Running::Continue'
|
|
73
|
+
// and send ourselves the `Stop2` message (which will cleanly close the server)
|
|
74
|
+
// `Stop2` will then eventually also try to close this actor, at which point we'll allow it
|
|
75
|
+
if s.alive() {
|
|
76
|
+
tracing::trace!("actor was ❤️, sending `Stop2` to SELF",);
|
|
77
|
+
addr.do_send(Stop);
|
|
78
|
+
Running::Continue
|
|
79
|
+
} else {
|
|
80
|
+
tracing::trace!("Server actor was already 💀");
|
|
81
|
+
Running::Stop
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
fn stopped(&mut self, _ctx: &mut Self::Context) {
|
|
86
|
+
tracing::trace!(
|
|
87
|
+
actor.name = "ServerActor",
|
|
88
|
+
actor.lifecyle = "stopped",
|
|
89
|
+
identity = ?self.config.identity
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
use std::net::SocketAddr;
|
|
2
|
+
use thiserror::Error;
|
|
3
|
+
|
|
4
|
+
// We derive `thiserror::Error`
|
|
5
|
+
#[derive(serde::Serialize, serde::Deserialize, Debug, Error)]
|
|
6
|
+
pub enum ServerError {
|
|
7
|
+
// The `#[from]` attribute generates `From<JsonRejection> for ApiError`
|
|
8
|
+
// implementation. See `thiserror` docs for more information
|
|
9
|
+
#[error("address in use {socket_addr}")]
|
|
10
|
+
AddrInUse { socket_addr: SocketAddr },
|
|
11
|
+
#[error("invalid bind address: {addr_parse_error}")]
|
|
12
|
+
InvalidAddress { addr_parse_error: String },
|
|
13
|
+
#[error("could not determine the reason")]
|
|
14
|
+
Unknown,
|
|
15
|
+
#[error("server was closed")]
|
|
16
|
+
Closed,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
#[test]
|
|
20
|
+
fn test_api_error() {
|
|
21
|
+
#[derive(serde::Serialize, serde::Deserialize, Debug)]
|
|
22
|
+
struct Output {
|
|
23
|
+
error: ServerError,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let e = ServerError::AddrInUse {
|
|
27
|
+
socket_addr: "127.0.0.1:3000".parse().unwrap(),
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
let output = Output { error: e };
|
|
31
|
+
let _as_yaml = serde_yaml::to_string(&output).unwrap();
|
|
32
|
+
let _as_json = serde_json::to_string_pretty(&output).unwrap();
|
|
33
|
+
// println!("{}", as_yaml);
|
|
34
|
+
// println!("{}", as_json);
|
|
35
|
+
|
|
36
|
+
let input = r#"
|
|
37
|
+
error: !AddrInUse
|
|
38
|
+
socket_addr: 127.0.0.1:3000
|
|
39
|
+
"#;
|
|
40
|
+
let _input: Output = serde_yaml::from_str(&input).unwrap();
|
|
41
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
use crate::dto::ClientEvent;
|
|
2
|
+
use crate::server::actor::ServerActor;
|
|
3
|
+
|
|
4
|
+
use std::path::{Path, PathBuf};
|
|
5
|
+
|
|
6
|
+
#[derive(actix::Message, Clone, Debug)]
|
|
7
|
+
#[rtype(result = "()")]
|
|
8
|
+
pub enum Change {
|
|
9
|
+
Fs {
|
|
10
|
+
path: PathBuf,
|
|
11
|
+
change_kind: ChangeKind,
|
|
12
|
+
},
|
|
13
|
+
FsMany(Vec<Change>),
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
#[typeshare::typeshare]
|
|
17
|
+
#[derive(Clone, Debug, serde::Serialize)]
|
|
18
|
+
pub enum ChangeKind {
|
|
19
|
+
Changed,
|
|
20
|
+
Added,
|
|
21
|
+
Removed,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
impl Change {
|
|
25
|
+
pub fn fs<A: AsRef<Path>>(a: A) -> Self {
|
|
26
|
+
Self::Fs {
|
|
27
|
+
path: a.as_ref().to_path_buf(),
|
|
28
|
+
change_kind: ChangeKind::Changed,
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
pub fn fs_many<A: AsRef<Path>>(a: &[A]) -> Self {
|
|
32
|
+
Self::FsMany(
|
|
33
|
+
a.iter()
|
|
34
|
+
.map(|p| p.as_ref().to_owned())
|
|
35
|
+
.map(|p| Self::Fs {
|
|
36
|
+
path: p,
|
|
37
|
+
change_kind: ChangeKind::Changed,
|
|
38
|
+
})
|
|
39
|
+
.collect(),
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
pub fn fs_added<A: AsRef<Path>>(a: A) -> Self {
|
|
43
|
+
Self::Fs {
|
|
44
|
+
path: a.as_ref().to_path_buf(),
|
|
45
|
+
change_kind: ChangeKind::Added,
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
pub fn fs_removed<A: AsRef<Path>>(a: A) -> Self {
|
|
49
|
+
Self::Fs {
|
|
50
|
+
path: a.as_ref().to_path_buf(),
|
|
51
|
+
change_kind: ChangeKind::Removed,
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
impl actix::Handler<Change> for ServerActor {
|
|
57
|
+
type Result = ();
|
|
58
|
+
|
|
59
|
+
fn handle(&mut self, msg: Change, _ctx: &mut Self::Context) -> Self::Result {
|
|
60
|
+
if let Some(sender) = self.signals.as_ref().and_then(|s| s.client_sender.as_ref()) {
|
|
61
|
+
// todo: what messages are the clients expecting?
|
|
62
|
+
tracing::info!("forwarding `Change` event to connected web socket clients");
|
|
63
|
+
match sender.send(ClientEvent::Change((&msg).into())) {
|
|
64
|
+
Ok(_) => {
|
|
65
|
+
tracing::trace!("change event sent to clients");
|
|
66
|
+
}
|
|
67
|
+
Err(_) => tracing::error!("not sent to client_sender"),
|
|
68
|
+
};
|
|
69
|
+
} else {
|
|
70
|
+
tracing::debug!("signals not ready, should they be?");
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
#[cfg(test)]
|
|
75
|
+
mod test {
|
|
76
|
+
use super::*;
|
|
77
|
+
use crate::dto::ChangeDTO;
|
|
78
|
+
#[test]
|
|
79
|
+
fn test_serialize() -> anyhow::Result<()> {
|
|
80
|
+
let fs: ChangeDTO = (&Change::fs("./a.js")).into();
|
|
81
|
+
let json = serde_json::to_string(&fs).unwrap();
|
|
82
|
+
print!("{json}");
|
|
83
|
+
Ok(())
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
use crate::server::actor::ServerActor;
|
|
2
|
+
use crate::server::error::ServerError;
|
|
3
|
+
use crate::server::router::make_router;
|
|
4
|
+
use crate::server::state::ServerState;
|
|
5
|
+
use crate::servers_supervisor::actor::ServersSupervisor;
|
|
6
|
+
use actix::{Addr, AsyncContext};
|
|
7
|
+
use actix_rt::Arbiter;
|
|
8
|
+
use bsnext_input::server_config::Identity;
|
|
9
|
+
use std::future::Future;
|
|
10
|
+
use std::io::ErrorKind;
|
|
11
|
+
use std::net::{SocketAddr, TcpListener};
|
|
12
|
+
use std::pin::Pin;
|
|
13
|
+
use std::sync::Arc;
|
|
14
|
+
use tokio::sync::{oneshot, RwLock};
|
|
15
|
+
|
|
16
|
+
#[derive(actix::Message)]
|
|
17
|
+
#[rtype(result = "Result<(SocketAddr, actix::Addr<ServerActor>), ServerError>")]
|
|
18
|
+
pub struct Listen {
|
|
19
|
+
pub(crate) parent: Addr<ServersSupervisor>,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
impl actix::Handler<Listen> for ServerActor {
|
|
23
|
+
type Result =
|
|
24
|
+
Pin<Box<dyn Future<Output = Result<(SocketAddr, actix::Addr<ServerActor>), ServerError>>>>;
|
|
25
|
+
|
|
26
|
+
fn handle(&mut self, msg: Listen, ctx: &mut Self::Context) -> Self::Result {
|
|
27
|
+
let identity = self.config.identity.clone();
|
|
28
|
+
tracing::trace!("actor started for {:?}", identity);
|
|
29
|
+
let (send_complete, handle, client_sender) = self.install_signals();
|
|
30
|
+
let (oneshot_send, oneshot_rec) = oneshot::channel();
|
|
31
|
+
let h1 = handle.clone();
|
|
32
|
+
let h2 = handle.clone();
|
|
33
|
+
|
|
34
|
+
let app_state = Arc::new(ServerState {
|
|
35
|
+
// parent: ,
|
|
36
|
+
routes: Arc::new(RwLock::new(self.config.routes.clone())),
|
|
37
|
+
id: self.config.identity.as_id(),
|
|
38
|
+
parent: Some(msg.parent.clone()),
|
|
39
|
+
client_sender: Arc::new(client_sender),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
self.app_state = Some(app_state.clone());
|
|
43
|
+
let self_addr = ctx.address();
|
|
44
|
+
|
|
45
|
+
let server = async move {
|
|
46
|
+
let router = make_router(&app_state);
|
|
47
|
+
|
|
48
|
+
let maybe_socket_addr: Result<SocketAddr, _> = match identity {
|
|
49
|
+
Identity::Both {
|
|
50
|
+
ref bind_address, ..
|
|
51
|
+
} => bind_address.parse(),
|
|
52
|
+
Identity::Address { ref bind_address } => bind_address.parse(),
|
|
53
|
+
Identity::Named { .. } => {
|
|
54
|
+
format!("127.0.0.1:{}", get_available_port().expect("port?")).parse()
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
let Ok(socket_addr) = maybe_socket_addr else {
|
|
59
|
+
tracing::error!(
|
|
60
|
+
"{:?} [❌ NOT started] could not parse bind_address",
|
|
61
|
+
identity
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
match oneshot_send.send(Err(ServerError::InvalidAddress {
|
|
65
|
+
addr_parse_error: maybe_socket_addr.unwrap_err().to_string(),
|
|
66
|
+
})) {
|
|
67
|
+
Ok(_) => {}
|
|
68
|
+
Err(_) => tracing::error!("oneshot send failed"),
|
|
69
|
+
}
|
|
70
|
+
return;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
tracing::trace!("trying to listen on {:?}", socket_addr);
|
|
74
|
+
|
|
75
|
+
let server = axum_server::bind(socket_addr)
|
|
76
|
+
.handle(h1)
|
|
77
|
+
.serve(router.into_make_service_with_connect_info::<SocketAddr>());
|
|
78
|
+
|
|
79
|
+
let result: Result<_, ServerError> = match server.await {
|
|
80
|
+
Ok(_) => {
|
|
81
|
+
tracing::debug!("{:?} [started] Server all done", identity);
|
|
82
|
+
if send_complete.send(()).is_err() {
|
|
83
|
+
tracing::error!("{:?} [started] could not send complete message", identity);
|
|
84
|
+
}
|
|
85
|
+
Ok(())
|
|
86
|
+
}
|
|
87
|
+
Err(e) => match e.kind() {
|
|
88
|
+
ErrorKind::AddrInUse => {
|
|
89
|
+
tracing::error!("{:?} [not-started] [AddrInUse] {}", identity, e);
|
|
90
|
+
Err(ServerError::AddrInUse { socket_addr })
|
|
91
|
+
}
|
|
92
|
+
_ => {
|
|
93
|
+
tracing::error!("{:?} [not-started] UNKNOWN {}", identity, e);
|
|
94
|
+
Err(ServerError::Unknown)
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
if !oneshot_send.is_closed() {
|
|
99
|
+
let _r = oneshot_send.send(result);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
Arbiter::current().spawn(server);
|
|
104
|
+
|
|
105
|
+
let self_addr = self_addr.clone();
|
|
106
|
+
|
|
107
|
+
Box::pin(async move {
|
|
108
|
+
tokio::select! {
|
|
109
|
+
listening = h2.listening() => {
|
|
110
|
+
match listening {
|
|
111
|
+
Some(socket_addr) => {
|
|
112
|
+
tracing::debug!("{} listening...", socket_addr);
|
|
113
|
+
Ok((socket_addr, self_addr.clone()))
|
|
114
|
+
}
|
|
115
|
+
None => {
|
|
116
|
+
Err(ServerError::Unknown)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
msg = oneshot_rec => {
|
|
121
|
+
match msg {
|
|
122
|
+
Ok(v) => {
|
|
123
|
+
match v {
|
|
124
|
+
Ok(_) => {
|
|
125
|
+
tracing::info!("All good from one_shot?");
|
|
126
|
+
Err(ServerError::Closed)
|
|
127
|
+
}
|
|
128
|
+
Err(e) => {
|
|
129
|
+
Err(e)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
Err(e) => {
|
|
134
|
+
tracing::error!("-->{e}");
|
|
135
|
+
Err(ServerError::Unknown)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
pub fn get_available_port() -> Option<u16> {
|
|
145
|
+
TcpListener::bind("127.0.0.1:0")
|
|
146
|
+
.and_then(|listener| listener.local_addr())
|
|
147
|
+
.map(|socket_addr| socket_addr.port())
|
|
148
|
+
.ok()
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
#[derive(actix::Message)]
|
|
152
|
+
#[rtype(result = "()")]
|
|
153
|
+
pub struct Listening {
|
|
154
|
+
addr: SocketAddr,
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
impl actix::Handler<Listening> for ServerActor {
|
|
158
|
+
type Result = ();
|
|
159
|
+
|
|
160
|
+
fn handle(&mut self, msg: Listening, _ctx: &mut Self::Context) -> Self::Result {
|
|
161
|
+
self.addr = Some(msg.addr);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
use crate::server::actor::ServerActor;
|
|
2
|
+
use crate::server::handler_routes_updated::RoutesUpdated;
|
|
3
|
+
use actix::AsyncContext;
|
|
4
|
+
use actix_rt::Arbiter;
|
|
5
|
+
use anyhow::anyhow;
|
|
6
|
+
use bsnext_input::route_manifest::RoutesManifest;
|
|
7
|
+
use bsnext_input::server_config::ServerConfig;
|
|
8
|
+
|
|
9
|
+
#[derive(actix::Message, Clone)]
|
|
10
|
+
#[rtype(result = "anyhow::Result<()>")]
|
|
11
|
+
pub struct Patch {
|
|
12
|
+
pub server_config: ServerConfig,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
impl actix::Handler<Patch> for ServerActor {
|
|
16
|
+
type Result = anyhow::Result<()>;
|
|
17
|
+
|
|
18
|
+
fn handle(&mut self, msg: Patch, ctx: &mut Self::Context) -> Self::Result {
|
|
19
|
+
let addr = ctx.address();
|
|
20
|
+
tracing::trace!("Handler<PatchOne> for ServerActor");
|
|
21
|
+
let app_state = self
|
|
22
|
+
.app_state
|
|
23
|
+
.as_ref()
|
|
24
|
+
.ok_or(anyhow!("could not access state"))?;
|
|
25
|
+
let app_state_clone = app_state.clone();
|
|
26
|
+
let routes = msg.server_config.routes.clone();
|
|
27
|
+
let next_manifest = RoutesManifest::new(&routes);
|
|
28
|
+
let changeset = self.routes_manifest.changeset_for(&next_manifest);
|
|
29
|
+
self.routes_manifest = RoutesManifest::new(&routes);
|
|
30
|
+
|
|
31
|
+
let update_dn = async move {
|
|
32
|
+
let mut mut_routes = app_state_clone.routes.write().await;
|
|
33
|
+
*mut_routes = routes;
|
|
34
|
+
addr.do_send(RoutesUpdated {
|
|
35
|
+
change_set: changeset,
|
|
36
|
+
})
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
Arbiter::current().spawn(update_dn);
|
|
40
|
+
Ok(())
|
|
41
|
+
}
|
|
42
|
+
}
|