use std::{ffi::OsString, os::unix::ffi::OsStringExt, path::PathBuf, time}; use mlua::{FromLua, Value}; use tokio::{ fs::{self, File}, io, }; use crate::{ Handle, handlers, request::Request, response::{self, Response}, }; #[derive(Debug, thiserror::Error)] pub enum Error { #[error("io error: {0}")] Io(#[from] io::Error), #[error("http error: {0}")] Http(#[from] http::Error), } #[derive(Debug, Clone)] pub struct StaticFile { path: PathBuf, mime: String, } impl Handle for StaticFile { async fn handle(&self, request: Request) -> Result { if let http::Method::GET | http::Method::HEAD = request.inner().method().clone() { if !fs::try_exists(&self.path).await.map_err(Error::Io)? { return Ok(Response::new( http::Response::builder() .status(http::StatusCode::NOT_FOUND) .body(response::Body::Empty) .map_err(Error::Http)?, )); } let file = File::open(&self.path).await.map_err(Error::Io)?; let metadata = file.metadata().await.map_err(Error::Io)?; let now = time::SystemTime::now(); let date = httpdate::fmt_http_date(now); let response = http::Response::builder() .status(http::StatusCode::OK) .header("CONTENT-LENGTH", metadata.len()) .header("CONTENT-TYPE", &self.mime) .header("DATE", date); match request.inner().method().clone() { http::Method::GET => Ok(Response::new( response .body(response::Body::File(file)) .map_err(Error::Http)?, )), http::Method::HEAD => Ok(Response::new( response.body(response::Body::Empty).map_err(Error::Http)?, )), _ => unreachable!(), } } else { Err(handlers::Error::Unsupported) } } } 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")), } } }