diff options
| author | Fuwn <[email protected]> | 2021-08-04 16:05:42 -0700 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2021-08-04 16:05:42 -0700 |
| commit | a8063b0cc5f4eec4710081cbb0679a78f5b601bc (patch) | |
| tree | 6fc87e74234e2f4a324fe55d0d81343307344d3d | |
| parent | fix(whirl): signal handling not working (diff) | |
| download | whirl-a8063b0cc5f4eec4710081cbb0679a78f5b601bc.tar.xz whirl-a8063b0cc5f4eec4710081cbb0679a78f5b601bc.zip | |
refactor(whirl_api): migrate to Axum
The Tokio group recently released a new web-framework called Axum, and being that almost every
aspect of Whirl harnesses Tokio's pre-existing libraries; I think it's more fitting to migrate the
web-server over to a Tokio-based one as well!
| -rw-r--r-- | crates/whirl_api/Cargo.toml | 2 | ||||
| -rw-r--r-- | crates/whirl_api/src/lib.rs | 53 | ||||
| -rw-r--r-- | crates/whirl_api/src/routes/stats/mod.rs | 34 | ||||
| -rw-r--r-- | crates/whirl_api/src/routes/worlds/info/mod.rs | 19 | ||||
| -rw-r--r-- | crates/whirl_api/src/routes/worlds/vip/mod.rs | 43 |
5 files changed, 72 insertions, 79 deletions
diff --git a/crates/whirl_api/Cargo.toml b/crates/whirl_api/Cargo.toml index c0a3512..afaa653 100644 --- a/crates/whirl_api/Cargo.toml +++ b/crates/whirl_api/Cargo.toml @@ -16,6 +16,8 @@ publish = false # Web-server actix-web = { version = "3.3.2", features = ["rustls"] } actix-cors = "0.5.4" +axum = "0.1.2" +hyper = "0.14.11" # Utility sysinfo = "0.19.2" diff --git a/crates/whirl_api/src/lib.rs b/crates/whirl_api/src/lib.rs index e89d891..ddee9ff 100644 --- a/crates/whirl_api/src/lib.rs +++ b/crates/whirl_api/src/lib.rs @@ -30,7 +30,7 @@ extern crate log; #[macro_use] extern crate serde_derive; -use actix_web::web::{resource, scope}; +use axum::prelude::*; mod routes; @@ -45,34 +45,19 @@ impl Api { /// # Panics /// - A panic may occur if the mpsc sender is unable to send a clone of the /// server. - pub async fn listen( - tx: std::sync::mpsc::Sender<actix_web::dev::Server>, - address: &str, - ) -> std::io::Result<()> { - let mut sys = actix_web::rt::System::new("api"); + pub async fn listen(address: &str) { + // TODO: Version handler + let app = route("/", get(|| async { "Whirlsplash" })) + .route("/api/v1/stats", get(routes::stats::statistics)) + .route("/api/v1/worlds/info", get(routes::worlds::info::info)) + .route("/api/v1/worlds/vip", get(routes::worlds::vip::vip)); - let server = actix_web::HttpServer::new(|| { - actix_web::App::new() - .wrap(actix_cors::Cors::default().allow_any_origin()) - .service(resource("/").to(|| async { "Whirlsplash" })) - .service( - scope("/api/v1") - .service(resource("/statistics").to(routes::stats::statistics)) - .service( - scope("/worlds") - .service(resource("/vip").to(routes::worlds::vip::vip)) - .service(resource("/info").to(routes::worlds::info::info)), - ), - ) - }) - .bind(address)? - .run(); + hyper::Server::bind(&address.parse().unwrap()) + .serve(app.into_make_service()) + .await + .unwrap(); info!("http api now listening at {}", address); - - tx.send(server.clone()).unwrap(); - - sys.block_on(server) } } @@ -84,17 +69,11 @@ impl Api { /// server. #[must_use] pub fn make() -> tokio::task::JoinHandle<()> { - // actix_web::rt::System::new("").block_on(rx.recv().unwrap().stop(true)); - tokio::spawn(async move { - crate::Api::listen( - std::sync::mpsc::channel().0, - &*format!( - "0.0.0.0:{}", - whirl_config::Config::get().whirlsplash.api.port - ), - ) - .await - .unwrap(); + crate::Api::listen(&*format!( + "0.0.0.0:{}", + whirl_config::Config::get().whirlsplash.api.port + )) + .await; }) } diff --git a/crates/whirl_api/src/routes/stats/mod.rs b/crates/whirl_api/src/routes/stats/mod.rs index 5ec5eee..595b441 100644 --- a/crates/whirl_api/src/routes/stats/mod.rs +++ b/crates/whirl_api/src/routes/stats/mod.rs @@ -5,7 +5,7 @@ pub mod structures; use std::convert::TryFrom; -use actix_web::HttpResponse; +use axum::response; use num_traits::cast::AsPrimitive; use sysinfo::{get_current_pid, ProcessExt, System, SystemExt}; @@ -13,23 +13,27 @@ use crate::routes::stats::structures::{Statistics, StatisticsProcess, Statistics // This is mostly for developmental testing, it consumes more CPU than it's // worth. -pub fn statistics() -> HttpResponse { +#[allow(clippy::unused_async)] +pub async fn statistics() -> impl response::IntoResponse { let mut sys = System::new_all(); sys.refresh_all(); let process = sys.process(get_current_pid().unwrap()).unwrap(); - HttpResponse::Ok().json(Statistics { - system: StatisticsSystem { - os_type: sys.name().unwrap(), - release: sys.kernel_version().unwrap(), - uptime: whirl_common::system::unixts_to_hrtime(usize::try_from(sys.uptime()).unwrap()), - }, - process: StatisticsProcess { - // (process.cpu_usage() * 100.0).round() / 100.0 - memory_usage: (process.memory() / 1000).to_string(), - cpu_usage: (process.cpu_usage() / sys.processors().len().as_(): f32).to_string(), - // uptime: seconds_to_hrtime((sys.get_uptime() - process.start_time()) as usize), - }, - }) + ( + hyper::StatusCode::OK, + response::Json(Statistics { + system: StatisticsSystem { + os_type: sys.name().unwrap(), + release: sys.kernel_version().unwrap(), + uptime: whirl_common::system::unixts_to_hrtime(usize::try_from(sys.uptime()).unwrap()), + }, + process: StatisticsProcess { + // (process.cpu_usage() * 100.0).round() / 100.0 + memory_usage: (process.memory() / 1000).to_string(), + cpu_usage: (process.cpu_usage() / sys.processors().len().as_(): f32).to_string(), + // uptime: seconds_to_hrtime((sys.get_uptime() - process.start_time()) as usize), + }, + }), + ) } diff --git a/crates/whirl_api/src/routes/worlds/info/mod.rs b/crates/whirl_api/src/routes/worlds/info/mod.rs index 6ea1453..ea04112 100644 --- a/crates/whirl_api/src/routes/worlds/info/mod.rs +++ b/crates/whirl_api/src/routes/worlds/info/mod.rs @@ -1,22 +1,19 @@ // Copyright (C) 2021-2021 The Whirlsplash Collective // SPDX-License-Identifier: GPL-3.0-only -use std::str::from_utf8; - -use actix_web::{HttpRequest, HttpResponse}; +#[derive(Serialize, Deserialize)] +pub struct Parameters { + username: Option<String>, +} -// error: this argument is passed by value, but not consumed in the function -// body -#[allow(clippy::needless_pass_by_value)] -pub fn info(req: HttpRequest) -> HttpResponse { +#[allow(clippy::needless_pass_by_value, clippy::unused_async)] +pub async fn info(axum::extract::Query(req): axum::extract::Query<Parameters>) -> &'static str { let mut easy = curl::easy::Easy::new(); easy .url(&format!( "http://www-dynamic.us.worlds.net/cgi-bin/profile.pl?{}", - qstring::QString::from(req.query_string()) - .get("username") - .unwrap_or(""), + req.username.as_ref().unwrap_or(&"".to_string()), )) .unwrap(); @@ -34,5 +31,5 @@ pub fn info(req: HttpRequest) -> HttpResponse { transfer.perform().unwrap(); } - HttpResponse::Ok().body(from_utf8(&response).unwrap().to_string()) + Box::leak(String::from_utf8(response).unwrap().into_boxed_str()) } diff --git a/crates/whirl_api/src/routes/worlds/vip/mod.rs b/crates/whirl_api/src/routes/worlds/vip/mod.rs index c9455e6..7b7aee0 100644 --- a/crates/whirl_api/src/routes/worlds/vip/mod.rs +++ b/crates/whirl_api/src/routes/worlds/vip/mod.rs @@ -5,27 +5,35 @@ mod structures; use std::str::from_utf8; -use actix_web::{HttpRequest, HttpResponse}; +use axum::response; use crate::routes::worlds::vip::structures::Vip; -// error: this argument is passed by value, but not consumed in the function -// body -#[allow(clippy::needless_pass_by_value)] -pub fn vip(req: HttpRequest) -> HttpResponse { - let queries = qstring::QString::from(req.query_string()); +#[derive(Serialize, Deserialize)] +pub struct Parameters { + username: Option<String>, +} + +#[allow(clippy::needless_pass_by_value, clippy::unused_async)] +pub async fn vip( + axum::extract::Query(req): axum::extract::Query<Parameters>, +) -> impl response::IntoResponse { let mut easy = curl::easy::Easy::new(); let mut error = String::new(); - let username = queries.get("username"); - if username.is_none() || username.map_or(false, str::is_empty) { + let username = req.username; + if username.is_none() + || username + .as_ref() + .map_or(false, std::string::String::is_empty) + { error = "no username query parameter provided, defaulting to 'null'".to_string(); } easy .url(&format!( "http://www-dynamic.us.worlds.net/cgi-bin/vip.pl?Username={}", - username.unwrap_or("null"), + username.unwrap_or_else(|| "null".to_string()), )) .unwrap(); @@ -43,11 +51,14 @@ pub fn vip(req: HttpRequest) -> HttpResponse { transfer.perform().unwrap(); } - HttpResponse::Ok().json(Vip { - vip: from_utf8(&response) - .unwrap() - .to_string() - .contains("You're already a VIP!"), - error: if error.is_empty() { None } else { Some(error) }, - }) + ( + hyper::StatusCode::OK, + response::Json(Vip { + vip: from_utf8(&response) + .unwrap() + .to_string() + .contains("You're already a VIP!"), + error: if error.is_empty() { None } else { Some(error) }, + }), + ) } |