RecvStream::is_end_stream() is never true. RecvStream::data() never returns None
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 1.6k
- Forks
- 382
- Avg merge
- 20h 23m
- Merged PRs (30d)
- 9
Description
When running a slightly modified version of the example (to get it to connect to an easily reproducable real world-endpoint), RecvStream's never stops returning when called and is_end_stream() never returns true.
Example Code
use std::error::Error as StdError;
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddrV4};
use std::sync::Arc;
use rustls::pki_types::pem::PemObject;
use rustls::pki_types::{CertificateDer, DnsName};
use tokio::net::TcpStream;
use tokio_rustls::{TlsConnector, rustls};
use h2::client;
use http::Request;
#[tokio::main]
async fn main() -> Result<(), Box<dyn StdError + Send + Sync + 'static>> {
let mut root_cert_store = rustls::RootCertStore::empty();
let (a, b) = root_cert_store.add_parsable_certificates(
CertificateDer::pem_file_iter("/etc/ssl/cert.pem")
.expect("Cannot open CA file")
.map(|result| result.unwrap()),
);
let mut config = rustls::ClientConfig::builder()
.with_root_certificates(root_cert_store)
.with_no_client_auth();
config.alpn_protocols = vec![b"h2".to_vec()];
let domain: DnsName = "google.com".try_into().unwrap();
let addr: SocketAddrV4 = "142.250.117.139:443".parse().unwrap(); // manual dns check for google.com
let tcp_stream = TcpStream::connect(addr).await?;
let tls_stream = TlsConnector::from(Arc::new(config))
.connect(domain.into(), tcp_stream)
.await?;
let (mut client, h2) = client::handshake(tls_stream).await?;
let request = Request::builder()
.uri("https://google.com/")
.body(())
.unwrap();
let (response, _) = client.send_request(request.clone(), false).unwrap();
// Spawn a task to run the conn...
tokio::spawn(async move {
if let Err(e) = h2.await {
println!("GOT ERR={:?}", e);
}
});
let response = response.await?;
// Get the body
let mut body = response.into_body();
let mut i = 0;
while let Some(chunk) = body.data().await {
match chunk {
Ok(data) => println!("Yay data: {data:?}"),
Err(err) => {
println!("Boo! {err:?}. {:?}", body.is_end_stream());
}
}
// Safety to prevent this going on forever.
i += 1;
if i > 10 {
break;
}
}
Ok(())
}
Output
Yay data: b"<HTML><HEAD><meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\">\n<TITLE>301 Moved</TITLE></HEAD><BODY>\n<H1>301 Moved</H1>\nThe document has moved\n<A HREF=\"https://www.google.com/\">here</A>.\r\n</BODY></HTML>\r\n"
Boo! Error { kind: Reset(StreamId(1), NO_ERROR, Remote) }. false
Boo! Error { kind: Reset(StreamId(1), NO_ERROR, Remote) }. false
Boo! Error { kind: Reset(StreamId(1), NO_ERROR, Remote) }. false
Boo! Error { kind: Reset(StreamId(1), NO_ERROR, Remote) }. false
Boo! Error { kind: Reset(StreamId(1), NO_ERROR, Remote) }. false
Boo! Error { kind: Reset(StreamId(1), NO_ERROR, Remote) }. false
Boo! Error { kind: Reset(StreamId(1), NO_ERROR, Remote) }. false
Boo! Error { kind: Reset(StreamId(1), NO_ERROR, Remote) }. false
Boo! Error { kind: Reset(StreamId(1), NO_ERROR, Remote) }. false
Boo! Error { kind: Reset(StreamId(1), NO_ERROR, Remote) }. false
This is despite confirming via tcpdump while this code is running that the server does not send Reset frames over and over. Connecting to the endpoint via curl shows they set the eos flag on the first frame with html data in it. This never seems to be picked up by h2.
A frame with the end stream flag set should be enough to mark is_end_stream to true, surely, or failing that a real Reset frame with a reason of NO_ERROR should also.
This behaviour began with 0.4.10.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by tracing RecvStream::data() and RecvStream::is_end_stream() from the supplied reproduction, comparing their behavior before and after version 0.4.10. Verify how the received END_STREAM and reset conditions are represented, then confirm that the stream terminates rather than repeatedly returning the same reset error.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- networking
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100