reqwest: Connection reset while connecting `https://storage.googleapis.com` with rustls
Hi, I came across a strange issue where sending a request via rustls with the host header to https://storage.googleapis.com results in the connection being reset.
Related to https://github.com/apache/incubator-opendal/issues/2125#issuecomment-1523431970
Reproduce
Cargo.toml
[package]
name = "demo"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
default = ["reqwest/rustls-tls-native-roots"]
native-tls = ["reqwest/native-tls"]
[dependencies]
reqwest = { version = "0.11.16", default-features = false }
tokio = { version = "1.28.0", features = ["full"] }
main.rs
use reqwest::Client;
#[tokio::main]
async fn main() {
let client = Client::new();
let url = "https://storage.googleapis.com/";
let content = client
.get(url)
.body("")
.header("host", "storage.googleapis.com")
.send()
.await
.expect("Failed to send request");
println!("{}", content.text().await.unwrap())
}
With rustls:
:) cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.04s
Running `target/debug/demo`
thread 'main' panicked at 'Failed to send request: reqwest::Error { kind: Request, url: Url { scheme: "https", cannot_be_a_base: false, username: "", password: None, host: Some(Domain("storage.googleapis.com")), port: None, path: "/", query: None, fragment: None }, source: hyper::Error(Http2, Error { kind: Reset(StreamId(1), PROTOCOL_ERROR, Remote) }) }', src/main.rs:14:10
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
With native-tls:
:( cargo run --features=native-tls
Finished dev [unoptimized + debuginfo] target(s) in 0.04s
Running `target/debug/demo`
<?xml version='1.0' encoding='UTF-8'?><Error><Code>MissingSecurityHeader</Code><Message>Your request was missing a required header.</Message><Details>Authorization</Details></Error>
If we remove the host header, it works too:
:) cargo run
Compiling demo v0.1.0 (/tmp/demo)
Finished dev [unoptimized + debuginfo] target(s) in 0.30s
Running `target/debug/demo`
<?xml version='1.0' encoding='UTF-8'?><Error><Code>MissingSecurityHeader</Code><Message>Your request was missing a required header.</Message><Details>Authorization</Details></Error>
Question
How do rustls and native-tls interact with the host header? Is this behavior expected?
About this issue
- Original URL
- State: open
- Created a year ago
- Reactions: 3
- Comments: 20 (15 by maintainers)
I know what happens!
The package-capture tools change the behavior. Damn…
Oh, they’re sending a
RST_STREAMofPROTOCOL_ERROR? I think that interpretation is incorrect. It’s not a protocol error, the protocol says it MAY happen.I assumed it was the server replying with a 400, which is fine, a server can say anything is bad in that sense.
Well, between the two, only rustls supports ALPN, which means it will use HTTP/2 automatically (if the server supports it). The native-tls backend doesn’t have ALPN support, the request is using HTTP/1 (unless you use
http2_prior_knowledge()).It shouldn’t be an error, at least the spec doesn’t say it should.
The HTTP/2 spec (RFC 9113) doesn’t make the
Hostheader illegal (like it does a few others, such asconnection). But it does say that:authorityis the correct one, and that a client should not make a request where the:authorityandhostdiffer.