use http_body_util::Full;
use hyper::body::Incoming;
use hyper::server::conn::http1;
use hyper::service::service_fn;
use hyper::{Request, Response};
use hyper_util::rt::TokioIo;
use std::net::SocketAddr;
use std::sync::Arc;
use tokio::net::TcpListener;
use tracing::{error, info};
use crate::api::endpoints;
use crate::config::Config;
use crate::error::Result;
pub async fn start_api_server(addr: &str, config: Arc<tokio::sync::RwLock<Config>>) -> Result<()> {
let addr: SocketAddr = addr.parse()?;
start_api_server_with_addr(addr, config).await
}
pub async fn start_api_server_with_addr(
addr: SocketAddr,
config: Arc<tokio::sync::RwLock<Config>>,
) -> Result<()> {
let listener = TcpListener::bind(&addr).await?;
info!("API server listening on http://{}", addr);
loop {
let (stream, _) = listener.accept().await?;
let io = TokioIo::new(stream);
let config = config.clone();
tokio::task::spawn(async move {
let service = service_fn(move |req| {
let config = config.clone();
handle_api_request(req, config)
});
if let Err(err) = http1::Builder::new().serve_connection(io, service).await {
error!("Error serving API connection: {:?}", err);
}
});
}
}
async fn handle_api_request(
req: Request<Incoming>,
config: Arc<tokio::sync::RwLock<Config>>,
) -> anyhow::Result<Response<Full<bytes::Bytes>>> {
let path = req.uri().path();
let method = req.method();
info!("API request: {} {}", method, path);
match (method.as_str(), path) {
("GET", "/config") => endpoints::handle_get_config(req, config).await,
("POST", "/config") => endpoints::handle_post_config(req, config).await,
("GET", "/health") => endpoints::handle_health_check(req).await,
_ => {
let response = Response::builder()
.status(404)
.body(Full::new(bytes::Bytes::from("Not Found".to_string())))
.unwrap();
Ok(response)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_api_server_address_parsing() {
let addr: SocketAddr = "127.0.0.1:8081".parse().unwrap();
assert_eq!(addr.port(), 8081);
assert_eq!(addr.ip().to_string(), "127.0.0.1");
}
}