folk-plugin-http 0.2.0

HTTP plugin for Folk — accepts connections via hyper and dispatches to PHP workers
Documentation
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()
        }
    }
}