-
Notifications
You must be signed in to change notification settings - Fork 83
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
Add range
submodule
#180
Add range
submodule
#180
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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. |
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.
Typo: within
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
TryFrom
andToHeaderValues
. 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
Headers
type 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.