diff --git a/Cargo.lock b/Cargo.lock index 9b43187..1288c02 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,12 +8,6 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" -[[package]] -name = "anyhow" -version = "1.0.102" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" - [[package]] name = "atoi" version = "2.0.0" @@ -57,6 +51,61 @@ dependencies = [ "fs_extra", ] +[[package]] +name = "axum" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" +dependencies = [ + "axum-core", + "base64", + "bytes", + "form_urlencoded", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde_core", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sha1", + "sync_wrapper", + "tokio", + "tokio-tungstenite", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "base64" version = "0.22.1" @@ -135,17 +184,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" -[[package]] -name = "chacha20" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" -dependencies = [ - "cfg-if", - "cpufeatures 0.3.0", - "rand_core 0.10.0", -] - [[package]] name = "cmake" version = "0.1.57" @@ -215,15 +253,6 @@ dependencies = [ "libc", ] -[[package]] -name = "cpufeatures" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" -dependencies = [ - "libc", -] - [[package]] name = "crc" version = "3.4.0" @@ -264,6 +293,12 @@ dependencies = [ "typenum", ] +[[package]] +name = "data-encoding" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" + [[package]] name = "der" version = "0.7.10" @@ -443,9 +478,9 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" [[package]] name = "futures-executor" @@ -471,27 +506,27 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" [[package]] name = "futures-sink" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" [[package]] name = "futures-task" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" [[package]] name = "futures-util" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" dependencies = [ "futures-core", "futures-io", @@ -539,20 +574,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "getrandom" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" -dependencies = [ - "cfg-if", - "libc", - "r-efi", - "rand_core 0.10.0", - "wasip2", - "wasip3", -] - [[package]] name = "h2" version = "0.4.13" @@ -676,6 +697,12 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + [[package]] name = "hyper" version = "1.8.1" @@ -690,6 +717,7 @@ dependencies = [ "http", "http-body", "httparse", + "httpdate", "itoa", "pin-project-lite", "pin-utils", @@ -820,12 +848,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" - [[package]] name = "idna" version = "1.1.0" @@ -855,8 +877,6 @@ checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", "hashbrown 0.16.1", - "serde", - "serde_core", ] [[package]] @@ -932,12 +952,6 @@ dependencies = [ "spin", ] -[[package]] -name = "leb128fmt" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" - [[package]] name = "libc" version = "0.2.182" @@ -1005,6 +1019,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + [[package]] name = "md-5" version = "0.10.6" @@ -1258,16 +1278,6 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "prettyplease" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" -dependencies = [ - "proc-macro2", - "syn", -] - [[package]] name = "proc-macro2" version = "1.0.106" @@ -1369,17 +1379,6 @@ dependencies = [ "rand_core 0.9.5", ] -[[package]] -name = "rand" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" -dependencies = [ - "chacha20", - "getrandom 0.4.1", - "rand_core 0.10.0", -] - [[package]] name = "rand_chacha" version = "0.3.1" @@ -1418,12 +1417,6 @@ dependencies = [ "getrandom 0.3.4", ] -[[package]] -name = "rand_core" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" - [[package]] name = "redox_syscall" version = "0.5.18" @@ -1682,12 +1675,6 @@ dependencies = [ "libc", ] -[[package]] -name = "semver" -version = "1.0.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" - [[package]] name = "serde" version = "1.0.228" @@ -1731,6 +1718,17 @@ dependencies = [ "zmij", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -1747,6 +1745,7 @@ dependencies = [ name = "server" version = "0.1.0" dependencies = [ + "axum", "reqwest", "serde", "serde_json", @@ -1757,15 +1756,14 @@ dependencies = [ [[package]] name = "session-rs" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2f82aa4448e402161e9c7c33c6ee53a19e073d3eeca6c871a8a14383f088e7e" +checksum = "21e32201e293e8a6188977cfc0be574a25392b205473a63e538fc661fc712a00" dependencies = [ - "base64", - "rand 0.10.0", + "axum", + "futures-util", "serde", "serde_json", - "sha1", "tokio", ] @@ -1776,7 +1774,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", - "cpufeatures 0.2.17", + "cpufeatures", "digest", ] @@ -1787,7 +1785,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures 0.2.17", + "cpufeatures", "digest", ] @@ -1797,6 +1795,16 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + [[package]] name = "signature" version = "2.2.0" @@ -2203,6 +2211,7 @@ dependencies = [ "libc", "mio", "pin-project-lite", + "signal-hook-registry", "socket2", "tokio-macros", "windows-sys 0.61.2", @@ -2240,6 +2249,18 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-tungstenite" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f72a05e828585856dacd553fba484c242c46e391fb0e58917c942ee9202915c" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", +] + [[package]] name = "tokio-util" version = "0.7.18" @@ -2266,6 +2287,7 @@ dependencies = [ "tokio", "tower-layer", "tower-service", + "tracing", ] [[package]] @@ -2336,6 +2358,22 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "tungstenite" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c01152af293afb9c7c2a57e4b559c5620b421f6d133261c60dd2d0cdb38e6b8" +dependencies = [ + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand 0.9.2", + "sha1", + "thiserror 2.0.18", +] + [[package]] name = "typenum" version = "1.19.0" @@ -2369,12 +2407,6 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - [[package]] name = "untrusted" version = "0.9.0" @@ -2445,15 +2477,6 @@ dependencies = [ "wit-bindgen", ] -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" -dependencies = [ - "wit-bindgen", -] - [[package]] name = "wasite" version = "0.1.0" @@ -2519,40 +2542,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" -dependencies = [ - "leb128fmt", - "wasmparser", -] - -[[package]] -name = "wasm-metadata" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" -dependencies = [ - "anyhow", - "indexmap", - "wasm-encoder", - "wasmparser", -] - -[[package]] -name = "wasmparser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" -dependencies = [ - "bitflags", - "hashbrown 0.15.5", - "indexmap", - "semver", -] - [[package]] name = "web-sys" version = "0.3.88" @@ -2929,88 +2918,6 @@ name = "wit-bindgen" version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "wit-bindgen-rust-macro", -] - -[[package]] -name = "wit-bindgen-core" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck", - "indexmap", - "prettyplease", - "syn", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn", - "wit-bindgen-core", - "wit-bindgen-rust", -] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags", - "indexmap", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser", -] [[package]] name = "writeable" diff --git a/Cargo.toml b/Cargo.toml index 2c8a73b..3909376 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,13 +4,14 @@ version = "0.1.0" edition = "2024" [dependencies] +axum = { version = "0.8.9", features = ["ws"] } reqwest = { version = "0.13.2", features = ["json"] } serde = { version = "1.0.228", features = ["serde_derive"] } serde_json = "1.0.149" -session-rs = "0.1.3" +session-rs = { version = "0.2.0", default-features = false, features = ["axum"] } sqlx = { version = "0.8.6", features = [ "sqlite", "runtime-tokio-native-tls", "macros", ] } -tokio = "1.49.0" +tokio = { version = "1.49.0", features = ["macros", "rt-multi-thread", "net", "signal", "sync", "time"] } diff --git a/Dockerfile b/Dockerfile index 342a9c0..2d016a0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -39,4 +39,7 @@ ENV BIND_ADDR=0.0.0.0:8080 \ EXPOSE 8080 VOLUME ["/data"] +HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ + CMD ["server", "healthcheck"] + CMD ["server"] diff --git a/src/main.rs b/src/main.rs index ac49a31..236a333 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,213 +3,263 @@ mod methods; mod types; mod user; -use std::{collections::HashMap, pin::Pin, sync::Arc}; +use std::{collections::HashMap, sync::Arc, time::Duration}; -use session_rs::server::SessionServer; +use axum::{ + Router, + extract::{State, WebSocketUpgrade}, + http::StatusCode, + response::Response, + routing::get, +}; +use session_rs::Session; +use sqlx::SqlitePool; use tokio::sync::Mutex; use crate::types::SessionMap; -#[tokio::main(flavor = "current_thread")] -async fn main() -> session_rs::Result<()> { - let pool = Arc::new(user::init_db().await); +/// Largest WebSocket message a client may send. Every RPC payload is tiny. +const MAX_MESSAGE_SIZE: usize = 1 << 20; + +#[derive(Clone)] +struct AppState { + pool: Arc, + sessions: SessionMap, +} + +#[tokio::main] +async fn main() -> std::io::Result<()> { let bind_addr = std::env::var("BIND_ADDR").unwrap_or_else(|_| "127.0.0.1:8080".into()); - let server = SessionServer::bind(&bind_addr).await?; + + // `server healthcheck` probes a running server; used by the Docker HEALTHCHECK. + if std::env::args().nth(1).as_deref() == Some("healthcheck") { + std::process::exit(healthcheck(&bind_addr).await); + } + + let state = AppState { + pool: Arc::new(user::init_db().await), + sessions: Arc::new(Mutex::new(HashMap::new())), + }; + + let app = Router::new() + .route("/", get(ws)) + .route("/health", get(health)) + .with_state(state); + + let listener = tokio::net::TcpListener::bind(&bind_addr).await?; println!("Listening on {bind_addr}"); - let sessions: SessionMap = Arc::new(Mutex::new(HashMap::new())); - - server - .session_loop({ - let pool = Arc::clone(&pool); - move |session, _| { - let pool = Arc::clone(&pool); - let sessions = Arc::clone(&sessions); - - Box::pin(async move { - println!("Connected"); - let uuid = Arc::new(Mutex::new(String::new())); - let name = Arc::new(Mutex::new(String::new())); - - session - .on_close({ - let session = session.clone(); - let uuid = uuid.clone(); - let sessions = Arc::clone(&sessions); - - move || { - let uuid = uuid.clone(); - let sessions = Arc::clone(&sessions); - let session = session.clone(); - - Box::pin(async move { - let uuid_lock = uuid.lock().await; - if !uuid_lock.is_empty() { - if let Some(sessions) = - sessions.lock().await.get_mut(uuid_lock.as_str()) - { - sessions.remove(&session); - } - } - Ok(()) - }) - } - }) - .await; - - session.start_ping( - tokio::time::Duration::from_secs(30), - tokio::time::Duration::from_secs(5), - ); - - session - .on_request::({ - let pool = Arc::clone(&pool); - let uuid = Arc::clone(&uuid); - let sessions = Arc::clone(&sessions); - let name = Arc::clone(&name); - let session = session.clone(); - - move |_, token| { - methods::auth::authenticate( - sessions.clone(), - session.clone(), - name.clone(), - uuid.clone(), - token, - pool.clone(), - ) - } - }) - .await; - - session - .on_request::({ - let uuid = Arc::clone(&uuid); - let pool = Arc::clone(&pool); - - move |_, item_id| { - let pool = Arc::clone(&pool); - let item_id = item_id.clone(); - - cosmetics::buy( - cosmetics::CosmeticKind::Cloak, - uuid.clone(), - item_id, - pool, - ) - } - }) - .await; - - session - .on_request::({ - let uuid = Arc::clone(&uuid); - let pool = Arc::clone(&pool); - - move |_, item_id| { - let pool = Arc::clone(&pool); - let item_id = item_id.clone(); - - cosmetics::buy( - cosmetics::CosmeticKind::Hat, - uuid.clone(), - item_id, - pool, - ) - } - }) - .await; - - session - .on_request::({ - let uuid = Arc::clone(&uuid); - let pool = Arc::clone(&pool); - - move |_, item_id| { - let pool = Arc::clone(&pool); - let item_id = item_id.clone(); - - cosmetics::equip( - cosmetics::CosmeticKind::Cloak, - uuid.clone(), - item_id, - pool, - ) - } - }) - .await; - - session - .on_request::({ - let uuid = Arc::clone(&uuid); - let pool = Arc::clone(&pool); - - move |_, item_id| { - let pool = Arc::clone(&pool); - let item_id = item_id.clone(); - - cosmetics::equip( - cosmetics::CosmeticKind::Hat, - uuid.clone(), - item_id, - pool, - ) - } - }) - .await; - - session - .on_request::({ - let sessions = Arc::clone(&sessions); - let uuid = Arc::clone(&uuid); - - move |_, emote| { - let sessions = Arc::clone(&sessions); - let uuid = Arc::clone(&uuid); - - methods::emote::send_emote(sessions, uuid, emote) - } - }) - .await; - - session - .on_request::({ - let sessions = Arc::clone(&sessions); - let pool = Arc::clone(&pool); - - move |_, uuid| { - methods::user::get_user( - sessions.clone(), - uuid.clone(), - pool.clone(), - ) - } - }) - .await; - - session - .on_request::({ - let sessions = Arc::clone(&sessions); - let pool = Arc::clone(&pool); - let uuid = Arc::clone(&uuid); - let name = Arc::clone(&name); - - move |_, targets| { - methods::user::send_user( - sessions.clone(), - name.clone(), - uuid.clone(), - targets.targets.clone(), - pool.clone(), - ) - } - }) - .await; - - Ok::<(), session_rs::Error>(()) - }) as Pin + Send>> - } - }) + axum::serve(listener, app) + .with_graceful_shutdown(shutdown_signal()) .await } + +async fn ws(upgrade: WebSocketUpgrade, State(state): State) -> Response { + upgrade + .max_message_size(MAX_MESSAGE_SIZE) + .max_frame_size(MAX_MESSAGE_SIZE) + .on_upgrade(move |socket| async move { + println!("Connected"); + let session = Session::from_axum(socket); + + register_handlers(&session, state).await; + + session.start_ping(Duration::from_secs(30), Duration::from_secs(5)); + session.start_receiver(); + }) +} + +async fn health(State(state): State) -> (StatusCode, &'static str) { + match sqlx::query("SELECT 1").execute(state.pool.as_ref()).await { + Ok(_) => (StatusCode::OK, "ok"), + Err(_) => (StatusCode::SERVICE_UNAVAILABLE, "database unavailable"), + } +} + +async fn register_handlers(session: &Session, state: AppState) { + let AppState { pool, sessions } = state; + let uuid = Arc::new(Mutex::new(String::new())); + let name = Arc::new(Mutex::new(String::new())); + + session + .on_close({ + let session = session.clone(); + let uuid = uuid.clone(); + let sessions = Arc::clone(&sessions); + + move || { + let uuid = uuid.clone(); + let sessions = Arc::clone(&sessions); + let session = session.clone(); + + async move { + let uuid_lock = uuid.lock().await; + if !uuid_lock.is_empty() { + if let Some(sessions) = sessions.lock().await.get_mut(uuid_lock.as_str()) { + sessions.remove(&session); + } + } + Ok(()) + } + } + }) + .await; + + session + .on_request::({ + let pool = Arc::clone(&pool); + let uuid = Arc::clone(&uuid); + let sessions = Arc::clone(&sessions); + let name = Arc::clone(&name); + let session = session.clone(); + + move |_, token| { + methods::auth::authenticate( + sessions.clone(), + session.clone(), + name.clone(), + uuid.clone(), + token, + pool.clone(), + ) + } + }) + .await; + + session + .on_request::({ + let uuid = Arc::clone(&uuid); + let pool = Arc::clone(&pool); + + move |_, item_id| { + cosmetics::buy( + cosmetics::CosmeticKind::Cloak, + uuid.clone(), + item_id, + Arc::clone(&pool), + ) + } + }) + .await; + + session + .on_request::({ + let uuid = Arc::clone(&uuid); + let pool = Arc::clone(&pool); + + move |_, item_id| { + cosmetics::buy( + cosmetics::CosmeticKind::Hat, + uuid.clone(), + item_id, + Arc::clone(&pool), + ) + } + }) + .await; + + session + .on_request::({ + let uuid = Arc::clone(&uuid); + let pool = Arc::clone(&pool); + + move |_, item_id| { + cosmetics::equip( + cosmetics::CosmeticKind::Cloak, + uuid.clone(), + item_id, + Arc::clone(&pool), + ) + } + }) + .await; + + session + .on_request::({ + let uuid = Arc::clone(&uuid); + let pool = Arc::clone(&pool); + + move |_, item_id| { + cosmetics::equip( + cosmetics::CosmeticKind::Hat, + uuid.clone(), + item_id, + Arc::clone(&pool), + ) + } + }) + .await; + + session + .on_request::({ + let sessions = Arc::clone(&sessions); + let uuid = Arc::clone(&uuid); + + move |_, emote| methods::emote::send_emote(Arc::clone(&sessions), Arc::clone(&uuid), emote) + }) + .await; + + session + .on_request::({ + let sessions = Arc::clone(&sessions); + let pool = Arc::clone(&pool); + + move |_, uuid| methods::user::get_user(sessions.clone(), uuid, pool.clone()) + }) + .await; + + session + .on_request::({ + let sessions = Arc::clone(&sessions); + let pool = Arc::clone(&pool); + let uuid = Arc::clone(&uuid); + let name = Arc::clone(&name); + + move |_, targets| { + methods::user::send_user( + sessions.clone(), + name.clone(), + uuid.clone(), + targets.targets, + pool.clone(), + ) + } + }) + .await; +} + +/// Exit code 0 if `/health` on the local server answers 2xx. +async fn healthcheck(bind_addr: &str) -> i32 { + let port = bind_addr.rsplit(':').next().unwrap_or("8080"); + + match reqwest::get(format!("http://127.0.0.1:{port}/health")).await { + Ok(res) if res.status().is_success() => 0, + _ => 1, + } +} + +/// Resolves on Ctrl+C or SIGTERM (what Docker sends on stop). +async fn shutdown_signal() { + let ctrl_c = async { + let _ = tokio::signal::ctrl_c().await; + }; + + #[cfg(unix)] + let terminate = async { + if let Ok(mut signal) = + tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate()) + { + signal.recv().await; + } + }; + + #[cfg(not(unix))] + let terminate = std::future::pending::<()>(); + + tokio::select! { + _ = ctrl_c => {} + _ = terminate => {} + } + + println!("Shutting down"); +}