mirror of
https://github.com/Noratrieb/viewstrap.git
synced 2026-01-14 16:45:10 +01:00
setup
This commit is contained in:
commit
1855296152
5 changed files with 1488 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/target
|
||||||
1325
Cargo.lock
generated
Normal file
1325
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
17
Cargo.toml
Normal file
17
Cargo.toml
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
[package]
|
||||||
|
name = "viewstrap"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
axum = { version = "0.6.10", features = ["ws", "headers"] }
|
||||||
|
color-eyre = "0.6.2"
|
||||||
|
futures = "0.3.26"
|
||||||
|
headers = "0.3.8"
|
||||||
|
tokio = { version = "1.26.0", features = ["full"] }
|
||||||
|
tower = "0.4.13"
|
||||||
|
tower-http = { version = "0.4.0", features = ["trace", "fs"] }
|
||||||
|
tracing = "0.1.37"
|
||||||
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
0
assets/index.html
Normal file
0
assets/index.html
Normal file
145
src/main.rs
Normal file
145
src/main.rs
Normal file
|
|
@ -0,0 +1,145 @@
|
||||||
|
#[macro_use]
|
||||||
|
extern crate tracing;
|
||||||
|
|
||||||
|
use std::{borrow::Cow, net::SocketAddr, path::PathBuf};
|
||||||
|
|
||||||
|
use axum::{
|
||||||
|
extract::{
|
||||||
|
ws::{CloseFrame, Message, WebSocket},
|
||||||
|
ConnectInfo, TypedHeader, WebSocketUpgrade,
|
||||||
|
},
|
||||||
|
response::IntoResponse,
|
||||||
|
routing::get,
|
||||||
|
Router,
|
||||||
|
};
|
||||||
|
use futures::{sink::SinkExt, stream::StreamExt};
|
||||||
|
use tower_http::{
|
||||||
|
services::ServeDir,
|
||||||
|
trace::{DefaultMakeSpan, TraceLayer},
|
||||||
|
};
|
||||||
|
use tracing_subscriber::{prelude::__tracing_subscriber_SubscriberExt, util::SubscriberInitExt};
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
tracing_subscriber::registry()
|
||||||
|
.with(
|
||||||
|
tracing_subscriber::EnvFilter::try_from_default_env()
|
||||||
|
.unwrap_or_else(|_| "viewstrap=debug,tower_http=debug".into()),
|
||||||
|
)
|
||||||
|
.with(tracing_subscriber::fmt::layer())
|
||||||
|
.init();
|
||||||
|
|
||||||
|
info!("bootstrapping viewstrap...");
|
||||||
|
|
||||||
|
let assets_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("assets");
|
||||||
|
|
||||||
|
// build our application with some routes
|
||||||
|
let app = Router::new()
|
||||||
|
.fallback_service(ServeDir::new(assets_dir).append_index_html_on_directories(true))
|
||||||
|
.route("/ws", get(ws_handler))
|
||||||
|
// logging so we can see whats going on
|
||||||
|
.layer(
|
||||||
|
TraceLayer::new_for_http()
|
||||||
|
.make_span_with(DefaultMakeSpan::default().include_headers(true)),
|
||||||
|
);
|
||||||
|
|
||||||
|
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
|
||||||
|
info!("listening on {}", addr);
|
||||||
|
axum::Server::bind(&addr)
|
||||||
|
.serve(app.into_make_service_with_connect_info::<SocketAddr>())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The handler for the HTTP request (this gets called when the HTTP GET lands at the start
|
||||||
|
/// of websocket negotiation). After this completes, the actual switching from HTTP to
|
||||||
|
/// websocket protocol will occur.
|
||||||
|
/// This is the last point where we can extract TCP/IP metadata such as IP address of the client
|
||||||
|
/// as well as things from HTTP headers such as user-agent of the browser etc.
|
||||||
|
async fn ws_handler(
|
||||||
|
ws: WebSocketUpgrade,
|
||||||
|
user_agent: Option<TypedHeader<headers::UserAgent>>,
|
||||||
|
ConnectInfo(addr): ConnectInfo<SocketAddr>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
let user_agent = if let Some(TypedHeader(user_agent)) = user_agent {
|
||||||
|
user_agent.to_string()
|
||||||
|
} else {
|
||||||
|
String::from("Unknown browser")
|
||||||
|
};
|
||||||
|
println!("`{user_agent}` at {addr} connected.");
|
||||||
|
// finalize the upgrade process by returning upgrade callback.
|
||||||
|
// we can customize the callback by sending additional info such as address.
|
||||||
|
ws.on_upgrade(move |socket| handle_socket(socket, addr))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Actual websocket statemachine (one will be spawned per connection)
|
||||||
|
async fn handle_socket(mut socket: WebSocket, who: SocketAddr) {
|
||||||
|
//send a ping (unsupported by some browsers) just to kick things off and get a response
|
||||||
|
if socket.send(Message::Ping(vec![1, 2, 3])).await.is_ok() {
|
||||||
|
info!("Pinged {}...", who);
|
||||||
|
} else {
|
||||||
|
info!("Could not send ping {}!", who);
|
||||||
|
// no Error here since the only thing we can do is to close the connection.
|
||||||
|
// If we can not send messages, there is no way to salvage the statemachine anyway.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// receive single message from a client (we can either receive or send with socket).
|
||||||
|
// this will likely be the Pong for our Ping or a hello message from client.
|
||||||
|
// waiting for message from a client will block this task, but will not block other client's
|
||||||
|
// connections.
|
||||||
|
if let Some(msg) = socket.recv().await {
|
||||||
|
if let Ok(msg) = msg {
|
||||||
|
info!(?msg);
|
||||||
|
} else {
|
||||||
|
info!("client {who} abruptly disconnected");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// By splitting socket we can send and receive at the same time. In this example we will send
|
||||||
|
// unsolicited messages to client based on some sort of server's internal event (i.e .timer).
|
||||||
|
let (mut sender, mut receiver) = socket.split();
|
||||||
|
|
||||||
|
// Spawn a task that will push several messages to the client (does not matter what client does)
|
||||||
|
let mut send_task = tokio::spawn(async move {
|
||||||
|
println!("Sending close to {who}...");
|
||||||
|
if let Err(e) = sender
|
||||||
|
.send(Message::Close(Some(CloseFrame {
|
||||||
|
code: axum::extract::ws::close_code::NORMAL,
|
||||||
|
reason: Cow::from("Goodbye"),
|
||||||
|
})))
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
println!("Could not send Close due to {}, probably it is ok?", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// This second task will receive messages from client and print them on server console
|
||||||
|
let mut recv_task = tokio::spawn(async move {
|
||||||
|
while let Some(Ok(msg)) = receiver.next().await {
|
||||||
|
info!(?msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// If any one of the tasks exit, abort the other.
|
||||||
|
tokio::select! {
|
||||||
|
rv_a = (&mut send_task) => {
|
||||||
|
match rv_a {
|
||||||
|
Ok(_) => {},
|
||||||
|
Err(a) => error!("Error sending messages {:?}", a)
|
||||||
|
}
|
||||||
|
recv_task.abort();
|
||||||
|
},
|
||||||
|
rv_b = (&mut recv_task) => {
|
||||||
|
match rv_b {
|
||||||
|
Ok(_) => {},
|
||||||
|
Err(b) => error!("Error receiving messages {:?}", b)
|
||||||
|
}
|
||||||
|
send_task.abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// returning from the handler closes the websocket connection
|
||||||
|
println!("Websocket context {} destroyed", who);
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue