diff --git a/Cargo.lock b/Cargo.lock index 011c3e5..5c6dd18 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -88,9 +88,11 @@ checksum = "c9e3356844c4d6a6d6467b8da2cffb4a2820be256f50a3a386c9d152bab31043" dependencies = [ "async-trait", "axum-core", + "base64", "bitflags", "bytes", "futures-util", + "headers", "http", "http-body", "hyper", @@ -103,8 +105,10 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", + "sha-1", "sync_wrapper", "tokio", + "tokio-tungstenite", "tower", "tower-http", "tower-layer", @@ -133,12 +137,30 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "block-buffer" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +dependencies = [ + "generic-array", +] + [[package]] name = "boolinator" version = "2.4.0" @@ -172,6 +194,12 @@ version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + [[package]] name = "bytes" version = "1.2.1" @@ -227,12 +255,19 @@ dependencies = [ name = "client" version = "0.1.0" dependencies = [ + "bincode", "console_error_panic_hook", + "futures", + "gloo-net", "gloo-timers", "instant", "log", + "serde", + "tokio", + "wasm-bindgen-futures", "wasm-logger", "yew", + "yew-router", ] [[package]] @@ -245,6 +280,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "cpufeatures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.3.2" @@ -254,6 +298,26 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "flate2" version = "1.0.24" @@ -279,6 +343,21 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f21eda599937fba36daeb58a22e8f5cee2d14c4a17b5b7739c7c8e5e3b8230c" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.24" @@ -286,6 +365,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -294,6 +374,34 @@ version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf" +[[package]] +name = "futures-executor" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ff63c23854bee61b6e9cd331d523909f238fc7636290b96826e9cfa5faa00ab" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbf4d2a7a308fd4578637c0b17c7e1c7ba127b8f6ba00b29f717e9655d85eb68" + +[[package]] +name = "futures-macro" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42cd15d1c7456c04dbdf7e88bcd69760d74f3a798d6444e16974b505b0e62f17" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "futures-sink" version = "0.3.24" @@ -312,13 +420,28 @@ version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90" dependencies = [ + "futures-channel", "futures-core", + "futures-io", + "futures-macro", + "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", "slab", ] +[[package]] +name = "generic-array" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.2.7" @@ -385,12 +508,33 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8d5564e570a38b43d78bdc063374a0c3098c4f0d64005b12f9bbe87e869b6d7" dependencies = [ + "futures-channel", "gloo-events", "js-sys", "wasm-bindgen", "web-sys", ] +[[package]] +name = "gloo-net" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec897194fb9ac576c708f63d35604bc58f2a262b8cec0fabfed26f3991255f21" +dependencies = [ + "futures-channel", + "futures-core", + "futures-sink", + "gloo-utils", + "js-sys", + "pin-project", + "serde", + "serde_json", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "gloo-render" version = "0.1.1" @@ -422,6 +566,8 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fb7d06c1c8cc2a29bee7ec961009a0b2caa0793ee4900c2ffb348734ba1c8f9" dependencies = [ + "futures-channel", + "futures-core", "js-sys", "wasm-bindgen", ] @@ -445,6 +591,31 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "headers" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" +dependencies = [ + "base64", + "bitflags", + "bytes", + "headers-core", + "http", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http", +] + [[package]] name = "heck" version = "0.4.0" @@ -523,6 +694,16 @@ dependencies = [ "want", ] +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "indexmap" version = "1.9.1" @@ -597,7 +778,10 @@ version = "0.1.0" dependencies = [ "anyhow", "axum", + "bincode", "clap", + "client", + "headers", "log", "tokio", "tower", @@ -762,6 +946,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -804,6 +994,36 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "redox_syscall" version = "0.2.16" @@ -813,6 +1033,12 @@ dependencies = [ "bitflags", ] +[[package]] +name = "route-recognizer" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afab94fb28594581f62d981211a9a4d53cc8130bbcbbb89a0440d9b8e81a7746" + [[package]] name = "ryu" version = "1.0.11" @@ -840,6 +1066,18 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-wasm-bindgen" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "618365e8e586c22123d692b72a7d791d5ee697817b65a218cdf12a98870af0f7" +dependencies = [ + "fnv", + "js-sys", + "serde", + "wasm-bindgen", +] + [[package]] name = "serde_derive" version = "1.0.144" @@ -874,6 +1112,28 @@ dependencies = [ "serde", ] +[[package]] +name = "sha-1" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha1" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sharded-slab" version = "0.1.4" @@ -984,6 +1244,21 @@ dependencies = [ "once_cell", ] +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + [[package]] name = "tokio" version = "1.21.1" @@ -1016,6 +1291,18 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-tungstenite" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f714dd15bead90401d77e04243611caec13726c2408afd5b31901dfcdcb3b181" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", +] + [[package]] name = "tokio-util" version = "0.7.4" @@ -1140,6 +1427,31 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" +[[package]] +name = "tungstenite" +version = "0.17.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" +dependencies = [ + "base64", + "byteorder", + "bytes", + "http", + "httparse", + "log", + "rand", + "sha-1", + "thiserror", + "url", + "utf-8", +] + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + [[package]] name = "unicase" version = "2.6.0" @@ -1149,12 +1461,44 @@ dependencies = [ "version_check", ] +[[package]] +name = "unicode-bidi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + [[package]] name = "unicode-ident" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "url" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "uuid" version = "1.1.2" @@ -1199,6 +1543,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" dependencies = [ "cfg-if", + "serde", + "serde_json", "wasm-bindgen-macro", ] @@ -1385,3 +1731,34 @@ dependencies = [ "quote", "syn", ] + +[[package]] +name = "yew-router" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "155804f6f3aa309f596d5c3fa14486a94e7756f1edd7634569949e401d5099f2" +dependencies = [ + "gloo", + "gloo-utils", + "js-sys", + "route-recognizer", + "serde", + "serde-wasm-bindgen", + "serde_urlencoded", + "thiserror", + "wasm-bindgen", + "web-sys", + "yew", + "yew-router-macro", +] + +[[package]] +name = "yew-router-macro" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39049d193b52eaad4ffc80916bf08806d142c90b5edcebd527644de438a7e19a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/client/Cargo.toml b/client/Cargo.toml index 5a52261..acf84f1 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -14,5 +14,12 @@ yew = "0.19" console_error_panic_hook = "0.1" log = "0.4" wasm-logger = "0.2" +futures = "0.3" gloo-timers = "0.2" +gloo-net = "0.2" instant = { version = "0.1", features = [ "wasm-bindgen" ] } +wasm-bindgen-futures = "0.4" +bincode = "1" +yew-router = "0.16" +serde = { version = "1", features = ["derive"] } +tokio = { version = "1", default-features = false, features = ["sync"] } diff --git a/client/src/lib.rs b/client/src/lib.rs new file mode 100644 index 0000000..25cc1e1 --- /dev/null +++ b/client/src/lib.rs @@ -0,0 +1,9 @@ +use serde::{Deserialize, Serialize}; +use std::time::Duration; + +#[derive(Debug, Deserialize, Serialize)] +pub enum Message { + Start, + Stop, + Duration(Duration), +} diff --git a/client/src/main.rs b/client/src/main.rs index 670d99a..c4e79df 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -1,6 +1,58 @@ +use futures::{ + stream::{SplitSink, SplitStream}, + SinkExt, StreamExt, +}; +use gloo_net::websocket::{futures::WebSocket, Message}; use gloo_timers::callback::Interval; use instant::Instant; +use log::debug; +use std::sync::Arc; +use tokio::sync::Mutex; +use wasm_bindgen_futures::spawn_local; use yew::prelude::*; +use yew_router::prelude::*; + +#[derive(Clone, Debug, Eq, PartialEq, Routable)] +pub enum Route { + #[at("/")] + LockWatch, + #[not_found] + #[at("/404")] + NotFound, +} + +struct WebSocketHandle { + writer: Arc>>, + _reader: Arc>>, +} + +impl WebSocketHandle { + fn connect(url: &str) -> Self { + debug!("connecting to websocket `{}`", url); + let ws = WebSocket::open(url) + .unwrap_or_else(|_| panic!("could not connect to websocket `{}`", url)); + let (writer, reader) = ws.split(); + + Self { + writer: Arc::new(Mutex::new(writer)), + _reader: Arc::new(Mutex::new(reader)), + } + } + + fn send(&self, msg: client::Message) { + let writer = self.writer.clone(); + spawn_local(async move { + writer + .lock() + .await + .send(Message::Bytes( + bincode::serialize(&msg).expect("could not serialize websocket message"), + )) + .await + .expect("could not send websocket message"); + }); + } +} enum Msg { Start, @@ -9,6 +61,7 @@ enum Msg { } struct LockWatch { + websocket: WebSocketHandle, duration: Option, duration_handle: Option, } @@ -38,6 +91,7 @@ impl Component for LockWatch { fn create(_ctx: &Context) -> Self { Self { + websocket: WebSocketHandle::connect("ws://localhost:3000/api/ws"), duration: None, duration_handle: None, } @@ -47,6 +101,8 @@ impl Component for LockWatch { match msg { Msg::Start => { self.duration = Some(Instant::now()); + self.websocket.send(client::Message::Start); + self.duration_handle = { let link = ctx.link().clone(); Some(Interval::new(1, move || { @@ -54,7 +110,12 @@ impl Component for LockWatch { })) }; } - Msg::Stop => self.duration_handle = None, + Msg::Stop => { + self.duration_handle = None; + self.websocket.send(client::Message::Stop); + let duration = self.duration.expect("could not get duration").elapsed(); + self.websocket.send(client::Message::Duration(duration)); + } Msg::UpdateDuration => {} } diff --git a/lockwatch/Cargo.toml b/lockwatch/Cargo.toml index 9fc2534..a341f34 100644 --- a/lockwatch/Cargo.toml +++ b/lockwatch/Cargo.toml @@ -12,9 +12,12 @@ categories = ["web-programming::http-server"] [dependencies] anyhow = "1" log = "0.4" -axum = "0.5" +axum = { version = "0.5", features = ["ws", "headers"] } tokio = { version = "1.0", features = ["full"] } tracing-subscriber = "0.3" tower = "0.4" tower-http = { version = "0.3", features = ["full"] } clap = { version = "3", features = ["derive"] } +headers = "0.3" +bincode = "1" +client = { path = "../client" } diff --git a/lockwatch/src/main.rs b/lockwatch/src/main.rs index 6b2538a..7c9e223 100644 --- a/lockwatch/src/main.rs +++ b/lockwatch/src/main.rs @@ -1,16 +1,22 @@ use anyhow::Result; use axum::{ + extract::{ + ws::{Message, WebSocket}, + WebSocketUpgrade, + }, http::StatusCode, response::IntoResponse, - routing::get_service, - Router, + routing::{get, get_service}, + Router, TypedHeader, }; use clap::{ArgAction, Parser}; -use log::{error, info}; +use headers::UserAgent; +use log::{debug, error, info, trace, warn}; use std::{ env, io, net::{IpAddr, SocketAddr}, path::PathBuf, + time::Instant, }; use tower::ServiceBuilder; use tower_http::{ @@ -35,6 +41,64 @@ struct Cli { verbosity: u8, } +async fn handle_websocket(mut socket: WebSocket) { + let mut start: Option = None; + + loop { + if let Some(msg) = socket.recv().await { + if let Ok(msg) = msg { + match msg { + Message::Binary(b) => { + if let Ok(message) = bincode::deserialize::(&b) { + trace!("received message on websocket: {:?}", message); + + match message { + client::Message::Start => start = Some(Instant::now()), + client::Message::Stop => { + if let Some(start) = start { + info!("server measured {:?}", start.elapsed()); + } else { + error!("received stop message without running clock"); + } + } + client::Message::Duration(d) => info!("client measured {:?}", d), + } + } else { + error!("received invalid message: {:?}", b); + } + } + Message::Ping(p) => { + debug!("websocket ping: {:?}", p); + } + Message::Pong(p) => { + debug!("websocket pong: {:?}", p); + } + Message::Close(m) => { + info!("client disconnected: {:?}", m); + return; + } + Message::Text(_) => warn!("websocket text messages are not implemented"), + } + } else { + info!("client disconnected"); + return; + } + } + } +} + +#[allow(clippy::unused_async)] +async fn websocket_handler( + ws: WebSocketUpgrade, + user_agent: Option>, +) -> impl IntoResponse { + if let Some(TypedHeader(user_agent)) = user_agent { + info!("`{}` connected", user_agent.as_str()); + } + + ws.on_upgrade(handle_websocket) +} + #[allow(clippy::unused_async)] async fn file_error(err: io::Error) -> impl IntoResponse { error!("could not read from file: {}", err); @@ -52,6 +116,7 @@ async fn main() -> Result<()> { tracing_subscriber::fmt::init(); let app = Router::new() + .route("/api/ws", get(websocket_handler)) .nest( "/client", get_service(ServeDir::new(&cli.static_dir)).handle_error(file_error),