@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,199 @@
|
|
|
1
|
+
use crate::dto::{ServerChange, ServerChangeSet, ServerChangeSetItem};
|
|
2
|
+
|
|
3
|
+
use crate::server::handler_stop::Stop;
|
|
4
|
+
use actix::{Actor, Addr, Running};
|
|
5
|
+
|
|
6
|
+
use crate::server::actor::ServerActor;
|
|
7
|
+
use crate::servers_supervisor::start_handler::StartMessage;
|
|
8
|
+
|
|
9
|
+
use crate::server::handler_patch::Patch;
|
|
10
|
+
use bsnext_input::server_config::Identity;
|
|
11
|
+
use bsnext_input::Input;
|
|
12
|
+
use std::collections::HashSet;
|
|
13
|
+
use std::future::Future;
|
|
14
|
+
use std::net::SocketAddr;
|
|
15
|
+
|
|
16
|
+
use std::pin::Pin;
|
|
17
|
+
use tokio::sync::oneshot::Sender;
|
|
18
|
+
use tracing::{span, Level};
|
|
19
|
+
|
|
20
|
+
#[derive(Debug)]
|
|
21
|
+
pub struct ServersSupervisor {
|
|
22
|
+
pub(crate) handlers: std::collections::HashMap<Identity, ChildHandler>,
|
|
23
|
+
tx: Option<Sender<()>>,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
impl ServersSupervisor {
|
|
27
|
+
pub fn new(tx: Sender<()>) -> Self {
|
|
28
|
+
Self {
|
|
29
|
+
handlers: std::default::Default::default(),
|
|
30
|
+
tx: Some(tx),
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
pub(crate) fn input_changed(
|
|
35
|
+
&mut self,
|
|
36
|
+
self_addr: Addr<ServersSupervisor>,
|
|
37
|
+
input: Input,
|
|
38
|
+
) -> Pin<Box<impl Future<Output = ServerChangeSet> + Sized>> {
|
|
39
|
+
let span = span!(Level::TRACE, "input_changed");
|
|
40
|
+
let _guard = span.enter();
|
|
41
|
+
|
|
42
|
+
let existing: HashSet<_> = self.handlers.values().map(|v| v.identity.clone()).collect();
|
|
43
|
+
let incoming: HashSet<_> = input.servers.iter().map(|s| s.identity.clone()).collect();
|
|
44
|
+
|
|
45
|
+
let startup = incoming.difference(&existing).collect::<HashSet<_>>();
|
|
46
|
+
let shutdown = existing.difference(&incoming).collect::<HashSet<_>>();
|
|
47
|
+
let patch = existing.intersection(&incoming).collect::<HashSet<_>>();
|
|
48
|
+
|
|
49
|
+
let shutdown_jobs = shutdown
|
|
50
|
+
.into_iter()
|
|
51
|
+
.filter_map(|identity| self.handlers.get(identity).map(ToOwned::to_owned))
|
|
52
|
+
.collect::<Vec<_>>();
|
|
53
|
+
|
|
54
|
+
let start_jobs = startup
|
|
55
|
+
.iter()
|
|
56
|
+
.filter_map(|s| {
|
|
57
|
+
input
|
|
58
|
+
.servers
|
|
59
|
+
.iter()
|
|
60
|
+
.find(|x| x.identity == **s)
|
|
61
|
+
.map(ToOwned::to_owned)
|
|
62
|
+
})
|
|
63
|
+
.collect::<Vec<_>>();
|
|
64
|
+
|
|
65
|
+
let patch_jobs = patch
|
|
66
|
+
.iter()
|
|
67
|
+
.filter_map(|s| {
|
|
68
|
+
let child = self.handlers.get(s).map(ToOwned::to_owned);
|
|
69
|
+
let input = input
|
|
70
|
+
.servers
|
|
71
|
+
.iter()
|
|
72
|
+
.find(|x| x.identity == **s)
|
|
73
|
+
.map(ToOwned::to_owned);
|
|
74
|
+
match (child, input) {
|
|
75
|
+
(Some(child), Some(config)) => Some((child, config)),
|
|
76
|
+
_ => None,
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
.collect::<Vec<_>>();
|
|
80
|
+
|
|
81
|
+
Box::pin(async move {
|
|
82
|
+
let mut changeset = ServerChangeSet { items: vec![] };
|
|
83
|
+
for x in shutdown_jobs {
|
|
84
|
+
tracing::debug!("stopping {:?}", x.identity);
|
|
85
|
+
changeset.items.push(ServerChangeSetItem {
|
|
86
|
+
identity: (&x.identity).into(),
|
|
87
|
+
change: ServerChange::Stopped {
|
|
88
|
+
bind_address: x.socket_addr.to_string(),
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
match x.actor_address.send(Stop).await {
|
|
92
|
+
Ok(_) => {
|
|
93
|
+
self_addr.do_send(ChildStopped {
|
|
94
|
+
identity: x.identity.clone(),
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
Err(_) => {
|
|
98
|
+
tracing::error!("couldn't send Stop2 {:?}", x.identity);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if !start_jobs.is_empty() {
|
|
104
|
+
tracing::debug!("starting {:?} servers", start_jobs.len());
|
|
105
|
+
let idens = start_jobs
|
|
106
|
+
.iter()
|
|
107
|
+
.map(|x| &x.identity)
|
|
108
|
+
.map(|x| x.to_owned())
|
|
109
|
+
.collect::<Vec<_>>();
|
|
110
|
+
match self_addr
|
|
111
|
+
.send(StartMessage {
|
|
112
|
+
server_configs: start_jobs,
|
|
113
|
+
})
|
|
114
|
+
.await
|
|
115
|
+
{
|
|
116
|
+
Ok(_) => {
|
|
117
|
+
for x in idens {
|
|
118
|
+
changeset.items.push(ServerChangeSetItem {
|
|
119
|
+
identity: (&x).into(),
|
|
120
|
+
change: ServerChange::Started,
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
Err(_) => tracing::error!("could not send StartMessage to self"),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
for (child, config) in patch_jobs {
|
|
129
|
+
tracing::debug!("patching {:?}", child.identity);
|
|
130
|
+
child.actor_address.do_send(Patch {
|
|
131
|
+
server_config: config,
|
|
132
|
+
});
|
|
133
|
+
changeset.items.push(ServerChangeSetItem {
|
|
134
|
+
identity: (&child.identity).into(),
|
|
135
|
+
change: ServerChange::Patched,
|
|
136
|
+
})
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
changeset
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
impl Actor for ServersSupervisor {
|
|
145
|
+
type Context = actix::Context<Self>;
|
|
146
|
+
fn started(&mut self, _ctx: &mut Self::Context) {
|
|
147
|
+
tracing::trace!(actor.name = "Servers", actor.lifecyle = "started")
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
fn stopping(&mut self, _ctx: &mut Self::Context) -> Running {
|
|
151
|
+
tracing::trace!(actor.name = "Servers", actor.lifecyle = "stopping");
|
|
152
|
+
Running::Stop
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
fn stopped(&mut self, _ctx: &mut Self::Context) {
|
|
156
|
+
let span = span!(parent: None, Level::TRACE, "stopped", actor.name = "Servers");
|
|
157
|
+
tracing::trace!(actor.lifecyle = "stopping");
|
|
158
|
+
let _guard = span.enter();
|
|
159
|
+
if let Some(tx) = self.tx.take() {
|
|
160
|
+
tracing::trace!("sending final completion");
|
|
161
|
+
match tx.send(()) {
|
|
162
|
+
Ok(_) => {
|
|
163
|
+
tracing::trace!("✅ sent final completion message");
|
|
164
|
+
}
|
|
165
|
+
Err(_) => {
|
|
166
|
+
tracing::trace!("❌ could not send final completion message. This usually means the actor went away (eg: a crash) before we could send this.");
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
} else {
|
|
170
|
+
tracing::error!("could not access oneshot sender for completion message")
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
#[derive(Debug, Clone)]
|
|
176
|
+
pub struct ChildHandler {
|
|
177
|
+
pub actor_address: Addr<ServerActor>,
|
|
178
|
+
pub identity: Identity,
|
|
179
|
+
pub socket_addr: SocketAddr,
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
#[derive(actix::Message)]
|
|
183
|
+
#[rtype(result = "()")]
|
|
184
|
+
pub struct ChildStopped {
|
|
185
|
+
pub identity: Identity,
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
impl actix::Handler<ChildStopped> for ServersSupervisor {
|
|
189
|
+
type Result = ();
|
|
190
|
+
|
|
191
|
+
fn handle(&mut self, msg: ChildStopped, _ctx: &mut Self::Context) -> Self::Result {
|
|
192
|
+
tracing::trace!("Handler<Stopped> for Servers {:?}", msg.identity);
|
|
193
|
+
self.handlers.remove(&msg.identity);
|
|
194
|
+
tracing::trace!(
|
|
195
|
+
"Handler<Stopped> remaining handlers: {}",
|
|
196
|
+
self.handlers.len()
|
|
197
|
+
)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
use crate::server::handler_change::Change;
|
|
2
|
+
use crate::servers_supervisor::actor::ServersSupervisor;
|
|
3
|
+
use std::path::PathBuf;
|
|
4
|
+
|
|
5
|
+
#[derive(actix::Message)]
|
|
6
|
+
#[rtype(result = "()")]
|
|
7
|
+
pub struct FileChanged {
|
|
8
|
+
pub path: PathBuf,
|
|
9
|
+
pub id: u64,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
impl actix::Handler<FileChanged> for ServersSupervisor {
|
|
13
|
+
type Result = ();
|
|
14
|
+
|
|
15
|
+
fn handle(&mut self, msg: FileChanged, _ctx: &mut Self::Context) -> Self::Result {
|
|
16
|
+
for child in self.handlers.values() {
|
|
17
|
+
if child.identity.as_id() == msg.id {
|
|
18
|
+
child.actor_address.do_send(Change::fs(&msg.path))
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
#[derive(actix::Message)]
|
|
25
|
+
#[rtype(result = "()")]
|
|
26
|
+
pub struct FilesChanged {
|
|
27
|
+
pub paths: Vec<PathBuf>,
|
|
28
|
+
pub id: u64,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
impl actix::Handler<FilesChanged> for ServersSupervisor {
|
|
32
|
+
type Result = ();
|
|
33
|
+
|
|
34
|
+
fn handle(&mut self, msg: FilesChanged, _ctx: &mut Self::Context) -> Self::Result {
|
|
35
|
+
for child in self.handlers.values() {
|
|
36
|
+
if child.identity.as_id() == msg.id {
|
|
37
|
+
child.actor_address.do_send(Change::fs_many(&msg.paths))
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
use crate::dto::{GetServersMessageResponse, ServerDTO};
|
|
2
|
+
use crate::servers_supervisor::actor::ServersSupervisor;
|
|
3
|
+
|
|
4
|
+
#[derive(actix::Message)]
|
|
5
|
+
#[rtype(result = "GetServersMessageResponse")]
|
|
6
|
+
pub struct GetServersMessage;
|
|
7
|
+
|
|
8
|
+
impl actix::Handler<GetServersMessage> for ServersSupervisor {
|
|
9
|
+
type Result = GetServersMessageResponse;
|
|
10
|
+
|
|
11
|
+
fn handle(&mut self, _msg: GetServersMessage, _ctx: &mut Self::Context) -> Self::Result {
|
|
12
|
+
GetServersMessageResponse {
|
|
13
|
+
servers: self
|
|
14
|
+
.handlers
|
|
15
|
+
.iter()
|
|
16
|
+
.map(|(identity, child_handler)| ServerDTO {
|
|
17
|
+
id: identity.as_id().to_string(),
|
|
18
|
+
identity: identity.into(),
|
|
19
|
+
socket_addr: child_handler.socket_addr.to_string(),
|
|
20
|
+
})
|
|
21
|
+
.collect(),
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
use crate::dto::ServerChangeSet;
|
|
2
|
+
use crate::servers_supervisor::actor::ServersSupervisor;
|
|
3
|
+
use actix::AsyncContext;
|
|
4
|
+
use bsnext_input::Input;
|
|
5
|
+
use std::future::Future;
|
|
6
|
+
|
|
7
|
+
use std::pin::Pin;
|
|
8
|
+
|
|
9
|
+
#[derive(actix::Message)]
|
|
10
|
+
#[rtype(result = "ServerChangeSet")]
|
|
11
|
+
pub struct InputChanged {
|
|
12
|
+
pub input: Input,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
impl actix::Handler<InputChanged> for ServersSupervisor {
|
|
16
|
+
type Result = Pin<Box<dyn Future<Output = ServerChangeSet>>>;
|
|
17
|
+
|
|
18
|
+
fn handle(&mut self, msg: InputChanged, ctx: &mut Self::Context) -> Self::Result {
|
|
19
|
+
self.input_changed(ctx.address(), msg.input)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
use crate::server::actor::ServerActor;
|
|
2
|
+
use crate::server::handler_listen::Listen;
|
|
3
|
+
use crate::servers_supervisor::actor::{ChildHandler, ServersSupervisor};
|
|
4
|
+
use actix::{Actor, AsyncContext};
|
|
5
|
+
use bsnext_input::server_config::ServerConfig;
|
|
6
|
+
use futures_util::future::join_all;
|
|
7
|
+
use futures_util::FutureExt;
|
|
8
|
+
use std::future::Future;
|
|
9
|
+
use std::pin::Pin;
|
|
10
|
+
use tracing::{span, Instrument, Level};
|
|
11
|
+
|
|
12
|
+
#[derive(actix::Message)]
|
|
13
|
+
#[rtype(result = "()")]
|
|
14
|
+
pub(crate) struct StartMessage {
|
|
15
|
+
pub server_configs: Vec<ServerConfig>,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
impl actix::Handler<StartMessage> for ServersSupervisor {
|
|
19
|
+
type Result = Pin<Box<dyn Future<Output = ()>>>;
|
|
20
|
+
|
|
21
|
+
fn handle(&mut self, msg: StartMessage, ctx: &mut Self::Context) -> Self::Result {
|
|
22
|
+
let span = span!(Level::TRACE, "actix::Handler<StartMessage> for Servers");
|
|
23
|
+
let self_addr = ctx.address();
|
|
24
|
+
|
|
25
|
+
let workload = async move {
|
|
26
|
+
tracing::debug!("creating {} actor(s)", msg.server_configs.len());
|
|
27
|
+
let fts = msg
|
|
28
|
+
.server_configs
|
|
29
|
+
.into_iter()
|
|
30
|
+
.map(|server_config| {
|
|
31
|
+
let server = ServerActor::new_from_config(server_config.clone());
|
|
32
|
+
let actor_addr = server.start();
|
|
33
|
+
let c = server_config.clone();
|
|
34
|
+
actor_addr
|
|
35
|
+
.send(Listen {
|
|
36
|
+
parent: self_addr.clone(),
|
|
37
|
+
})
|
|
38
|
+
.map(|r| (r, c))
|
|
39
|
+
})
|
|
40
|
+
.collect::<Vec<_>>();
|
|
41
|
+
|
|
42
|
+
let results = join_all(fts).await;
|
|
43
|
+
for (fut_result, server_config) in results {
|
|
44
|
+
match fut_result {
|
|
45
|
+
Ok(msg_response) => match msg_response {
|
|
46
|
+
Ok((addr, actor_addr)) => {
|
|
47
|
+
tracing::debug!("✚ got listening child: {}", addr.to_string());
|
|
48
|
+
self_addr.do_send(ChildCreated {
|
|
49
|
+
server_handler: ChildHandler {
|
|
50
|
+
actor_address: actor_addr,
|
|
51
|
+
identity: server_config.identity,
|
|
52
|
+
socket_addr: addr,
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
Err(e) => {
|
|
57
|
+
tracing::error!("{:?} <- {}", server_config.identity, e)
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
Err(e) => tracing::error!(" <- [m] {}", e),
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
.instrument(span);
|
|
65
|
+
|
|
66
|
+
Box::pin(workload)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
#[derive(Debug, actix::Message)]
|
|
71
|
+
#[rtype(result = "()")]
|
|
72
|
+
pub struct ChildCreated {
|
|
73
|
+
server_handler: ChildHandler,
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
impl actix::Handler<ChildCreated> for ServersSupervisor {
|
|
77
|
+
type Result = ();
|
|
78
|
+
|
|
79
|
+
fn handle(&mut self, msg: ChildCreated, _ctx: &mut Self::Context) -> Self::Result {
|
|
80
|
+
self.handlers.insert(
|
|
81
|
+
msg.server_handler.identity.clone(),
|
|
82
|
+
msg.server_handler.clone(),
|
|
83
|
+
);
|
|
84
|
+
tracing::trace!("ChildCreated child count: {}", self.handlers.len());
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
use crate::server::handler_stop::Stop;
|
|
2
|
+
use crate::servers_supervisor::actor::ServersSupervisor;
|
|
3
|
+
use futures_util::future::join_all;
|
|
4
|
+
use std::future::Future;
|
|
5
|
+
use std::pin::Pin;
|
|
6
|
+
|
|
7
|
+
#[derive(actix::Message)]
|
|
8
|
+
#[rtype(result = "()")]
|
|
9
|
+
pub struct StopServers;
|
|
10
|
+
|
|
11
|
+
impl actix::Handler<StopServers> for ServersSupervisor {
|
|
12
|
+
type Result = Pin<Box<dyn Future<Output = ()>>>;
|
|
13
|
+
|
|
14
|
+
fn handle(&mut self, _msg: StopServers, _ctx: &mut Self::Context) -> Self::Result {
|
|
15
|
+
let aaddresses = self.handlers.clone();
|
|
16
|
+
|
|
17
|
+
Box::pin(async move {
|
|
18
|
+
tracing::debug!("stopping {} servers", aaddresses.len());
|
|
19
|
+
let fts = aaddresses
|
|
20
|
+
.values()
|
|
21
|
+
.map(|handler| handler.actor_address.send(Stop))
|
|
22
|
+
.collect::<Vec<_>>();
|
|
23
|
+
join_all(fts).await;
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
use axum::extract::ws::{Message, WebSocket, WebSocketUpgrade};
|
|
2
|
+
use axum::extract::{ConnectInfo, State};
|
|
3
|
+
use axum::response::IntoResponse;
|
|
4
|
+
|
|
5
|
+
use futures_util::{SinkExt, StreamExt};
|
|
6
|
+
|
|
7
|
+
use crate::dto::ClientEvent;
|
|
8
|
+
use crate::server::state::ServerState;
|
|
9
|
+
use std::net::SocketAddr;
|
|
10
|
+
use std::ops::ControlFlow;
|
|
11
|
+
use std::sync::Arc;
|
|
12
|
+
use tokio::sync::broadcast::error::RecvError;
|
|
13
|
+
use tokio::sync::broadcast::Sender;
|
|
14
|
+
|
|
15
|
+
/// The handler for the HTTP request (this gets called when the HTTP GET lands at the start
|
|
16
|
+
/// of websocket negotiation). After this completes, the actual switching from HTTP to
|
|
17
|
+
/// websocket protocol will occur.
|
|
18
|
+
/// This is the last point where we can extract TCP/IP metadata such as IP address of the client
|
|
19
|
+
/// as well as things from HTTP headers such as user-agent of the browser etc.
|
|
20
|
+
pub async fn ws_handler(
|
|
21
|
+
State(state): State<Arc<ServerState>>,
|
|
22
|
+
ws: WebSocketUpgrade,
|
|
23
|
+
ConnectInfo(addr): ConnectInfo<SocketAddr>,
|
|
24
|
+
) -> impl IntoResponse {
|
|
25
|
+
// finalize the upgrade process by returning upgrade callback.
|
|
26
|
+
// we can customize the callback by sending additional info such as address.
|
|
27
|
+
ws.on_upgrade(move |socket| handle_socket(socket, addr, state.client_sender.clone()))
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/// Actual websocket statemachine (one will be spawned per connection)
|
|
31
|
+
async fn handle_socket(
|
|
32
|
+
mut socket: WebSocket,
|
|
33
|
+
who: SocketAddr,
|
|
34
|
+
client_sender: Arc<Sender<ClientEvent>>,
|
|
35
|
+
) {
|
|
36
|
+
//send a ping (unsupported by some browsers) just to kick things off and get a response
|
|
37
|
+
if socket.send(Message::Ping(vec![1, 2, 3])).await.is_ok() {
|
|
38
|
+
tracing::trace!("Pinged {who}...");
|
|
39
|
+
} else {
|
|
40
|
+
tracing::trace!("Could not send ping {who}!");
|
|
41
|
+
// no Error here since the only thing we can do is to close the connection.
|
|
42
|
+
// If we can not send messages, there is no way to salvage the statemachine anyway.
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// receive single message from a client (we can either receive or send with socket).
|
|
47
|
+
// this will likely be the Pong for our Ping or a hello message from client.
|
|
48
|
+
// waiting for message from a client will block this task, but will not block other client's
|
|
49
|
+
// connections.
|
|
50
|
+
if let Some(msg) = socket.recv().await {
|
|
51
|
+
if let Ok(msg) = msg {
|
|
52
|
+
if process_message(msg, who).is_break() {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
} else {
|
|
56
|
+
tracing::debug!("client {who} abruptly disconnected");
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// By splitting socket we can send and receive at the same time. In this example we will send
|
|
62
|
+
// unsolicited messages to client based on some sort of server's internal event (i.e .timer).
|
|
63
|
+
let (mut sender, mut receiver) = socket.split();
|
|
64
|
+
|
|
65
|
+
// Spawn a task that will push several messages to the client (does not matter what client does)
|
|
66
|
+
let mut send_task = tokio::spawn(async move {
|
|
67
|
+
let mut count = 0;
|
|
68
|
+
let mut client_receiver = client_sender.subscribe();
|
|
69
|
+
loop {
|
|
70
|
+
match client_receiver.recv().await {
|
|
71
|
+
Ok(incoming) => {
|
|
72
|
+
tracing::trace!(?incoming, "ws incoming!");
|
|
73
|
+
let output = serde_json::to_string(&incoming);
|
|
74
|
+
let Ok(as_str) = output else {
|
|
75
|
+
tracing::error!("could not serialize incoming client message");
|
|
76
|
+
break;
|
|
77
|
+
};
|
|
78
|
+
if sender.send(Message::Text(as_str)).await.is_err() {
|
|
79
|
+
return count;
|
|
80
|
+
} else {
|
|
81
|
+
count += 1;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
Err(RecvError::Closed) => {
|
|
85
|
+
tracing::debug!("closed!");
|
|
86
|
+
}
|
|
87
|
+
Err(RecvError::Lagged(num)) => {
|
|
88
|
+
tracing::error!(?num, "loagged!");
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
count
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// This second task will receive messages from client and print them on server console
|
|
96
|
+
let mut recv_task = tokio::spawn(async move {
|
|
97
|
+
let mut cnt = 0;
|
|
98
|
+
while let Some(Ok(msg)) = receiver.next().await {
|
|
99
|
+
cnt += 1;
|
|
100
|
+
// print message and break if instructed to do so
|
|
101
|
+
if process_message(msg, who).is_break() {
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
cnt
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// If any one of the tasks exit, abort the other.
|
|
109
|
+
tokio::select! {
|
|
110
|
+
rv_a = (&mut send_task) => {
|
|
111
|
+
match rv_a {
|
|
112
|
+
Ok(a) => tracing::trace!("{a} messages sent to {who}"),
|
|
113
|
+
Err(a) => tracing::trace!("Error sending messages {a:?}")
|
|
114
|
+
}
|
|
115
|
+
recv_task.abort();
|
|
116
|
+
},
|
|
117
|
+
rv_b = (&mut recv_task) => {
|
|
118
|
+
match rv_b {
|
|
119
|
+
Ok(b) => tracing::trace!("Received {b} messages"),
|
|
120
|
+
Err(b) => tracing::trace!("Error receiving messages {b:?}")
|
|
121
|
+
}
|
|
122
|
+
send_task.abort();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// returning from the handler closes the websocket connection
|
|
127
|
+
tracing::trace!("Websocket context {who} destroyed");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/// helper to print contents of messages to stdout. Has special treatment for Close.
|
|
131
|
+
fn process_message(msg: Message, who: SocketAddr) -> ControlFlow<(), ()> {
|
|
132
|
+
match msg {
|
|
133
|
+
Message::Text(t) => {
|
|
134
|
+
tracing::trace!(">>> {who} sent str: {t:?}");
|
|
135
|
+
}
|
|
136
|
+
Message::Binary(d) => {
|
|
137
|
+
tracing::trace!(">>> {} sent {} bytes: {:?}", who, d.len(), d);
|
|
138
|
+
}
|
|
139
|
+
Message::Close(c) => {
|
|
140
|
+
if let Some(cf) = c {
|
|
141
|
+
tracing::trace!(
|
|
142
|
+
">>> {} sent close with code {} and reason `{}`",
|
|
143
|
+
who,
|
|
144
|
+
cf.code,
|
|
145
|
+
cf.reason
|
|
146
|
+
);
|
|
147
|
+
} else {
|
|
148
|
+
tracing::trace!(">>> {who} somehow sent close message without CloseFrame");
|
|
149
|
+
}
|
|
150
|
+
return ControlFlow::Break(());
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
Message::Pong(v) => {
|
|
154
|
+
tracing::trace!(">>> {who} sent pong with {v:?}");
|
|
155
|
+
}
|
|
156
|
+
// You should never need to manually handle Message::Ping, as axum's websocket library
|
|
157
|
+
// will do so for you automagically by replying with Pong and copying the v according to
|
|
158
|
+
// spec. But if you need the contents of the pings you can see them here.
|
|
159
|
+
Message::Ping(v) => {
|
|
160
|
+
tracing::trace!(">>> {who} sent ping with {v:?}");
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
ControlFlow::Continue(())
|
|
164
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "bsnext_example"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
edition = "2021"
|
|
5
|
+
|
|
6
|
+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
7
|
+
|
|
8
|
+
[dependencies]
|
|
9
|
+
bsnext_input = { path = "../bsnext_input" }
|
|
10
|
+
clap = { workspace = true }
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
use bsnext_input::route::{Route, RouteKind};
|
|
2
|
+
use bsnext_input::server_config::Identity;
|
|
3
|
+
use bsnext_input::{
|
|
4
|
+
server_config::{self},
|
|
5
|
+
Input,
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
|
9
|
+
pub struct BasicExample;
|
|
10
|
+
|
|
11
|
+
impl BasicExample {
|
|
12
|
+
pub fn into_input(self, identity: Option<Identity>) -> Input {
|
|
13
|
+
let server = server_config::ServerConfig {
|
|
14
|
+
identity: identity.unwrap_or_else(Identity::named),
|
|
15
|
+
routes: vec![
|
|
16
|
+
Route {
|
|
17
|
+
path: "/".to_string(),
|
|
18
|
+
kind: RouteKind::Html {
|
|
19
|
+
html: include_str!("../../../examples/basic/public/index.html").to_owned(),
|
|
20
|
+
},
|
|
21
|
+
..Default::default()
|
|
22
|
+
},
|
|
23
|
+
Route {
|
|
24
|
+
path: "/styles.css".to_string(),
|
|
25
|
+
kind: RouteKind::Raw {
|
|
26
|
+
raw: include_str!("../../../examples/basic/public/styles.css").to_owned(),
|
|
27
|
+
},
|
|
28
|
+
..Default::default()
|
|
29
|
+
},
|
|
30
|
+
Route {
|
|
31
|
+
path: "/script.js".to_string(),
|
|
32
|
+
kind: RouteKind::Raw {
|
|
33
|
+
raw: include_str!("../../../examples/basic/public/script.js").to_owned(),
|
|
34
|
+
},
|
|
35
|
+
..Default::default()
|
|
36
|
+
},
|
|
37
|
+
Route {
|
|
38
|
+
path: "/reset.css".to_string(),
|
|
39
|
+
kind: RouteKind::Raw {
|
|
40
|
+
raw: include_str!("../../../examples/basic/public/reset.css").to_owned(),
|
|
41
|
+
},
|
|
42
|
+
..Default::default()
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
..Default::default()
|
|
46
|
+
};
|
|
47
|
+
Input {
|
|
48
|
+
servers: vec![server],
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
use crate::basic::BasicExample;
|
|
2
|
+
use crate::lit::LitExample;
|
|
3
|
+
use crate::md::MdExample;
|
|
4
|
+
use bsnext_input::server_config::Identity;
|
|
5
|
+
use bsnext_input::Input;
|
|
6
|
+
|
|
7
|
+
pub mod basic;
|
|
8
|
+
pub mod lit;
|
|
9
|
+
pub mod md;
|
|
10
|
+
|
|
11
|
+
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)]
|
|
12
|
+
pub enum Example {
|
|
13
|
+
Basic,
|
|
14
|
+
Lit,
|
|
15
|
+
Md,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
impl Example {
|
|
19
|
+
pub fn into_input(self, identity: Identity) -> Input {
|
|
20
|
+
match self {
|
|
21
|
+
Example::Basic => BasicExample.into_input(Some(identity)),
|
|
22
|
+
Example::Lit => LitExample.into_input(Some(identity)),
|
|
23
|
+
Example::Md => MdExample.into_input(Some(identity)),
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|