Conversation
|
We should probably do it in a separate PR, and I'm not sure how idiomatic this would be, but |
Indeed this is orthogonal to this PR and deserve a separate discussion. |
|
Hello @yoshuawuyts, In the scope of #99 I see you are progressing well on #222, #224 , #225, #220. Some part of this PR need to be reworked, I identified a few bugs (regarding RFCs), I prefer to wait for your comments before going forward. Cheers, |
|
@ririsoft it's been a few weeks since we've actually released the patches you linked above, and we're definitely on track in that direction. Apologies that it took a while to figure out how we wanted the headers structured. If you're still interested in adding typed headers for |
Sure, I followed the discussions and will rewrite this PR with latest design. I am just a bit busy at work those days, I plan to come back on this next week. |
|
I have resumed the work here: https://github.com/ririsoft/http-types/tree/headers-range-wip |
|
Sorry guys, I worked less on this than initially expected but I am progressing and almost there. |
03d02ec to
c5fd4e7
Compare
|
Hi @yoshuawuyts, The work is ready for review on my side. I am very open to any suggestions of course. Also the RFC specification is not straightforward, you might want to have a 4 eyes check on my implementation. Cheers. |
src/range/accept_ranges.rs
Outdated
| const NONE: &'static str = "none"; | ||
|
|
||
| /// Create a new AcceptRange which does not accept range requests. | ||
| pub fn new() -> Self { |
There was a problem hiding this comment.
new setting the value to "none" might be confusing here since the widely used default is "bytes". Maybe we should name it new_none or a better idea ?
src/range/accept_ranges.rs
Outdated
| } | ||
|
|
||
| /// Returns the supported unit if any. | ||
| pub fn other(&self) -> &Option<String> { |
There was a problem hiding this comment.
I am not a fan of returning &Option<String>>. Maybe Option<&str> is better ?
src/range/accept_ranges.rs
Outdated
| let s = match self.other { | ||
| Some(ref other) => other.as_str(), | ||
| None if self.bytes => Self::BYTES, | ||
| None => Self::NONE, | ||
| }; |
There was a problem hiding this comment.
This could be factorized with the Display implementation. Let me known if you want that.
src/range/byte_content_range.rs
Outdated
|
|
||
| /// Create a ByteRanges from a string. | ||
| pub(crate) fn from_str(s: &str) -> crate::Result<Self> { | ||
| let fn_err = || { |
There was a problem hiding this comment.
There is no error code specified for invalid Content-Range header in a response. I chose to use the same error code as the one used for bad range requests. Maybe I should add this as a comment ?
src/range/byte_ranges.rs
Outdated
| self.ranges.get(0).copied() | ||
| } | ||
|
|
||
| /// Validates that the ranges are withing the expected document size. |
yoshuawuyts
left a comment
There was a problem hiding this comment.
Hey ririsoft, thanks for updating this PR. I spent some time reviewing it today, and I have some thoughts.
Bytes Prefix
When reviewing this PR I was looking for the Range header, but instead I found ByteRange and ByteRanges. It wasn't immediately clear to me which is which, and for consistency with the other typed headers I would've expected the header name to exist without the Byte prefix.
This has some implications for the API, but I'd like to find a way for us to drop the Byte prefix from the header names.
Assume the RangeUnit is always bytes?
The RFC states that RangeUnits can be expected to be expanded in the future through RFC. Though in the past 6 years this has not happened, which makes it questionable to which degree we need to consider this. We could make the assumption that the only two types that may ever be sent are "none" and bytes.
What if we assume the units may be extensible?
If we don't create specific structs for BytesRange, etc. then this route means points us more directly towards enums. We could define a RangeDirective enum which is #[non_exhaustive] and contains None and Bytes members. That is the type that's communicated in the Range headers.
Additionally we can add a From<(u64, u64)> impl to construct a RangeDirective instance, and make the bound in Range::push take impl Into<RangeDirective>.
Extensibility of range units
However this leads to an issue: currently the range set of headers is hard-coded to only operate on bytes. Hence the prefix. I guess the intended path to add support for more range units is to add additional FooRange and BarRange headers.
Instead I would like to suggest switching it around: create a RangeUnits enum which is tagged #[non_exhaustive] and None and Bytes members, and then have Range , ContentRange, and AcceptRanges headers. This will allow us to add more range types in the future without breaking anything.
AcceptRanges constructor
It seems AcceptRanges is working around not having a RangeUnits enum. I think AcceptRanges::new should take a RangeUnits enum, and we can keep a AcceptRanges::new_bytes as a shorthand.
suffix-length
I was reading about ranges on mdn and I'm confused about how the suffixes are exposed in our API? Do you have a good sense of how they work, and how we could implement them?
Conclusion
Despite having feedback on some of the design, I think we're very well on our way to getting where we need to be! — I'm incredibly excited for this work, and how it'll enable us to implement http-rs/tide#687. Thanks so much for your work on this!
|
Hi @yoshuawuyts thank you very much for your review. I am pretty much in line globally.
I initially worked on a version with a RangeUnit enum, like the one you mentioned, opening the door to other range units if the RFC evolves in the future. But I realized that this is a lot of boilerplate for every day use since only bytes unite are used today in practice. So I went for a compromise with this Byte prefix thing which is not very much satisfying at the end, I agree.
I would go for that design too, but I am not sure about having However, by removing A third option would be something like: enum RangeUnit {
Bytes
Other(String)
}This allows for usage of non standard units, but not great either. If we go for the
You can see them as relative seek from either start or end. So far I do not provide further helper over it, I am just exposing them with an optional value. In
Thank you for your great feedback. This leaves us with the following choices:
Not an easy decision. A little daemon on my left shoulder says "come on, take the challenge and go with those damn enums, you are a rustacean or not ?", a little angel on my right shoulder says "come on, you know this is over-engineering, right ?" Do you want me to have a try with the What do you think ? After this we need to talk about this comment which could impact the design of the |
Assume the RangeUnit is always bytes?
I think that's fine though, especially if we mark the enum as
Not necessarily; I think at this layer it's more important that the implementation is correct and understandable, than necessarily providing the best end-user ergonomics. Then in Tide we can use the implementation we've defined here to truly make it ergonomic. suffix-length
Fantastic! Happy to punt this topic for now then (: Conclusion
I think this is indeed the route I'd like to see explored. Thanks so much for taking the time to work through this; I feel I say this everytime it comes up, but I'm incredibly excited for this! |
This introduce a new 'range' module to support encoding, decoding and validation of HTTP range requests.
c5fd4e7 to
9e0b94c
Compare
Here we are ! The new design is I came to this design because each header exposes different types that I could not unify across one single There is a few points that I was not sure about, such having new constructors taking a impl Into<...> for each header. Do not hesitate to challenge my proposal I am fully open for suggestions. |
| let fn_err = || { | ||
| Error::from_str( | ||
| StatusCode::BadRequest, | ||
| "Invalid Range header for byte ranges", |
There was a problem hiding this comment.
In my previous commits I add several error cases to handle, let closure here is no more justified I should move this block to the match block below
| } | ||
| } | ||
|
|
||
| impl FromStr for BytesRange { |
There was a problem hiding this comment.
Maybe I should just provide a pub(crate) fn from_str implementation and not do it as impl FromStr to remain consistant with the rest of the code.
| /// # fn main() -> http_types::Result<()> { | ||
| /// # | ||
| /// use http_types::range::{BytesRange, BytesRangeSet, Range}; | ||
| /// use http_types::{Method, Request, StatusCode, Url}; | ||
| /// use std::convert::TryInto; | ||
| /// | ||
| /// let mut range_set = BytesRangeSet::new(); | ||
| /// range_set.push(0, 500); | ||
| /// let range = Range::Bytes(range_set); | ||
| /// | ||
| /// let mut req = Request::new(Method::Get, Url::parse("http://example.com").unwrap()); | ||
| /// range.apply(&mut req); | ||
| /// | ||
| /// let range = Range::from_headers(req)?.unwrap(); | ||
| /// | ||
| /// if let Range::Bytes(range_set) = range { | ||
| /// let err = range_set.match_size(350).unwrap_err(); | ||
| /// assert_eq!(err.status(), StatusCode::RequestedRangeNotSatisfiable); | ||
| /// } | ||
| /// # | ||
| /// # Ok(()) } |
There was a problem hiding this comment.
I could make this example shorter without using a Request
|
@ririsoft In #180 (review) I proposed to go for In #180 (comment) it sounds like you attempted to unify these into a single type, which then didn't work out -- am I reading that correctly? |
Hello @yoshuawuyts , |
|
@ririsoft that's all good! -- I think what I may do is fork your PR, and then file a patch against your PR. I think perhaps at this point that may be the easiest way to communicate what I mean 😅 |
Yeah we are reaching the limit of asynchronous talk here I am afraid :). A small patch says a lot more sometimes 👍 Also I had a look at what they did on hyperium: https://docs.rs/headers/0.3.3/headers/struct.Range.html. |
|
I am not using http-rs anymore and I am thus not interested in working on this PR further. |
This introduce a new 'range' module
to support encoding, decoding and validation of HTTP range requests.
This could be a helper to implement http-rs/tide#63 and enhance tide's serve_dir with range requests.
This is also an experiment toward typed headers discussed in #99.
I am using a simple design where the typed header implements
TryFromandToHeaderValues. This has the advantage to be fully compatible with the current header APIs. However I believe we could push Rust type system a little further.For instance we could enhance the
Headerstype with a new method and trait:One could then retrieve a typed value directly from the headers :
I am not sure if it works, but I could add a few commits on this pull request if you want a try.
Thanks in advance for your comments !
Cheers.