Use websockets to exchange data

Use websockets to send binary messages to the server. The server logs
the time taken by the client, as well as the time calculated with the
received messages sent by the client.
This commit is contained in:
finga 2022-09-20 16:23:45 +02:00
parent 73d3d739e0
commit 6aa9bb0e43
6 changed files with 527 additions and 5 deletions

377
Cargo.lock generated
View file

@ -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",
]

View file

@ -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"] }

9
client/src/lib.rs Normal file
View file

@ -0,0 +1,9 @@
use serde::{Deserialize, Serialize};
use std::time::Duration;
#[derive(Debug, Deserialize, Serialize)]
pub enum Message {
Start,
Stop,
Duration(Duration),
}

View file

@ -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<Mutex<SplitSink<WebSocket, Message>>>,
_reader: Arc<Mutex<SplitStream<WebSocket>>>,
}
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<Instant>,
duration_handle: Option<Interval>,
}
@ -38,6 +91,7 @@ impl Component for LockWatch {
fn create(_ctx: &Context<Self>) -> 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 => {}
}

View file

@ -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" }

View file

@ -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<Instant> = 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::<client::Message>(&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<TypedHeader<UserAgent>>,
) -> 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),