main
_ 2021-04-03 16:21:59 +00:00
parent 3389292457
commit ff73f501a4
2 changed files with 37 additions and 10 deletions

View File

@ -90,6 +90,7 @@ pub enum StatusCode {
NoContent, // 204 NoContent, // 204
PartialContent, // 206 PartialContent, // 206
NotModified, // 304
TemporaryRedirect, // 307 TemporaryRedirect, // 307
BadRequest, // 400 BadRequest, // 400
@ -114,6 +115,7 @@ impl From <StatusCode> for hyper::StatusCode {
StatusCode::NoContent => Self::NO_CONTENT, StatusCode::NoContent => Self::NO_CONTENT,
StatusCode::PartialContent => Self::PARTIAL_CONTENT, StatusCode::PartialContent => Self::PARTIAL_CONTENT,
StatusCode::NotModified => Self::NOT_MODIFIED,
StatusCode::TemporaryRedirect => Self::TEMPORARY_REDIRECT, StatusCode::TemporaryRedirect => Self::TEMPORARY_REDIRECT,
StatusCode::BadRequest => Self::BAD_REQUEST, StatusCode::BadRequest => Self::BAD_REQUEST,

View File

@ -121,13 +121,27 @@ async fn serve_dir_json (
#[instrument (level = "debug", skip (f))] #[instrument (level = "debug", skip (f))]
async fn serve_file ( async fn serve_file (
mut f: File, mut f: File,
should_send_body: bool, client_wants_body: bool,
range: range::ValidParsed range: range::ValidParsed,
if_none_match: Option <&Vec <u8>>,
) )
-> Result <Response, FileServerError> -> Result <Response, FileServerError>
{ {
// 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 (tx, rx) = channel (1);
let body = if should_send_body { let body = if client_wants_body && ! client_cache_hit {
Some (rx) Some (rx)
} }
else { else {
@ -143,7 +157,7 @@ async fn serve_file (
let seek = SeekFrom::Start (range.start); let seek = SeekFrom::Start (range.start);
f.seek (seek).await?; f.seek (seek).await?;
if should_send_body { if body.is_some () {
tokio::spawn (async move { tokio::spawn (async move {
let mut bytes_sent = 0; let mut bytes_sent = 0;
let mut bytes_left = content_length; let mut bytes_left = content_length;
@ -194,7 +208,7 @@ async fn serve_file (
// The intended semantics I'm using are: // The intended semantics I'm using are:
// - etag - Some random hashed value that changes whenever the metadata // - etag - Some random hashed value that changes whenever the metadata
// (name, inode number, length, mtime) of a file changes. Also changes // (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 // - 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 // with the origin server (us) because only we can check if the file
// changed on disk. // changed on disk.
@ -203,7 +217,9 @@ async fn serve_file (
// consider it stale. // consider it stale.
response.header ("cache-control".to_string (), b"no-cache,max-age=0".to_vec ()); 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 ()); response.header (String::from ("accept-ranges"), b"bytes".to_vec ());
if range_requested { if range_requested {
@ -215,11 +231,14 @@ async fn serve_file (
response.header (String::from ("content-length"), range.end.to_string ().into_bytes ()); response.header (String::from ("content-length"), range.end.to_string ().into_bytes ());
} }
if should_send_body { if client_cache_hit {
response.content_length = Some (content_length); response.status_code (StatusCode::NotModified);
}
else if ! client_wants_body {
response.status_code (StatusCode::NoContent);
} }
else { else {
response.status_code (StatusCode::NoContent); response.content_length = Some (content_length);
} }
if let Some (body) = body { if let Some (body) = body {
@ -229,6 +248,12 @@ async fn serve_file (
Ok (response) Ok (response)
} }
async fn get_file_etag (f: &File) -> Option <String>
{
let md = f.metadata ().await;
None
}
// Pass a request to the internal decision-making logic. // Pass a request to the internal decision-making logic.
// When it returns, prettify it as HTML or JSON based on what the client // When it returns, prettify it as HTML or JSON based on what the client
// asked for. // asked for.
@ -293,7 +318,7 @@ pub async fn serve_all (
file, file,
send_body, send_body,
range, 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) => { MarkdownErr (e) => {
#[cfg (feature = "markdown")] #[cfg (feature = "markdown")]
{ {