From 6cb479acc61ab19f655cedd878283b214e352a3d Mon Sep 17 00:00:00 2001 From: Vika Date: Tue, 4 Oct 2022 23:55:44 +0300 Subject: media: Use ETag and If-None-Match Note: this requires a reindex of the media database. For the default CAS backend, use the following: ```bash for i in */*/*/*/*.json; do etag="$(echo $i | sed -e 's/\///g' -e 's/\.json$//')"; mv "$i" "$i.bak" cat "$i.bak" | jq '. + { "etag": '\""$etag"\"'}' > "$i" rm "$i.bak" done ``` This change is backwards compatible, but caching headers won't be emitted without etags present in the metadata. Actual etags are backend-specific and might differ from backend to backend. --- kittybox-rs/src/media/mod.rs | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) (limited to 'kittybox-rs/src/media/mod.rs') diff --git a/kittybox-rs/src/media/mod.rs b/kittybox-rs/src/media/mod.rs index 0ce2ec9..ce08704 100644 --- a/kittybox-rs/src/media/mod.rs +++ b/kittybox-rs/src/media/mod.rs @@ -1,6 +1,10 @@ +use std::convert::TryFrom; + use axum::{ extract::{Extension, Host, multipart::Multipart, Path}, - response::{IntoResponse, Response}, headers::HeaderValue, + response::{IntoResponse, Response}, + headers::{Header, HeaderValue, IfNoneMatch, HeaderMapExt}, + TypedHeader, }; use kittybox_util::error::{MicropubError, ErrorType}; use kittybox_indieauth::Scope; @@ -65,6 +69,7 @@ pub(crate) async fn upload( pub(crate) async fn serve( Host(host): Host, Path(path): Path, + if_none_match: Option>, Extension(blobstore): Extension ) -> Response { use axum::http::StatusCode; @@ -72,6 +77,23 @@ pub(crate) async fn serve( match blobstore.read_streaming(&host, path.as_str()).await { Ok((metadata, stream)) => { tracing::debug!("Metadata: {:?}", metadata); + + let etag = if let Some(etag) = metadata.etag { + let etag = format!("\"{}\"", etag).parse::().unwrap(); + + if let Some(TypedHeader(if_none_match)) = if_none_match { + tracing::debug!("If-None-Match: {:?}", if_none_match); + // If-None-Match is a negative precondition that + // returns 304 when it doesn't match because it + // only matches when file is different + if !if_none_match.precondition_passes(&etag) { + return StatusCode::NOT_MODIFIED.into_response() + } + } + + Some(etag) + } else { None }; + let mut r = Response::builder(); { let headers = r.headers_mut().unwrap(); @@ -89,8 +111,13 @@ pub(crate) async fn serve( HeaderValue::from_str(&length.to_string()).unwrap() ); } + if let Some(etag) = etag { + headers.typed_insert(etag); + } } - r.body(axum::body::StreamBody::new(stream)).unwrap().into_response() + r.body(axum::body::StreamBody::new(stream)) + .unwrap() + .into_response() }, Err(err) => match err.kind() { ErrorKind::NotFound => { -- cgit 1.4.1