Skip to content

Conversation

@imlk0
Copy link

@imlk0 imlk0 commented Nov 10, 2025

This PR is a re-open of #82

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>
Copy link
Owner

@martinthomson martinthomson left a 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.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// 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)]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#[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;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let request = t == 0 || t == 2;

Comment on lines +76 to +81
let framing_indicator = if request {
FramingIndicator::Request(mode)
} else {
FramingIndicator::Response(mode)
};
Ok(framing_indicator)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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 {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this work?

Suggested change
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);
Copy link
Owner

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() {
Copy link
Owner

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.

Comment on lines +358 to +359
let mut trailers = HeaderMap::new();
trailers.insert("Trailer-Key", "Trailer-Value".parse().unwrap());
Copy link
Owner

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.

Suggested change
let mut trailers = HeaderMap::new();
trailers.insert("Trailer-Key", "Trailer-Value".parse().unwrap());
let trailers = HeaderMap::from_iter([
("Trailer-Key", "Trailer-Value".parse().unwrap())
]);

Comment on lines +379 to +381

// Verify that we got some data
assert!(!bhttp_data.is_empty());
Copy link
Owner

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.

Comment on lines +344 to +349

// 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);
Copy link
Owner

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..

Copy link
Owner

@martinthomson martinthomson left a 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.

Comment on lines +236 to +238
if let Some(method) = control_data.method() {
builder = builder.method(method);
}
Copy link
Owner

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:

Suggested change
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.

Comment on lines +247 to +251
if let Some(scheme) = control_data.scheme() {
if !scheme.is_empty() {
uri_builder = uri_builder.scheme(scheme);
}
}
Copy link
Owner

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:

Suggested change
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)?);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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() {
Copy link
Owner

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.

Comment on lines +285 to +287
if let Some(name) = name {
builder = builder.header(name, value);
}
Copy link
Owner

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() {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See below.

Comment on lines +483 to +484
std::str::from_utf8(&body_data).unwrap(),
"Hello World! My content includes a trailing CRLF.\r\n"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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.

Comment on lines +528 to +531
assert_eq!(
std::str::from_utf8(&body_data).unwrap(),
"This content contains CRLF.\r\n"
);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above:

Suggested change
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"
);

Comment on lines +263 to +264
if let Some(scheme) = control_data.scheme() {
if !scheme.is_empty() {
Copy link
Owner

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() {
Copy link
Owner

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants