zip_static_handler 0.22.0

Static file handler from zip archive
Documentation
use http_body_util::{Either, Empty, Full};
use hyper::body::Bytes;
use hyper::server::conn::http1;
use hyper::service::service_fn;
use hyper_util::rt::TokioIo;
use reqwest::Client;
use std::convert::Infallible;
use std::sync::{Arc, LazyLock};
use tokio::net::TcpListener;
use tokio::spawn;
use zip_static_handler::github::zip_download_branch_url;
use zip_static_handler::handler::{Handler, HeaderSelector, HeadersAndCompression};
use zip_static_handler::http::headers::{Line, ALLOW, CACHE_CONTROL, CONTENT_TYPE};

static DEFAULT_HEADERS: LazyLock<Vec<Line>> =
    LazyLock::new(|| vec![Line::with_array_ref_value(ALLOW, b"GET, HEAD")]);

async fn download(url: &str) -> Result<Vec<u8>, reqwest::Error> {
    let response = Client::default().get(url).send().await?;
    if !response.status().is_success() {
        panic!("failed to download {url} ({})", response.status().as_str());
    }
    Ok(response.bytes().await?.to_vec())
}

struct HSelector;

impl HeaderSelector for HSelector {
    fn headers_for_extension(
        &self,
        _filename: &str,
        extension: &str,
    ) -> Option<HeadersAndCompression> {
        match extension {
            "html" => Some(headers_and_compression(
                Some(b"text/html"),
                Some(b"no-cache"),
                true,
            )),
            "css" => Some(headers_and_compression(
                Some(b"text/css"),
                Some(b"no-cache"),
                true,
            )),
            "json" => Some(headers_and_compression(
                Some(b"application/json"),
                Some(b"no-cache"),
                true,
            )),
            "ico" => Some(headers_and_compression(
                Some(b"image/x-icon"),
                Some(b"max-age=604800,immutable"),
                true,
            )),
            "jpg" => Some(headers_and_compression(
                Some(b"image/jpg"),
                Some(b"max-age=604800,immutable"),
                true,
            )),
            "webp" => Some(headers_and_compression(
                Some(b"image/webp"),
                Some(b"max-age=604800,immutable"),
                true,
            )),
            "307" => Some(headers_and_compression(None, Some(b"no-cache"), false)),
            "308" => Some(headers_and_compression(None, None, false)),
            _ => None,
        }
    }

    fn error_headers(&self) -> &'static [Line] {
        default_headers()
    }
}

fn default_headers() -> &'static [Line] {
    DEFAULT_HEADERS.as_slice()
}

fn headers_and_compression(
    content_type: Option<&'static [u8]>,
    cache_control: Option<&'static [u8]>,
    compressible: bool,
) -> HeadersAndCompression {
    let mut headers = default_headers().to_vec();
    if let Some(content_type) = content_type {
        headers.push(Line::with_slice_value(CONTENT_TYPE, content_type));
    }
    if let Some(cache_control) = cache_control {
        headers.push(Line::with_slice_value(CACHE_CONTROL, cache_control));
    }
    HeadersAndCompression {
        headers,
        compressible,
        redirection: content_type.is_none(),
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let zip = download(&zip_download_branch_url(
        "programingjd",
        "about.programingjd.me",
        "main",
    ))
    .await?;
    let listener = TcpListener::bind(("127.0.0.1", 8080u16)).await?;
    let handler = Arc::new(
        Handler::builder()
            .with_zip_prefix("about.programingjd.me-main/")
            .with_custom_header_selector(&HSelector)
            .with_zip(zip)
            .try_build()?,
    );
    loop {
        let (stream, _remote_address) = listener.accept().await?;
        let io = TokioIo::new(stream);
        let handler = handler.clone();
        spawn(async move {
            let _ = http1::Builder::new()
                .serve_connection(
                    io,
                    service_fn(move |request| {
                        let handler = handler.clone();
                        async move {
                            Ok::<hyper::Response<Either<Full<Bytes>, Empty<Bytes>>>, Infallible>(
                                handler.handle_hyper_request(request),
                            )
                        }
                    }),
                )
                .await;
        });
    }
}