-
Notifications
You must be signed in to change notification settings - Fork 37
http-compat: add support for convert between bhttp binary and http crate #87
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
base: main
Are you sure you want to change the base?
Conversation
Signed-off-by: Kun Lai <laikun@linux.alibaba.com>
…ttp crate Signed-off-by: Kun Lai <laikun@linux.alibaba.com>
Signed-off-by: Kun Lai <laikun@linux.alibaba.com>
Signed-off-by: Kun Lai <laikun@linux.alibaba.com>
…t is consuming Signed-off-by: Kun Lai <laikun@linux.alibaba.com>
Signed-off-by: Kun Lai <laikun@linux.alibaba.com>
…http and http Signed-off-by: Kun Lai <laikun@linux.alibaba.com>
martinthomson
left a comment
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.
Thanks for doing this. And apologies for delays. I've been busy.
I have NOT looked at the decoder. You might find some of the comments from the encoder transfer over (particularly Poll::Pending handling); I'll try to get back to this soon.
| const TRANSFER_ENCODING: &[u8] = b"transfer-encoding"; | ||
| const CHUNKED: &[u8] = b"chunked"; | ||
|
|
||
| /// An HTTP status code. |
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.
| /// An HTTP status code. | |
| /// The framing indicator ([Section 3.3 of RFC 9292](https://datatracker.ietf.org/doc/html/rfc9292#section-3.3)) |
| } | ||
| } | ||
|
|
||
| #[derive(Clone, Copy, Debug)] |
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.
| #[derive(Clone, Copy, Debug)] | |
| /// An HTTP status code. | |
| #[derive(Clone, Copy, Debug)] |
| R: ReadSeek + ?Sized, | ||
| { | ||
| let t = read_varint(r)?.ok_or(Error::Truncated)?; | ||
| let request = t == 0 || t == 2; |
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 request = t == 0 || t == 2; |
| let framing_indicator = if request { | ||
| FramingIndicator::Request(mode) | ||
| } else { | ||
| FramingIndicator::Response(mode) | ||
| }; | ||
| Ok(framing_indicator) |
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 framing_indicator = if request { | |
| FramingIndicator::Request(mode) | |
| } else { | |
| FramingIndicator::Response(mode) | |
| }; | |
| Ok(framing_indicator) | |
| match t { | |
| 0 | 2 => Ok(FramingIndicator::Request(mode)), | |
| 1 | 3 => Ok(FramingIndicator::Response(mode)), | |
| _ => unreachable!("checked by Mode::try_from()"), | |
| } |
| unsafe impl<S: Send> Send for AsyncMessage<S> {} | ||
|
|
||
| impl<S: AsyncRead + Unpin> AsyncMessage<S> { | ||
| pub fn reader_ref(&self) -> &S { |
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.
Does this work?
| pub fn reader_ref(&self) -> &S { | |
| pub(crate) fn reader_ref(&self) -> &S { |
I say that because accessing a reference to the internals might lead to errors if outside code needs to do that.
| if let Ok(trailers) = frame.into_trailers() { | ||
| // Is a trailers frame | ||
| if let Some(current) = collected_trailers { | ||
| current.extend(trailers); |
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.
If you avoid the Option you can extend always.
|
|
||
| // Then, write the trailer field section with indeterminate length mode | ||
| let field_section = | ||
| if let Some(collected_trailers) = collected_trailers.as_ref() { |
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.
Avoiding the option will simplify more.
| let mut trailers = HeaderMap::new(); | ||
| trailers.insert("Trailer-Key", "Trailer-Value".parse().unwrap()); |
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.
Consider using HeaderMap::from_iter to remove the mut.
| let mut trailers = HeaderMap::new(); | |
| trailers.insert("Trailer-Key", "Trailer-Value".parse().unwrap()); | |
| let trailers = HeaderMap::from_iter([ | |
| ("Trailer-Key", "Trailer-Value".parse().unwrap()) | |
| ]); |
|
|
||
| // Verify that we got some data | ||
| assert!(!bhttp_data.is_empty()); |
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.
Consider parsing (synchronous is fine), then test for the trailers.
|
|
||
| // Verify that we got some data | ||
| assert!(!bhttp_data.is_empty()); | ||
|
|
||
| // The data should be longer than just the body since it includes headers and metadata | ||
| assert!(bhttp_data.len() > 18); |
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.
Consider parsing (synchronous is fine), then test for the various pieces you encoded..
martinthomson
left a comment
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 decoder parts are pretty good. Just a few little nits in that part.
| if let Some(method) = control_data.method() { | ||
| builder = builder.method(method); | ||
| } |
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 think that you want this:
| if let Some(method) = control_data.method() { | |
| builder = builder.method(method); | |
| } | |
| let Some(method) = control_data.method() else { | |
| return Err(Error::Something); | |
| }; | |
| builder = builder.method(method); |
All requests have a method.
| if let Some(scheme) = control_data.scheme() { | ||
| if !scheme.is_empty() { | ||
| uri_builder = uri_builder.scheme(scheme); | ||
| } | ||
| } |
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.
Again, the scheme is mandatory, so you can safely do something like this:
| if let Some(scheme) = control_data.scheme() { | |
| if !scheme.is_empty() { | |
| uri_builder = uri_builder.scheme(scheme); | |
| } | |
| } | |
| let Some(scheme) = control_data.scheme() else { | |
| return Err(Error::Something); | |
| } | |
| if scheme.is_empty() { | |
| return Err(Error::Something); | |
| } | |
| uri_builder = uri_builder.scheme(scheme); |
| // Handle authority field | ||
| match control_data.authority() { | ||
| Some(authority_bytes) if !authority_bytes.is_empty() => { | ||
| uri_builder = uri_builder.authority(std::str::from_utf8(authority_bytes)?); |
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.
| uri_builder = uri_builder.authority(std::str::from_utf8(authority_bytes)?); | |
| uri_builder = uri_builder.authority(str::from_utf8(authority_bytes)?); |
| } | ||
|
|
||
| // Handle authority field | ||
| match control_data.authority() { |
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.
As above, I think that you can use let Some(auth) = control_data.authority() else { return Err(...); };
One thing that this block fails to catch is the None case.
| if let Some(name) = name { | ||
| builder = builder.header(name, value); | ||
| } |
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 might want to filter out pseudo-headers here. Most of these are in the control data, but the format allows for others. I don't know if the http crate is OK with field names that start with ':', but it looks like they will produce an error at some level, quite possibly a panic.
| let decoder = BhttpDecoder::new(cursor); | ||
| let result = block_on(decoder.decode_message()).expect("Failed to decode message"); | ||
|
|
||
| match result.into_full_message().unwrap() { |
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.
See below.
| std::str::from_utf8(&body_data).unwrap(), | ||
| "Hello World! My content includes a trailing CRLF.\r\n" |
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.
| std::str::from_utf8(&body_data).unwrap(), | |
| "Hello World! My content includes a trailing CRLF.\r\n" | |
| &body_data, | |
| b"Hello World! My content includes a trailing CRLF.\r\n" |
No point converting.
| assert_eq!( | ||
| std::str::from_utf8(&body_data).unwrap(), | ||
| "This content contains CRLF.\r\n" | ||
| ); |
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.
As above:
| assert_eq!( | |
| std::str::from_utf8(&body_data).unwrap(), | |
| "This content contains CRLF.\r\n" | |
| ); | |
| assert_eq!( | |
| &body_data, | |
| b"This content contains CRLF.\r\n" | |
| ); |
| if let Some(scheme) = control_data.scheme() { | ||
| if !scheme.is_empty() { |
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.
Not sure why you are pulling the scheme again. That should already be settled.
If your goal is to use host as a backup, I would do this:
let Some(auth) = control_data.authority() else {
return Err(Error::Something);
};
// Add your comment here.
let authority = if auth.is_empty() {
let Some(host) = headers.get("host") else {
return Err(Error::Something);
};
} else {
auth;
};
uri_builder.authority(authority.to_str()?);| } | ||
| } | ||
|
|
||
| if let Some(path) = control_data.path() { |
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 should use let Some() ... else { return Err(...); }; as well.
This PR is a re-open of #82