main
parent
3389292457
commit
ff73f501a4
|
@ -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,
|
||||||
|
|
|
@ -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")]
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue