@browsersync/bslive 0.0.5 → 0.0.9

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.
Files changed (137) hide show
  1. package/Cargo.toml +32 -18
  2. package/bin.js +6 -0
  3. package/bslive/Cargo.toml +35 -0
  4. package/{build.rs → bslive/build.rs} +1 -1
  5. package/bslive/src/lib.rs +130 -0
  6. package/bsnext/Cargo.toml +21 -0
  7. package/bsnext/src/main.rs +110 -0
  8. package/crates/bsnext_client/Cargo.toml +8 -0
  9. package/crates/bsnext_client/build.rs +53 -0
  10. package/crates/bsnext_client/dist/index.js +6493 -0
  11. package/crates/bsnext_client/generated/dto.ts +116 -0
  12. package/crates/bsnext_client/generated/schema.ts +187 -0
  13. package/crates/bsnext_client/index.html +14 -0
  14. package/crates/bsnext_client/package-lock.json +2059 -0
  15. package/crates/bsnext_client/package.json +25 -0
  16. package/crates/bsnext_client/src/lib.rs +1 -0
  17. package/crates/bsnext_client/style.css +3 -0
  18. package/crates/bsnext_client/ts/console.ts +25 -0
  19. package/crates/bsnext_client/ts/index.ts +73 -0
  20. package/crates/bsnext_client/tsconfig.json +16 -0
  21. package/crates/bsnext_core/Cargo.toml +43 -0
  22. package/crates/bsnext_core/src/dir_loader.rs +230 -0
  23. package/crates/bsnext_core/src/dto.rs +281 -0
  24. package/crates/bsnext_core/src/handlers/mod.rs +1 -0
  25. package/crates/bsnext_core/src/handlers/proxy.rs +95 -0
  26. package/crates/bsnext_core/src/lib.rs +11 -0
  27. package/crates/bsnext_core/src/meta/mod.rs +5 -0
  28. package/crates/bsnext_core/src/not_found/mod.rs +2 -0
  29. package/crates/bsnext_core/src/not_found/not_found.html +20 -0
  30. package/crates/bsnext_core/src/not_found/not_found_service.rs +41 -0
  31. package/crates/bsnext_core/src/not_found/route_list.rs +49 -0
  32. package/crates/bsnext_core/src/panic_handler.rs +32 -0
  33. package/crates/bsnext_core/src/raw_loader.rs +226 -0
  34. package/crates/bsnext_core/src/server/actor.rs +92 -0
  35. package/crates/bsnext_core/src/server/error.rs +41 -0
  36. package/crates/bsnext_core/src/server/handler_change.rs +85 -0
  37. package/crates/bsnext_core/src/server/handler_listen.rs +157 -0
  38. package/crates/bsnext_core/src/server/handler_patch.rs +42 -0
  39. package/crates/bsnext_core/src/server/handler_routes_updated.rs +27 -0
  40. package/crates/bsnext_core/src/server/handler_stop.rs +38 -0
  41. package/crates/bsnext_core/src/server/mod.rs +10 -0
  42. package/crates/bsnext_core/src/server/router/mod.rs +112 -0
  43. package/crates/bsnext_core/src/server/router/tests.rs +204 -0
  44. package/crates/bsnext_core/src/server/signals.rs +11 -0
  45. package/crates/bsnext_core/src/server/state.rs +19 -0
  46. package/crates/bsnext_core/src/servers_supervisor/actor.rs +199 -0
  47. package/crates/bsnext_core/src/servers_supervisor/file_changed_handler.rs +41 -0
  48. package/crates/bsnext_core/src/servers_supervisor/get_servers_handler.rs +23 -0
  49. package/crates/bsnext_core/src/servers_supervisor/input_changed_handler.rs +21 -0
  50. package/crates/bsnext_core/src/servers_supervisor/mod.rs +6 -0
  51. package/crates/bsnext_core/src/servers_supervisor/start_handler.rs +82 -0
  52. package/crates/bsnext_core/src/servers_supervisor/stop_handler.rs +26 -0
  53. package/crates/bsnext_core/src/ws/mod.rs +164 -0
  54. package/crates/bsnext_example/Cargo.toml +10 -0
  55. package/crates/bsnext_example/src/basic.rs +51 -0
  56. package/crates/bsnext_example/src/lib.rs +26 -0
  57. package/crates/bsnext_example/src/lit.rs +37 -0
  58. package/crates/bsnext_example/src/md.rs +18 -0
  59. package/crates/bsnext_fs/Cargo.toml +22 -0
  60. package/crates/bsnext_fs/src/actor.rs +122 -0
  61. package/crates/bsnext_fs/src/buffered_debounce.rs +166 -0
  62. package/crates/bsnext_fs/src/filter.rs +30 -0
  63. package/crates/bsnext_fs/src/inner_fs_event_handler.rs +94 -0
  64. package/crates/bsnext_fs/src/lib.rs +141 -0
  65. package/crates/bsnext_fs/src/remove_path_handler.rs +46 -0
  66. package/crates/bsnext_fs/src/stop_handler.rs +15 -0
  67. package/crates/bsnext_fs/src/stream.rs +167 -0
  68. package/crates/bsnext_fs/src/test/mod.rs +213 -0
  69. package/crates/bsnext_fs/src/watch_path_handler.rs +67 -0
  70. package/crates/bsnext_fs/src/watcher.rs +348 -0
  71. package/crates/bsnext_input/Cargo.toml +22 -0
  72. package/crates/bsnext_input/src/input_test/mod.rs +151 -0
  73. package/crates/bsnext_input/src/lib.rs +153 -0
  74. package/crates/bsnext_input/src/md.rs +541 -0
  75. package/crates/bsnext_input/src/paths.rs +64 -0
  76. package/crates/bsnext_input/src/route.rs +185 -0
  77. package/crates/bsnext_input/src/route_manifest.rs +186 -0
  78. package/crates/bsnext_input/src/server_config.rs +88 -0
  79. package/crates/bsnext_input/src/target.rs +7 -0
  80. package/crates/bsnext_input/src/watch_opt_test/mod.rs +68 -0
  81. package/crates/bsnext_input/src/yml.rs +1 -0
  82. package/crates/bsnext_output/Cargo.toml +16 -0
  83. package/crates/bsnext_output/src/json.rs +11 -0
  84. package/crates/bsnext_output/src/lib.rs +25 -0
  85. package/crates/bsnext_output/src/pretty.rs +147 -0
  86. package/crates/bsnext_resp/Cargo.toml +13 -0
  87. package/crates/bsnext_resp/src/js/snippet.html +1 -0
  88. package/crates/bsnext_resp/src/js/ws.js +1 -0
  89. package/crates/bsnext_resp/src/lib.rs +79 -0
  90. package/crates/bsnext_system/Cargo.toml +29 -0
  91. package/crates/bsnext_system/src/args.rs +43 -0
  92. package/crates/bsnext_system/src/lib.rs +227 -0
  93. package/crates/bsnext_system/src/monitor.rs +241 -0
  94. package/crates/bsnext_system/src/monitor_any_watchables.rs +127 -0
  95. package/crates/bsnext_system/src/start_kind/snapshots/bsnext_system__start_kind__start_from_paths__test__test-2.snap +11 -0
  96. package/crates/bsnext_system/src/start_kind/snapshots/bsnext_system__start_kind__start_from_paths__test__test.snap +29 -0
  97. package/crates/bsnext_system/src/start_kind/start_from_example.rs +49 -0
  98. package/crates/bsnext_system/src/start_kind/start_from_inputs.rs +67 -0
  99. package/crates/bsnext_system/src/start_kind/start_from_paths.rs +51 -0
  100. package/crates/bsnext_system/src/start_kind.rs +56 -0
  101. package/crates/bsnext_system/src/startup.rs +51 -0
  102. package/crates/bsnext_tracing/Cargo.toml +10 -0
  103. package/crates/bsnext_tracing/src/lib.rs +89 -0
  104. package/examples/basic/input.yml +5 -0
  105. package/examples/basic/public/index.html +15 -0
  106. package/examples/basic/public/reset.css +52 -0
  107. package/examples/basic/public/script.js +1 -0
  108. package/examples/basic/public/styles.css +3 -0
  109. package/examples/kitchen-sink/a-file.md +1 -0
  110. package/examples/kitchen-sink/api/1.json +1 -0
  111. package/examples/kitchen-sink/api/2.json +3 -0
  112. package/examples/kitchen-sink/app.js +1 -0
  113. package/examples/kitchen-sink/input.html +185 -0
  114. package/examples/kitchen-sink/input.yml +133 -0
  115. package/examples/kitchen-sink/styles-2.css +3 -0
  116. package/examples/lit/index.html +21 -0
  117. package/examples/lit/input.yml +5 -0
  118. package/examples/lit/lit.js +82 -0
  119. package/examples/lit/package-lock.json +62 -0
  120. package/examples/lit/package.json +15 -0
  121. package/examples/md-single/frontmatter.md +35 -0
  122. package/examples/md-single/md-single.md +35 -0
  123. package/examples/openai/.nvm +1 -0
  124. package/examples/openai/build.mjs +21 -0
  125. package/examples/openai/index.html +13 -0
  126. package/examples/openai/input.yml +59 -0
  127. package/examples/openai/package-lock.json +720 -0
  128. package/examples/openai/package.json +21 -0
  129. package/examples/openai/src/index.js +21 -0
  130. package/examples/openai/sse/01.txt +8 -0
  131. package/examples/proxy-simple/input.yml +9 -0
  132. package/examples/single/input.yaml +26 -0
  133. package/index.d.ts +3 -1
  134. package/index.js +3 -2
  135. package/package.json +22 -19
  136. package/run.sh +6 -0
  137. package/src/lib.rs +0 -9
@@ -0,0 +1,95 @@
1
+ use anyhow::Context;
2
+ use axum::body::Body;
3
+ use axum::extract::Request;
4
+ use axum::Extension;
5
+
6
+ use axum::response::{IntoResponse, Response};
7
+
8
+ use http::{HeaderValue, StatusCode, Uri};
9
+ use hyper_tls::HttpsConnector;
10
+ use hyper_util::client::legacy::connect::HttpConnector;
11
+ use hyper_util::client::legacy::Client;
12
+
13
+ #[derive(Debug, Clone)]
14
+ pub struct ProxyConfig {
15
+ pub target: String,
16
+ pub path: String,
17
+ }
18
+
19
+ // Make our own error that wraps `anyhow::Error`.
20
+ pub struct AnyAppError(anyhow::Error);
21
+
22
+ // Tell axum how to convert `AppError` into a response.
23
+ impl IntoResponse for AnyAppError {
24
+ fn into_response(self) -> Response {
25
+ (
26
+ StatusCode::INTERNAL_SERVER_ERROR,
27
+ format!("Something went wrong: {}", self.0),
28
+ )
29
+ .into_response()
30
+ }
31
+ }
32
+ impl<E> From<E> for AnyAppError
33
+ where
34
+ E: Into<anyhow::Error>,
35
+ {
36
+ fn from(err: E) -> Self {
37
+ Self(err.into())
38
+ }
39
+ }
40
+
41
+ pub async fn proxy_handler(
42
+ Extension(config): Extension<ProxyConfig>,
43
+ req: Request,
44
+ ) -> Result<Response, AnyAppError> {
45
+ let client = {
46
+ req.extensions()
47
+ .get::<Client<HttpsConnector<HttpConnector>, Body>>()
48
+ .expect("must have a client, move this to an extractor?")
49
+ };
50
+ let client_c = client.clone();
51
+
52
+ let target = config.target.clone();
53
+
54
+ tracing::trace!(?config);
55
+
56
+ let path = req.uri().path();
57
+
58
+ tracing::trace!(req.uri = %path, config.path = config.path);
59
+ // tracing::trace!(req.headers = ?req.headers());
60
+
61
+ let path_query = req
62
+ .uri()
63
+ .path_and_query()
64
+ .map(|v| v.as_str())
65
+ .unwrap_or(path);
66
+
67
+ let uri = format!("{}{}", target, path_query);
68
+ let parsed = Uri::try_from(uri).context("tried to parse")?;
69
+
70
+ let target = match (parsed.host(), parsed.port()) {
71
+ (Some(host), Some(port)) => format!("{host}:{port}"),
72
+ (Some(host), None) => host.to_owned(),
73
+ _ => unreachable!("could not extract `host` from url"),
74
+ };
75
+
76
+ tracing::trace!(outgoing.uri = %parsed);
77
+
78
+ let host_header_value = HeaderValue::from_str(&target).unwrap();
79
+
80
+ let (parts, body) = req.into_parts();
81
+ let mut req = Request::from_parts(parts, body);
82
+
83
+ *req.uri_mut() = parsed;
84
+
85
+ // todo: Which other headers to mod here?
86
+ req.headers_mut().insert("host", host_header_value);
87
+
88
+ let res = client_c
89
+ .request(req)
90
+ .await
91
+ .map_err(|_| StatusCode::BAD_REQUEST)
92
+ .into_response();
93
+
94
+ Ok(res)
95
+ }
@@ -0,0 +1,11 @@
1
+ pub mod server;
2
+ pub mod servers_supervisor;
3
+
4
+ pub mod dir_loader;
5
+ pub mod dto;
6
+ pub mod handlers;
7
+ pub mod meta;
8
+ pub mod not_found;
9
+ pub mod panic_handler;
10
+ pub mod raw_loader;
11
+ pub mod ws;
@@ -0,0 +1,5 @@
1
+ #[derive(Debug, Clone)]
2
+ pub enum MetaData {
3
+ ServedFile,
4
+ ServedRaw,
5
+ }
@@ -0,0 +1,2 @@
1
+ pub mod not_found_service;
2
+ pub mod route_list;
@@ -0,0 +1,20 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport"
6
+ content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
7
+ <meta http-equiv="X-UA-Compatible" content="ie=edge">
8
+ <title>BSNEXT</title>
9
+ <style>
10
+ pre {
11
+ width: 100%;
12
+ overflow-x: auto;
13
+ }
14
+ </style>
15
+ </head>
16
+ <body>
17
+ <h2>No routes were matched - here's what's available on this address:</h2>
18
+ {{route_list}}
19
+ </body>
20
+ </html>
@@ -0,0 +1,41 @@
1
+ use axum::extract::{Request, State};
2
+ use axum::middleware::Next;
3
+ use std::sync::Arc;
4
+
5
+ use axum::response::{IntoResponse, Response};
6
+
7
+ use http::header::CONTENT_TYPE;
8
+ use http::{HeaderValue, StatusCode};
9
+ use mime_guess::mime;
10
+
11
+ use crate::handlers::proxy::AnyAppError;
12
+ use crate::not_found::route_list::create_route_list_html;
13
+ use crate::server::state::ServerState;
14
+ use tracing::{span, Level};
15
+
16
+ pub async fn not_found_loader(
17
+ State(state): State<Arc<ServerState>>,
18
+ req: Request,
19
+ next: Next,
20
+ ) -> Result<Response, AnyAppError> {
21
+ let span = span!(parent: None, Level::INFO, "not_found", path = req.uri().path());
22
+ let _guard = span.enter();
23
+
24
+ let r = next.run(req).await;
25
+ if r.status().as_u16() != 404 {
26
+ return Ok(r);
27
+ };
28
+
29
+ let routes = state.routes.read().await;
30
+ let markup = create_route_list_html(&routes);
31
+
32
+ Ok((
33
+ StatusCode::NOT_FOUND,
34
+ [(
35
+ CONTENT_TYPE,
36
+ HeaderValue::from_static(mime::TEXT_HTML_UTF_8.as_ref()),
37
+ )],
38
+ markup,
39
+ )
40
+ .into_response())
41
+ }
@@ -0,0 +1,49 @@
1
+ use bsnext_input::route::{DirRoute, ProxyRoute, Route, RouteKind};
2
+ use htmlescape::encode_minimal;
3
+
4
+ pub fn create_route_list_html(routes: &[Route]) -> String {
5
+ let wrapper = include_str!("not_found.html");
6
+ let mut markup = String::from("<table>");
7
+ markup.push_str(
8
+ r##"
9
+ <thead>
10
+ <tr>
11
+ <td>Path</td>
12
+ <td>Kind</td>
13
+ </tr>
14
+ </thead>
15
+ "##,
16
+ );
17
+ for x in routes.iter() {
18
+ let mut item = String::from("<tr>");
19
+ let kind = match &x.kind {
20
+ RouteKind::Html { .. } => String::from("RouteKind::Html"),
21
+ RouteKind::Json { .. } => String::from("RouteKind::Json"),
22
+ RouteKind::Raw { .. } => String::from("RouteKind::Raw"),
23
+ RouteKind::Sse { .. } => String::from("RouteKind::Sse"),
24
+ RouteKind::Proxy(ProxyRoute { proxy }) => {
25
+ format!("RouteKind::Proxy('{}')", proxy)
26
+ }
27
+ RouteKind::Dir(DirRoute { dir }) => {
28
+ format!("RouteKind::Dir('{}')", dir.clone())
29
+ }
30
+ };
31
+ item.push_str(
32
+ format!(
33
+ "<td><a href='{}'><code>{}</code></a></td>\
34
+ \
35
+ <td><small>{}</small></td>",
36
+ x.path, x.path, kind
37
+ )
38
+ .as_str(),
39
+ );
40
+ item.push_str("</tr>");
41
+ markup.push_str(item.as_str());
42
+ }
43
+ markup.push_str("</table>");
44
+ markup.push_str("<pre><code>");
45
+ let json = serde_json::to_string_pretty(routes).expect("serde");
46
+ markup.push_str(&encode_minimal(&json));
47
+ markup.push_str("</pre></code>");
48
+ wrapper.replace("{{route_list}}", markup.as_str())
49
+ }
@@ -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,226 @@
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::{CorsOpts, DelayKind, DelayOpts, DirRoute, ProxyRoute, Route, 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
+ use tokio::time::sleep;
22
+ use tokio_stream::StreamExt;
23
+ use tower::ServiceExt;
24
+ use tower_http::cors::CorsLayer;
25
+ use tracing::{span, Level};
26
+
27
+ // use futures_util::stream::{self, Stream};
28
+
29
+ pub async fn raw_loader(
30
+ State(app): State<Arc<ServerState>>,
31
+ req: Request,
32
+ next: Next,
33
+ ) -> impl IntoResponse {
34
+ let span = span!(parent: None, Level::INFO, "raw_loader", path = req.uri().path());
35
+ let _guard = span.enter();
36
+
37
+ let routes = app.routes.read().await;
38
+ let mut temp_router = matchit::Router::new();
39
+ // let mut app = Router::new();
40
+
41
+ // which route kinds can be hard-matched first
42
+ for route in routes.iter() {
43
+ let path = route.path();
44
+ match &route.kind {
45
+ RouteKind::Html { .. }
46
+ | RouteKind::Json { .. }
47
+ | RouteKind::Raw { .. }
48
+ | RouteKind::Sse { .. } => {
49
+ let existing = temp_router.at_mut(path);
50
+ if let Ok(prev) = existing {
51
+ *prev.value = route.clone();
52
+ tracing::trace!("updated mutable route at {}", path)
53
+ } else if let Err(err) = existing {
54
+ match temp_router.insert(path, route.clone()) {
55
+ Ok(_) => {
56
+ tracing::trace!(path, "+")
57
+ }
58
+ Err(_) => {
59
+ tracing::error!("❌ {:?}", err.to_string())
60
+ }
61
+ }
62
+ }
63
+ }
64
+ _ => {}
65
+ }
66
+ }
67
+
68
+ drop(routes);
69
+
70
+ let matched = temp_router.at(req.uri().path());
71
+
72
+ let Ok(matched) = matched else {
73
+ return next.run(req).await;
74
+ };
75
+
76
+ let route = matched.value;
77
+ let _params = matched.params;
78
+
79
+ let mut app = Router::new();
80
+ match &route.kind {
81
+ RouteKind::Sse { sse } => {
82
+ let raw = sse.to_owned();
83
+ app = app.route(
84
+ req.uri().path(),
85
+ any(|| async move {
86
+ let l = raw
87
+ .lines()
88
+ .map(|l| l.to_owned())
89
+ .map(|l| l.strip_prefix("data:").unwrap_or(&l).to_owned())
90
+ .filter(|l| !l.trim().is_empty())
91
+ .collect::<Vec<_>>();
92
+
93
+ tracing::trace!(lines.count = l.len(), "sending EventStream");
94
+
95
+ let stream = tokio_stream::iter(l)
96
+ .throttle(Duration::from_millis(500))
97
+ .map(|chu| Event::default().data(chu))
98
+ .map(Ok::<_, Infallible>);
99
+
100
+ Sse::new(stream)
101
+ }),
102
+ )
103
+ }
104
+ RouteKind::Raw { raw } => {
105
+ tracing::trace!("-> served Route::Raw {} {} bytes", route.path, raw.len());
106
+ let moved = raw.clone();
107
+ let p = req.uri().path().to_owned();
108
+ app = app.route(
109
+ req.uri().path(),
110
+ any(|| async move { text_asset_response(&p, &moved) }),
111
+ );
112
+ }
113
+ RouteKind::Html { html } => {
114
+ tracing::trace!("-> served Route::Html {} {} bytes", route.path, html.len());
115
+ let moved = html.clone();
116
+ app = app.route(req.uri().path(), any(|| async { Html(moved) }));
117
+ }
118
+ RouteKind::Json { json } => {
119
+ tracing::trace!("-> served Route::Json {} {}", route.path, json);
120
+ let moved = json.to_owned();
121
+ app = app.route(req.uri().path(), any(|| async move { Json(moved) }));
122
+ }
123
+ RouteKind::Dir(DirRoute { dir: _ }) => {
124
+ unreachable!("should never reach RouteKind::Dir")
125
+ }
126
+ RouteKind::Proxy(ProxyRoute { proxy: _ }) => {
127
+ unreachable!("should never reach RouteKind::Proxy")
128
+ }
129
+ }
130
+
131
+ app = add_route_layers(app, route);
132
+
133
+ app.layer(middleware::from_fn(tag_raw))
134
+ .layer(middleware::from_fn(response_modifications_layer))
135
+ .oneshot(req)
136
+ .await
137
+ .into_response()
138
+ }
139
+
140
+ async fn tag_raw(req: Request, next: Next) -> Result<impl IntoResponse, (StatusCode, String)> {
141
+ let (mut parts, body) = next.run(req).await.into_parts();
142
+ if parts.status.as_u16() == 200 {
143
+ parts.extensions.insert(MetaData::ServedRaw);
144
+ }
145
+ Ok(Response::from_parts(parts, body))
146
+ }
147
+
148
+ pub fn add_route_layers(app: Router, route: &Route) -> Router {
149
+ let mut app = app;
150
+
151
+ if route
152
+ .cors_opts
153
+ .as_ref()
154
+ .is_some_and(|v| *v == CorsOpts::Cors(true))
155
+ {
156
+ tracing::trace!(to = route.path, "adding permissive cors");
157
+ app = app.layer(CorsLayer::permissive());
158
+ }
159
+
160
+ if let Some(DelayOpts::Delay(DelayKind::Ms(ms))) = route.delay_opts.as_ref() {
161
+ tracing::trace!(to = route.path, ?ms, "adding delay");
162
+ let ms = *ms;
163
+ app = app.layer(middleware::from_fn(
164
+ move |req: Request, next: Next| async move {
165
+ let res = next.run(req).await;
166
+ sleep(Duration::from_millis(ms)).await;
167
+ Ok::<_, Infallible>(res)
168
+ },
169
+ ));
170
+ }
171
+
172
+ // if route.opts.as_ref().is_some_and(|v| v.buff) {
173
+ // app = app.layer(middleware::from_fn(print_request_response));
174
+ // }
175
+
176
+ app
177
+ }
178
+
179
+ #[allow(dead_code)]
180
+ async fn print_request_response(
181
+ req: Request,
182
+ next: Next,
183
+ ) -> Result<impl IntoResponse, (StatusCode, String)> {
184
+ let res = next.run(req).await;
185
+
186
+ let (parts, body) = res.into_parts();
187
+ let bytes = buffer_and_print("response", body).await?;
188
+ let lines = bytes.chunks(150).map(|c| c.to_owned()).collect::<Vec<_>>();
189
+
190
+ let stream = tokio_stream::iter(lines)
191
+ .throttle(Duration::from_millis(500))
192
+ .map(Ok::<_, Infallible>);
193
+
194
+ let res = Response::from_parts(parts, Body::from_stream(stream));
195
+
196
+ Ok(res)
197
+ }
198
+
199
+ async fn buffer_and_print<B>(direction: &str, body: B) -> Result<Bytes, (StatusCode, String)>
200
+ where
201
+ B: axum::body::HttpBody<Data = Bytes>,
202
+ B::Error: std::fmt::Display,
203
+ {
204
+ let bytes = match body.collect().await {
205
+ Ok(collected) => collected.to_bytes(),
206
+ Err(err) => {
207
+ return Err((
208
+ StatusCode::BAD_REQUEST,
209
+ format!("failed to read {direction} body: {err}"),
210
+ ));
211
+ }
212
+ };
213
+
214
+ if let Ok(body) = std::str::from_utf8(&bytes) {
215
+ tracing::debug!("▶️ {direction} body = {body:?}");
216
+ }
217
+
218
+ Ok(bytes)
219
+ }
220
+
221
+ pub fn text_asset_response(path: &str, css: &str) -> Response {
222
+ let mime = mime_guess::from_path(path);
223
+ let aas_str = mime.first_or_text_plain();
224
+ let cloned = css.to_owned();
225
+ ([(CONTENT_TYPE, aas_str.to_string())], cloned).into_response()
226
+ }
@@ -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
+ }