use crate::{ handler::{self, Handle}, request::{Method, Request}, response::{Body, Response, Status}, }; use std::{ffi::OsString, os::unix::ffi::OsStringExt, path::PathBuf}; use mlua::{FromLua, Value}; use tokio::{ fs::File, io::{self}, }; #[derive(Debug, thiserror::Error)] pub enum Error { #[error("io error: {0}")] Io(#[from] io::Error), } #[derive(Debug, Clone)] pub struct StaticFile { path: PathBuf, mime: String, } impl Handle for StaticFile { async fn handle(self, request: Request) -> Result { match request.method() { Method::Get | Method::Head => match File::open(&self.path).await { Ok(file) => { let metadata = file.metadata().await.map_err(Error::Io)?; if metadata.is_file() { Ok(Response::builder() .status(Status::Ok) .headers([ ("content-length", format!("{}", metadata.len())), ("content-type", self.mime), ]) .body(Body::File(file))) } else { Ok(Response::builder() .status(Status::NotFound) .body(Body::Empty)) } } Err(e) if matches!(e.kind(), io::ErrorKind::NotFound) => Ok(Response::builder() .status(Status::NotFound) .body(Body::Empty)), Err(e) => Err(Error::Io(e))?, }, } } } impl FromLua for StaticFile { fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result { match value { Value::Table(table) => Ok(Self { path: PathBuf::from(OsString::from_vec( table.get::("path")?.as_bytes().to_vec(), )), mime: table.get::("mime")?, }), _ => Err(mlua::Error::runtime("expected table")), } } }