diff --git a/Cargo.lock b/Cargo.lock index 358f3d3..1f7add5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -43,13 +43,13 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.59" +version = "0.1.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6e93155431f3931513b243d371981bb2770112b370c82745a1d19d2f99364" +checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn 1.0.104", + "syn 2.0.22", ] [[package]] @@ -135,6 +135,7 @@ dependencies = [ "async-recursion", "axum", "bytes", + "futures", "hyper", "log", "pretty_env_logger", @@ -213,6 +214,21 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.28" @@ -220,6 +236,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -228,6 +245,17 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +[[package]] +name = "futures-executor" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + [[package]] name = "futures-io" version = "0.3.28" @@ -263,9 +291,13 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ + "futures-channel", "futures-core", + "futures-io", "futures-macro", + "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", "slab", diff --git a/Cargo.toml b/Cargo.toml index fb36722..ba6c13a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ bytes = "1" rand = "0.8.5" async-recursion = "1.0.0" walkdir = "2" +futures = "0.3" log = "0.4" pretty_env_logger = "0.5.0" archived = { path = "./archived" } diff --git a/src/view.rs b/src/view.rs index 68ad5d9..0b76486 100644 --- a/src/view.rs +++ b/src/view.rs @@ -10,8 +10,8 @@ use axum::{ }; use bytes::Bytes; -use hyper::StatusCode; -use tokio::fs::File; +use hyper::{http::HeaderValue, StatusCode}; +use tokio::{fs::File, runtime::Handle}; use tokio_util::io::ReaderStream; pub enum ViewSuccess { @@ -20,7 +20,7 @@ pub enum ViewSuccess { } pub enum ViewError { - NotFound, // 404 + NotFound, // 404 InternalServerError, // 500 } @@ -28,20 +28,67 @@ impl IntoResponse for ViewSuccess { fn into_response(self) -> Response { match self { ViewSuccess::FromDisk(file) => { + // get handle to current runtime + // i use this to block on futures here (not async) + let handle = Handle::current(); + let _ = handle.enter(); + + // read the metadata of the file on disk + // this function isn't async + // .. so we have to use handle.block_on() to get the metadata + let metadata = futures::executor::block_on(file.metadata()); + + // if we error then return 500 + if metadata.is_err() { + error!("failed to read metadata from disk"); + return ViewError::InternalServerError.into_response(); + } + + // unwrap (which we know is safe) and read the file size as a string + let metadata = metadata.unwrap(); + let len_str = metadata.len().to_string(); + + debug!("file is {} bytes on disk", &len_str); + + // HeaderValue::from_str will never error if only visible ASCII characters are passed (32-127) + // .. so unwrapping this should be fine + let content_length = HeaderValue::from_str(&len_str).unwrap(); + // create a streamed body response (we want to stream larger files) let reader = ReaderStream::new(file); let stream = StreamBody::new(reader); - stream.into_response() + // extract mutable headers from the response + let mut res = stream.into_response(); + let headers = res.headers_mut(); + + // clear headers, browser can imply content type + headers.clear(); + + // insert Content-Length header + // that way the browser shows how big a file is when it's being downloaded + headers.insert("Content-Length", content_length); + + res } ViewSuccess::FromCache(data) => { // extract mutable headers from the response - let mut res = data.into_response(); + let mut res = data.clone().into_response(); let headers = res.headers_mut(); // clear the headers, let the browser imply it headers.clear(); + /* // we do not need this for FromCache because it works fine + // read the length of the data as a string + let len_str = data.len().to_string(); + + // HeaderValue::from_str will never error if only visible ASCII characters are passed (32-127) + // .. so this should be fine + let content_length = HeaderValue::from_str(&len_str).unwrap(); + headers.append("Content-Length", content_length); + */ + res } } @@ -61,7 +108,7 @@ impl IntoResponse for ViewError { ViewError::InternalServerError => { // convert string into response, change status code let mut res = "internal server error!".into_response(); - *res.status_mut() = StatusCode::NOT_FOUND; + *res.status_mut() = StatusCode::INTERNAL_SERVER_ERROR; res }