feat(homeserver): basic Axum server with nothing but / router

This commit is contained in:
nazeh
2024-07-14 11:35:28 +03:00
parent 5daca7c9e7
commit 3b8f6cf7b8
8 changed files with 1153 additions and 1 deletions

1010
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
[workspace]
members = []
members = ["homeserver"]
# See: https://github.com/rust-lang/rust/issues/90148#issuecomment-949194352
resolver = "2"

12
homeserver/Cargo.toml Normal file
View File

@@ -0,0 +1,12 @@
[package]
name = "pubky_homeserver"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow = "1.0.82"
axum = "0.7.5"
tokio = { version = "1.37.0", features = ["full"] }
tower-http = { version = "0.5.2", features = ["cors", "trace"] }
tracing = "0.1.40"
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }

4
homeserver/src/lib.rs Normal file
View File

@@ -0,0 +1,4 @@
mod routes;
mod server;
pub use server::Homeserver;

13
homeserver/src/main.rs Normal file
View File

@@ -0,0 +1,13 @@
use anyhow::Result;
use pubky_homeserver::Homeserver;
#[tokio::main]
async fn main() -> Result<()> {
tracing_subscriber::fmt().init();
let server = Homeserver::start().await?;
server.run_until_done().await?;
Ok(())
}

7
homeserver/src/routes.rs Normal file
View File

@@ -0,0 +1,7 @@
use axum::{routing::get, Router};
pub mod root;
pub fn create_app() -> Router {
Router::new().route("/", get(root::handler))
}

View File

@@ -0,0 +1,5 @@
use axum::response::IntoResponse;
pub async fn handler() -> Result<impl IntoResponse, String> {
Ok("This a Pubky homeserver.".to_string())
}

101
homeserver/src/server.rs Normal file
View File

@@ -0,0 +1,101 @@
use std::{future::IntoFuture, net::SocketAddr};
use anyhow::{Error, Result};
use tokio::{net::TcpListener, signal, task::JoinSet};
use tracing::{info, warn};
#[derive(Debug)]
pub struct Homeserver {
tasks: JoinSet<std::io::Result<()>>,
}
impl Homeserver {
pub async fn start() -> Result<Self> {
let app = crate::routes::create_app();
let mut tasks = JoinSet::new();
let app = app.clone();
let listener = TcpListener::bind(SocketAddr::from((
[127, 0, 0, 1],
6287, // config.port()
)))
.await?;
let bound_addr = listener.local_addr()?;
// Spawn http server task
tasks.spawn(
axum::serve(
listener,
app.into_make_service_with_connect_info::<SocketAddr>(),
)
.with_graceful_shutdown(shutdown_signal())
.into_future(),
);
info!("HTTP server listening on {bound_addr}");
Ok(Self { tasks })
}
// === Public Methods ===
/// Shutdown the server and wait for all tasks to complete.
pub async fn shutdown(mut self) -> Result<()> {
self.tasks.abort_all();
self.run_until_done().await?;
Ok(())
}
/// Wait for all tasks to complete.
///
/// Runs forever unless tasks fail.
pub async fn run_until_done(mut self) -> Result<()> {
let mut final_res: Result<()> = Ok(());
while let Some(res) = self.tasks.join_next().await {
match res {
Ok(Ok(())) => {}
Err(err) if err.is_cancelled() => {}
Ok(Err(err)) => {
warn!(?err, "task failed");
final_res = Err(Error::from(err));
}
Err(err) => {
warn!(?err, "task panicked");
final_res = Err(err.into());
}
}
}
final_res
}
}
async fn shutdown_signal() {
let ctrl_c = async {
signal::ctrl_c()
.await
.expect("failed to install Ctrl+C handler");
};
#[cfg(unix)]
let terminate = async {
signal::unix::signal(signal::unix::SignalKind::terminate())
.expect("failed to install signal handler")
.recv()
.await;
};
#[cfg(not(unix))]
let terminate = std::future::pending::<()>();
fn graceful_shutdown() {
info!("Gracefully Shutting down..");
}
tokio::select! {
_ = ctrl_c => graceful_shutdown(),
_ = terminate => graceful_shutdown(),
}
}