summaryrefslogtreecommitdiff
path: root/src/handlers/staticfile.rs
blob: a7653157d01ea43618698493a02ebe693fd7acbf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
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<T> Handle<T> for StaticFile {
    async fn handle(&self, request: Request<T>) -> Result<Response, handlers::Error> {
        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<Self> {
        match value {
            Value::Table(table) => Ok(Self {
                path: PathBuf::from(OsString::from_vec(
                    table.get::<mlua::String>("path")?.as_bytes().to_vec(),
                )),
                mime: table.get::<String>("mime")?,
            }),
            _ => Err(mlua::Error::runtime("expected table")),
        }
    }
}