From ff73f501a48d42780e823330535d62d55a775492 Mon Sep 17 00:00:00 2001 From: _ <> Date: Sat, 3 Apr 2021 16:21:59 +0000 Subject: [PATCH] :construction: --- crates/ptth_core/src/http_serde.rs | 2 + crates/ptth_server/src/file_server/mod.rs | 45 ++++++++++++++++++----- 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/crates/ptth_core/src/http_serde.rs b/crates/ptth_core/src/http_serde.rs index 07ed0f5..f533925 100644 --- a/crates/ptth_core/src/http_serde.rs +++ b/crates/ptth_core/src/http_serde.rs @@ -90,6 +90,7 @@ pub enum StatusCode { NoContent, // 204 PartialContent, // 206 + NotModified, // 304 TemporaryRedirect, // 307 BadRequest, // 400 @@ -114,6 +115,7 @@ impl From for hyper::StatusCode { StatusCode::NoContent => Self::NO_CONTENT, StatusCode::PartialContent => Self::PARTIAL_CONTENT, + StatusCode::NotModified => Self::NOT_MODIFIED, StatusCode::TemporaryRedirect => Self::TEMPORARY_REDIRECT, StatusCode::BadRequest => Self::BAD_REQUEST, diff --git a/crates/ptth_server/src/file_server/mod.rs b/crates/ptth_server/src/file_server/mod.rs index 3f70878..b5956c7 100644 --- a/crates/ptth_server/src/file_server/mod.rs +++ b/crates/ptth_server/src/file_server/mod.rs @@ -121,13 +121,27 @@ async fn serve_dir_json ( #[instrument (level = "debug", skip (f))] async fn serve_file ( mut f: File, - should_send_body: bool, - range: range::ValidParsed + client_wants_body: bool, + range: range::ValidParsed, + if_none_match: Option <&Vec >, ) -> Result { + // Tripping the etag through UTF-8 isn't the best way to encourage it to + // be valid ASCII, but if I make it binary I might accidentally pass the + // hash binary as a header, which is not valid. + + let etag = get_file_etag (&f).await.map (String::into_bytes); + let client_cache_hit = match &etag { + None => false, + Some (actual) => match &if_none_match { + None => false, + Some (if_none_match) => &actual == if_none_match, + } + }; + let (tx, rx) = channel (1); - let body = if should_send_body { + let body = if client_wants_body && ! client_cache_hit { Some (rx) } else { @@ -143,7 +157,7 @@ async fn serve_file ( let seek = SeekFrom::Start (range.start); f.seek (seek).await?; - if should_send_body { + if body.is_some () { tokio::spawn (async move { let mut bytes_sent = 0; let mut bytes_left = content_length; @@ -194,7 +208,7 @@ async fn serve_file ( // The intended semantics I'm using are: // - etag - Some random hashed value that changes whenever the metadata // (name, inode number, length, mtime) of a file changes. Also changes - // on new server instance. + // on new server instance. Maybe. // - no-cache - Clients and the relay can store this, but should revalidate // with the origin server (us) because only we can check if the file // changed on disk. @@ -203,7 +217,9 @@ async fn serve_file ( // consider it stale. response.header ("cache-control".to_string (), b"no-cache,max-age=0".to_vec ()); - response.header ("etag".to_string (), rusty_ulid::generate_ulid_string ().into_bytes ()); + etag.map (|etag| { + response.header ("etag".to_string (), etag); + }); response.header (String::from ("accept-ranges"), b"bytes".to_vec ()); if range_requested { @@ -215,11 +231,14 @@ async fn serve_file ( response.header (String::from ("content-length"), range.end.to_string ().into_bytes ()); } - if should_send_body { - response.content_length = Some (content_length); + if client_cache_hit { + response.status_code (StatusCode::NotModified); + } + else if ! client_wants_body { + response.status_code (StatusCode::NoContent); } else { - response.status_code (StatusCode::NoContent); + response.content_length = Some (content_length); } if let Some (body) = body { @@ -229,6 +248,12 @@ async fn serve_file ( Ok (response) } +async fn get_file_etag (f: &File) -> Option +{ + let md = f.metadata ().await; + None +} + // Pass a request to the internal decision-making logic. // When it returns, prettify it as HTML or JSON based on what the client // asked for. @@ -293,7 +318,7 @@ pub async fn serve_all ( file, send_body, range, - }) => serve_file (file.into_inner (), send_body, range).await?, + }) => serve_file (file.into_inner (), send_body, range, headers.get ("if-none-match")).await?, MarkdownErr (e) => { #[cfg (feature = "markdown")] {