1use anyhow::anyhow;
2use async_trait::async_trait;
3use hyper::{header, Body, Method, Response, StatusCode};
4use regex::Regex;
5use tokio::fs::File;
6use tokio::io::AsyncReadExt; use crate::{
9 context::Context,
10 types::{HttpRequest, HttpResonse},
11 view::View,
12};
13
14pub struct StaticFiles {
15 root: String,
16 re_path: Regex,
17}
18
19impl StaticFiles {
20 pub fn new(root: String, re_path: Regex) -> Self {
21 Self { root, re_path }
22 }
23}
24
25#[async_trait]
26impl View for StaticFiles {
27 fn re_path(&self) -> Regex {
28 self.re_path.clone()
29 }
30 fn methods(&self) -> Vec<Method> {
31 vec![Method::GET]
32 }
33 async fn call(&self, req: &mut HttpRequest, ctx: &mut Context) -> anyhow::Result<HttpResonse> {
34 let view_args = &ctx.view_args;
35 let file_path = view_args
36 .as_ref()
37 .ok_or(anyhow!("no view_args"))?
38 .get("file_path")
39 .ok_or(anyhow!("no file_path"))?;
40 let file_path = format!("{}/{}", self.root, file_path);
41 let file_path = std::path::Path::new(&file_path);
42 if !file_path.exists() {
43 return Ok(Response::builder()
44 .status(StatusCode::NOT_FOUND)
45 .body(Body::from("Not found"))?);
46 }
47
48 let mut file = tokio::fs::File::open(file_path).await?;
49 let mut buf: Vec<u8> = Vec::new();
50 file.read_to_end(&mut buf).await?;
51 let mime = mime_guess::from_path(file_path).first_or_octet_stream();
52 let mut r = Response::builder()
53 .status(StatusCode::OK)
54 .body(Body::from(buf))?;
55 r.headers_mut()
56 .insert(header::CONTENT_TYPE, (&mime.to_string()).parse()?);
57 Ok(r)
58 }
59}