aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFuwn <[email protected]>2021-08-04 16:05:42 -0700
committerFuwn <[email protected]>2021-08-04 16:05:42 -0700
commita8063b0cc5f4eec4710081cbb0679a78f5b601bc (patch)
tree6fc87e74234e2f4a324fe55d0d81343307344d3d
parentfix(whirl): signal handling not working (diff)
downloadwhirl-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.toml2
-rw-r--r--crates/whirl_api/src/lib.rs53
-rw-r--r--crates/whirl_api/src/routes/stats/mod.rs34
-rw-r--r--crates/whirl_api/src/routes/worlds/info/mod.rs19
-rw-r--r--crates/whirl_api/src/routes/worlds/vip/mod.rs43
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) },
+ }),
+ )
}