vicarian 0.4.0

Vicarian is a TLS-first reverse-proxy server with ACME support
use std::sync::Arc;

use anyhow::Result;
use async_trait::async_trait;
use bytes::Bytes;
use http::{HeaderValue, header::{STRICT_TRANSPORT_SECURITY, VIA}};
use http_body_util::BodyExt;
use pingora_core::OrErr;
use pingora_http::ResponseHeader;
use pingora_proxy::Session;
use static_web_server::handler::{
    RequestHandler as SWSHandler, RequestHandlerOpts as SWSHandlerOpts,
};

use crate::{config::StaticBackend, proxy::{E500, BackendHandler}};

// TODO: Should be own top-level module (like metrics)?

pub struct StaticHandler {
    sws_handler: SWSHandler,
}

impl StaticHandler {
    pub fn new(backend: &StaticBackend) -> Self {
        let static_root = backend.root.clone()
            .join("index.html");
        let fallback_page = std::fs::read(&static_root)
            .unwrap_or_else(|_| Vec::new());
        let opts = Arc::new(SWSHandlerOpts{
            root_dir: backend.root.clone()
                .into_std_path_buf(),
            compression: true,
            dir_listing: true,
            page_fallback: fallback_page,
            redirect_trailing_slash: true,
            ..Default::default()
        });
        Self {
            sws_handler: SWSHandler {
                opts,
            },
        }
    }
}

#[async_trait]
impl BackendHandler for StaticHandler {
    async fn handle(&self, session: &mut Session) -> Result<bool> {

        // FIXME: Check length
        let parts = session.req_header().as_owned_parts();
        let is_head = parts.method == http::Method::HEAD;

        let mut req = hyper::Request::from_parts(parts, static_web_server::body::empty());
        let resp = self.sws_handler.handle(&mut req, None).await
            .or_err(E500, "Failed to retrieve static file")?;

        let (rparts, mut body) = resp.into_parts();
        let mut header = ResponseHeader::from(rparts);
        header.insert_header(VIA, HeaderValue::from_static("1.1 Vicarian"))?;
        header.insert_header(STRICT_TRANSPORT_SECURITY, HeaderValue::from_static("max-age=31536000; includeSubDomains"))?;

        session.write_response_header(Box::new(header), false).await?;

        if !is_head {
            while let Some(frame) = body.frame().await {
                let data = frame.or_err(E500, "Failed to read static body")?;
                if let Some(bref) = data.data_ref() {
                    let bytes = bref.to_owned();
                    session.write_response_body(bytes.into(), false).await?;
                }
            }
        }
        session.write_response_body(Bytes::new().into(), true).await?;

        Ok(true)
    }
}