lockwatch/lockwatch/src/main.rs
finga 9c0286b55a Several pages [wip]
- Be able to set a name
2022-10-01 04:03:16 +02:00

153 lines
4.8 KiB
Rust

use anyhow::Result;
use axum::{
extract::{
ws::{Message, WebSocket},
WebSocketUpgrade,
},
http::StatusCode,
response::IntoResponse,
routing::{get, get_service},
Router, TypedHeader,
};
use clap::{ArgAction, Parser};
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::{
services::{ServeDir, ServeFile},
trace::TraceLayer,
};
#[derive(Parser)]
#[clap(about, author, version)]
struct Cli {
/// The address to listen on
#[clap(short, long, default_value = "::1")]
address: IpAddr,
/// The port to listen on
#[clap(short, long, default_value = "3000")]
port: u16,
/// Path to the directory containing the statically served files
#[clap(short, long, default_value = "dist")]
static_dir: String,
/// Set the log level. Multiple -v options increase the verbosity
#[clap(short, action = ArgAction::Count)]
verbosity: u8,
}
async fn handle_websocket(mut socket: WebSocket) {
let mut start: Option<Instant> = None;
let mut name: String = "Anonymous".to_string();
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::Name(n) => name = n,
client::Message::Start => start = Some(Instant::now()),
client::Message::Stop => {
if let Some(start) = start {
info!("server measured {:?} for {}", start.elapsed(), name);
} else {
error!("received stop message without running clock");
}
}
client::Message::Duration(d) => {
info!("client measured {:?} for {}", d, name)
}
}
} 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);
(StatusCode::INTERNAL_SERVER_ERROR, "Something went wrong...")
}
#[tokio::main]
async fn main() -> Result<()> {
let cli = Cli::parse();
if env::var("RUST_LOG").is_err() {
env::set_var("RUST_LOG", format!("{},hyper=info,mio=info", cli.verbosity));
}
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),
)
.route(
"/",
get_service(ServeFile::new(
&PathBuf::from(&cli.static_dir).join("index.html"),
))
.handle_error(file_error),
)
.route(
"/:clock_id",
get_service(ServeFile::new(
&PathBuf::from(&cli.static_dir).join("index.html"),
))
.handle_error(file_error),
)
.layer(ServiceBuilder::new().layer(TraceLayer::new_for_http()));
let addr = SocketAddr::from((cli.address, cli.port));
info!("listening on {}", addr);
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await?;
Ok(())
}