|
| 1 | +use crate::log; |
| 2 | +use crate::{Body, Endpoint, Request, Response, Result, StatusCode}; |
| 3 | +use std::io; |
| 4 | +use std::path::Path; |
| 5 | + |
| 6 | +use async_std::path::PathBuf as AsyncPathBuf; |
| 7 | +use async_trait::async_trait; |
| 8 | + |
| 9 | +pub(crate) struct ServeFile { |
| 10 | + path: AsyncPathBuf, |
| 11 | +} |
| 12 | + |
| 13 | +impl ServeFile { |
| 14 | + /// Create a new instance of `ServeFile`. |
| 15 | + pub(crate) fn init(path: impl AsRef<Path>) -> io::Result<Self> { |
| 16 | + let file = path.as_ref().to_owned().canonicalize()?; |
| 17 | + Ok(Self { |
| 18 | + path: AsyncPathBuf::from(file), |
| 19 | + }) |
| 20 | + } |
| 21 | +} |
| 22 | + |
| 23 | +#[async_trait] |
| 24 | +impl<State: Clone + Send + Sync + 'static> Endpoint<State> for ServeFile { |
| 25 | + async fn call(&self, _: Request<State>) -> Result { |
| 26 | + match Body::from_file(&self.path).await { |
| 27 | + Ok(body) => Ok(Response::builder(StatusCode::Ok).body(body).build()), |
| 28 | + Err(e) if e.kind() == io::ErrorKind::NotFound => { |
| 29 | + log::warn!("File not found: {:?}", &self.path); |
| 30 | + Ok(Response::new(StatusCode::NotFound)) |
| 31 | + } |
| 32 | + Err(e) => Err(e.into()), |
| 33 | + } |
| 34 | + } |
| 35 | +} |
| 36 | + |
| 37 | +#[cfg(test)] |
| 38 | +mod test { |
| 39 | + use super::*; |
| 40 | + |
| 41 | + use crate::http::{Response, Url}; |
| 42 | + use std::fs::{self, File}; |
| 43 | + use std::io::Write; |
| 44 | + |
| 45 | + fn serve_file(tempdir: &tempfile::TempDir) -> crate::Result<ServeFile> { |
| 46 | + let static_dir = tempdir.path().join("static"); |
| 47 | + fs::create_dir(&static_dir)?; |
| 48 | + |
| 49 | + let file_path = static_dir.join("foo"); |
| 50 | + let mut file = File::create(&file_path)?; |
| 51 | + write!(file, "Foobar")?; |
| 52 | + |
| 53 | + Ok(ServeFile::init(file_path)?) |
| 54 | + } |
| 55 | + |
| 56 | + fn request(path: &str) -> crate::Request<()> { |
| 57 | + let request = |
| 58 | + crate::http::Request::get(Url::parse(&format!("http://localhost/{}", path)).unwrap()); |
| 59 | + crate::Request::new((), request, vec![]) |
| 60 | + } |
| 61 | + |
| 62 | + #[async_std::test] |
| 63 | + async fn should_serve_file() { |
| 64 | + let tempdir = tempfile::tempdir().unwrap(); |
| 65 | + let serve_file = serve_file(&tempdir).unwrap(); |
| 66 | + |
| 67 | + let mut res: Response = serve_file.call(request("static/foo")).await.unwrap().into(); |
| 68 | + |
| 69 | + assert_eq!(res.status(), 200); |
| 70 | + assert_eq!(res.body_string().await.unwrap(), "Foobar"); |
| 71 | + } |
| 72 | + |
| 73 | + #[async_std::test] |
| 74 | + async fn should_serve_404_when_file_missing() { |
| 75 | + let serve_file = ServeFile { |
| 76 | + path: AsyncPathBuf::from("gone/file"), |
| 77 | + }; |
| 78 | + |
| 79 | + let res: Response = serve_file.call(request("static/foo")).await.unwrap().into(); |
| 80 | + |
| 81 | + assert_eq!(res.status(), 404); |
| 82 | + } |
| 83 | +} |
0 commit comments