folk-plugin-http 0.1.1

HTTP plugin for Folk — accepts connections via hyper and dispatches to PHP workers
Documentation
//! HTTP payload encoding: Hyper ↔ MessagePack.
//!
//! Request to PHP:  msgpack map { method, uri, headers, body }
//! Response from PHP: msgpack map { status, headers, body }

use std::collections::HashMap;

use anyhow::{Context, Result};
use bytes::Bytes;
use serde::{Deserialize, Serialize};

/// Serialized HTTP request sent to PHP worker.
#[derive(Debug, Serialize, Deserialize)]
pub struct HttpRequestPayload {
    pub method: String,
    pub uri: String,
    pub headers: HashMap<String, String>,
    pub body: Bytes,
}

/// Deserialized HTTP response from PHP worker.
#[derive(Debug, Serialize, Deserialize)]
pub struct HttpResponsePayload {
    pub status: u16,
    pub headers: HashMap<String, String>,
    pub body: Bytes,
}

/// Encode a hyper Request to MessagePack bytes.
pub async fn encode_request<B>(req: hyper::Request<B>) -> Result<Bytes>
where
    B: hyper::body::Body<Data = Bytes> + Send,
    B::Error: std::error::Error + Send + Sync + 'static,
{
    let (parts, body) = req.into_parts();
    let body_bytes = http_body_util::BodyExt::collect(body)
        .await
        .context("read request body")?
        .to_bytes();

    let headers: HashMap<String, String> = parts
        .headers
        .iter()
        .filter_map(|(k, v)| Some((k.to_string(), v.to_str().ok()?.to_string())))
        .collect();

    let payload = HttpRequestPayload {
        method: parts.method.to_string(),
        uri: parts.uri.to_string(),
        headers,
        body: body_bytes,
    };

    let encoded = rmp_serde::to_vec_named(&payload).context("encode request")?;
    Ok(Bytes::from(encoded))
}

/// Decode MessagePack bytes to hyper Response.
pub fn decode_response(bytes: Bytes) -> Result<hyper::Response<http_body_util::Full<Bytes>>> {
    let payload: HttpResponsePayload = rmp_serde::from_slice(&bytes).context("decode response")?;

    let mut builder = hyper::Response::builder().status(payload.status);
    for (k, v) in &payload.headers {
        builder = builder.header(k.as_str(), v.as_str());
    }

    builder
        .body(http_body_util::Full::new(payload.body))
        .context("build response")
}