-
Notifications
You must be signed in to change notification settings - Fork 171
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ServeDir & ServeFile last modified headers handling #145
Conversation
8e83667
to
6b737a4
Compare
@zenria do you merge/rebase the latest master? Seems some things have changed making this hard to review. |
6b737a4
to
d64c656
Compare
I've just rebased my branch on master :) |
Thank you! Given the size of this change I'm pushing it to 0.2 as I would like to get 0.1.2 our relatively soon. |
We made some more changes to CI end of last week so you'll have to rebase Just a heads up there are also some other PRs landing this week which touch these files so you'll probably get some merge conflicts soon. |
a837668
to
cef7a29
Compare
I've rebased my branch, you were right, there was some merge conflicts ;) I've got rid of |
Thanks! I'll take a look some time this week. |
Just a heads up. I'm waiting on #173 being merged before diving into this. |
Removing this from the 0.2 milestone. It wont require breaking changes so we can always ship it in 0.2.1 |
Thank you for your work on this; I'm excited to have this functionality in tower-http! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good overall! Using httpdate
for parsing is fine. Hyper is also using it.
I'm sorry but it looks like you'll have to rebase master
again 😞 At least ServeFile
has been simplified quite a bit which should make this simpler as well.
cef7a29
to
cc21606
Compare
I've rebased my branch :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My comments don’t carry any weight in this repository, just actively reading through to see what changed.
tower-http/src/services/fs/mod.rs
Outdated
fn from_header_value(value: &http::header::HeaderValue) -> Option<IfModifiedSince> { | ||
String::from_utf8(value.as_bytes().to_vec()) | ||
.ok() | ||
.map(|value| httpdate::parse_http_date(&value).ok()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
map
followed by flatten
should be flat_map
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes! you're right. (btw this is an option, as there is no flat_map
on that type, I always forget I can use and_then
to do that.)
tower-http/src/services/fs/mod.rs
Outdated
String::from_utf8(value.as_bytes().to_vec()) | ||
.ok() | ||
.map(|value| httpdate::parse_http_date(&value).ok()) | ||
.flatten() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto
tower-http/src/services/fs/mod.rs
Outdated
} | ||
|
||
fn check_modified_headers( | ||
modified: &Option<LastModified>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Usually you don’t want references to options, but an optional reference. Option::as_ref
Can be used
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in that case this does not matter, but I agree.
.headers() | ||
.get(header::IF_MODIFIED_SINCE) | ||
.map(IfModifiedSince::from_header_value) | ||
.flatten(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto here x2
@@ -424,7 +459,7 @@ fn append_slash_on_path(uri: Uri) -> Uri { | |||
enum Output { | |||
File(FileRequest), | |||
Redirect(HeaderValue), | |||
NotFound, | |||
StatusCode(StatusCode), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Feels unfortunate that this went from one possibility to hundreds when it seemingly only needed two possibilities.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, you're right!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the check_modified_headers
can output 2 different status codes: 304 Not Modified
and 412 Precondition Failed
this is why I'm not using 2 more enum variants.
@@ -749,7 +787,6 @@ mod tests { | |||
.unwrap(); | |||
let res = svc.oneshot(request).await.unwrap(); | |||
|
|||
assert_eq!(res.headers()["content-type"], "text/plain"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why’s this get removed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, this is a merge error!
@@ -1,8 +1,7 @@ | |||
//! Service that serves a file. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why’s this get removed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this too :(
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nicely done! Thanks for the PR! I have some comments regarding the tests but those extra checks should still pass with this implementation. Also have a comment regarding some unnecessary heap allocations.
I will approve this since I personally think my comments can be easily fixed in a follow up pr by either you or me and this has been in the works for some time now. But it's up to @davidpdrsn if he wants to block this on my comments.
tower-http/src/services/fs/mod.rs
Outdated
String::from_utf8(value.as_bytes().to_vec()) | ||
.ok() | ||
.and_then(|value| httpdate::parse_http_date(&value).ok()) | ||
.map(|time| IfModifiedSince(time.into())) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can avoid heap allocation here
String::from_utf8(value.as_bytes().to_vec()) | |
.ok() | |
.and_then(|value| httpdate::parse_http_date(&value).ok()) | |
.map(|time| IfModifiedSince(time.into())) | |
std::str::from_utf8(value.as_bytes())) | |
.ok() | |
.and_then(|value| httpdate::parse_http_date(value).ok()) | |
.map(|time| IfModifiedSince(time.into())) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I was not aware of this function, thanks a lot!
tower-http/src/services/fs/mod.rs
Outdated
String::from_utf8(value.as_bytes().to_vec()) | ||
.ok() | ||
.and_then(|value| httpdate::parse_http_date(&value).ok()) | ||
.map(|time| IfUnmodifiedSince(time.into())) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
String::from_utf8(value.as_bytes().to_vec()) | |
.ok() | |
.and_then(|value| httpdate::parse_http_date(&value).ok()) | |
.map(|time| IfUnmodifiedSince(time.into())) | |
std::str::from_utf8(value.as_bytes())) | |
.ok() | |
.and_then(|value| httpdate::parse_http_date(value).ok()) | |
.map(|time| IfUnmodifiedSince(time.into())) |
.unwrap(); | ||
|
||
let res = svc.oneshot(req).await.unwrap(); | ||
assert_eq!(res.status(), StatusCode::NOT_MODIFIED); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we please also check that the body is empty here like the spec states?
.unwrap(); | ||
|
||
let res = svc.oneshot(req).await.unwrap(); | ||
assert_eq!(res.status(), StatusCode::OK); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's check that the file is actually part of the body here as a sanity check.
.unwrap(); | ||
|
||
let res = svc.oneshot(req).await.unwrap(); | ||
assert_eq!(res.status(), StatusCode::OK); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here
.unwrap(); | ||
|
||
let res = svc.oneshot(req).await.unwrap(); | ||
assert_eq!(res.status(), StatusCode::PRECONDITION_FAILED); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And this shouldn't return the body.
@@ -1124,4 +1160,63 @@ mod tests { | |||
&format!("bytes */{}", file_contents.len()) | |||
) | |||
} | |||
#[tokio::test] | |||
async fn last_modified() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comments here like in the tests for serve_file
Ci issue should be fixed by this: #206 |
Ok, thank you @Nehliin I'll update the PR with the CI & suggested fixes :) |
19f2f8d
to
a373bcd
Compare
I cannot find the |
Please open a new issue instead of resurrecting such and old PR. |
Note: This PR also includes #137, I will modify it when it will be mergedMotivation
ServeDir & ServeFile should respect http caching best practice by setting
Last-Modified
response header and handlingIf-Modified_since
&If-Unmodified-Since
requests headers.Solution
The
Last-Modified
header is added on responses of ServeFile & ServeDir.If-Modified_since
&If-Unmodified-Since
requests headers are handled so the service will respondNOT_MODIFIED
orPRECONDITION_FAILED
depending on values set into the headers.