use std::sync::Arc;
use anyhow::Result;
use axum::Router;
use axum::body::Body;
use axum::extract::State;
use axum::http::{Request, Response};
use axum::routing::any;
use folk_api::Executor;
use tokio::net::TcpListener;
use tokio::sync::watch;
use tracing::error;
use crate::config::HttpConfig;
use crate::payload::{decode_response, encode_request};
pub struct HttpServer {
config: HttpConfig,
executor: Arc<dyn Executor>,
}
impl HttpServer {
pub fn new(config: HttpConfig, executor: Arc<dyn Executor>) -> Self {
Self { config, executor }
}
pub async fn run(self, mut shutdown: watch::Receiver<bool>) -> Result<()> {
let executor = self.executor.clone();
let app = Router::new()
.route("/{*path}", any(handle))
.route("/", any(handle))
.with_state(executor);
let listener = TcpListener::bind(self.config.listen).await?;
axum::serve(listener, app)
.with_graceful_shutdown(async move {
loop {
if shutdown.changed().await.is_err() || *shutdown.borrow() {
break;
}
}
})
.await?;
Ok(())
}
}
async fn handle(State(executor): State<Arc<dyn Executor>>, req: Request<Body>) -> Response<Body> {
let payload = match encode_request(req).await {
Ok(p) => p,
Err(e) => {
error!(error = ?e, "encode request");
return Response::builder()
.status(500)
.body(Body::from("encode error"))
.unwrap();
}
};
let response_value = match executor.execute_value("http.handle", payload).await {
Ok(v) => v,
Err(e) => {
error!(error = ?e, "dispatch to worker");
return Response::builder()
.status(502)
.body(Body::from("worker error"))
.unwrap();
}
};
match decode_response(response_value) {
Ok(r) => r,
Err(e) => {
error!(error = ?e, "decode response");
Response::builder()
.status(500)
.body(Body::from("decode error"))
.unwrap()
}
}
}