153 lines
4.8 KiB
Rust
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(())
|
|
}
|