🤖
Reqwestでhttp/2通信するときの注意(Rust)
Rustを書いててハマった備忘2です。[reqwest 0.12.3]
普通に通信できてたと思ったらhttp/1.1で単純にhttp/2にしようとしたらエラーでごっつい時間がかかりました。
起こった問題
最初のコード
Cargo.toml
[dependencies]
reqwest = "0.12"
use reqwest::{Url, Client};
#[tokio::main()]
async fn main() {
let url = Url::parse("https://www.rust-lang.org/").unwrap();
let client = Client::builder().build().unwrap();
let request = client.get(url).build().unwrap();
println!("{:?}", request.version()); // HTTP/1.1
}
HTTP/2を指定したつもりのエラーになるコード
コード
Cargo.toml
[dependencies]
reqwest = "0.12"
use reqwest::{Url, Client, Version};
#[tokio::main()]
async fn main() {
let url = Url::parse("https://www.rust-lang.org/").unwrap();
let client = Client::builder().build().unwrap();
let request = client.get(url.clone()).version(Version::HTTP_2).build().unwrap();
println!("{:?}", request.version()); // HTTP/2.0
let response = client.get(url).version(Version::HTTP_2).send().await;
match response {
Ok(_) => println!("success"),
Err(e) => eprintln!("{:?}", e),
}
}
結果
vscode ➜ /workspaces/z_rust_sandbox (master) $ cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.05s
Running `target/debug/z_rust_sandbox`
HTTP/2.0
reqwest::Error { kind: Request, url: Url { scheme: "https", cannot_be_a_base: false, username: "", password: None, host: Some(Domain("www.rust-lang.org")), port: None, path: "/", query: None, fragment: None }, source: Error { kind: UserUnsupportedVersion, source: None } }
わかったこと
- reqwestは何も指定しない場合、TLS機能として
default-tls
機能を使用する- reqwest 0.12.3 時点では
default-tls
内でnative-tls
を使用している- 将来的に
rustls
にしたいように見受けられる
- 将来的に
- Linuxにおける
native-tls
ではOpenSSLが使用される - 特に指定しない場合、OpenSSLのALPNでHTTPプロトコルが決定される
- その場合、OpenSSLのALPNではhttp/2とhttp/1.1が提案されるようだ
- 少なくとも
curl
ではそのように見受けられる
curl
vscode ➜ /workspaces/z_rust_sandbox (master) $ echo $CURL_SSL_BACKEND OpenSSL vscode ➜ /workspaces/z_rust_sandbox (master) $ curl https://www.rust-lang.org/ --verbose * Trying 54.192.18.63:443... * Connected to www.rust-lang.org (54.192.18.63) port 443 (#0) * ALPN: offers h2,http/1.1 ~~~~~ some lines omitted by author ~~~~ * ALPN: server accepted h2 ~~~~~ some lines omitted by author ~~~~ * using HTTP/2 ~~~~~ some lines omitted by author ~~~~ > GET / HTTP/2 ~~~~~ some lines omitted by author ~~~~ * TLSv1.3 (IN), TLS handshake, Newsession Ticket (4): < HTTP/2 200 ~~~~~ some lines omitted by author ~~~~
- 少なくとも
- その場合、OpenSSLのALPNではhttp/2とhttp/1.1が提案されるようだ
- 指定した場合はそれが用いられる
-
openssl s_client
で確認
openssl s_client
vscode ➜ /workspaces/z_rust_sandbox (master) $ openssl version OpenSSL 3.0.11 19 Sep 2023 (Library: OpenSSL 3.0.11 19 Sep 2023) vscode ➜ /workspaces/z_rust_sandbox (master) $ echo | openssl s_client -alpn h2 -connect www.rust-lang.org:443 | grep ALPN ~~~~~ some lines omitted by author ~~~~ DONE ALPN protocol: h2
-
- reqwest 0.12.3 時点では
-
native-tls-alpn
機能を指定しないとOpenSSLのALPNが機能しないようだ- デフォルトでは有効になっていないため明示的に有効化する必要がある
- もしくは作者の案内の通り、TLS機能として
rustls
を指定するとhttp/2を使用できる
問題の解決
解決1
Cargo.toml
[dependencies]
reqwest = { version = "0.12", features = ["native-tls-alpn"]}
use reqwest::{Url, Client};
#[tokio::main()]
async fn main() {
let url = Url::parse("https://www.rust-lang.org/").unwrap();
let client = Client::builder().build().unwrap();
let request = client.get(url.clone()).build().unwrap();
println!("{:?}", request.version()); // HTTP/1.1
let response = client.get(url).send().await;
match response {
Ok(resp) => {
println!("{:?}", resp.version()); // HTTP/2.0
println!("success");
},
Err(e) => eprintln!("{:?}", e),
}
}
結果
vscode ➜ /workspaces/z_rust_sandbox (master) $ cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.06s
Running `target/debug/z_rust_sandbox`
HTTP/1.1
HTTP/2.0
success
解決2
Cargo.toml
[dependencies]
reqwest = { version = "0.12", features = ["rustls-tls"]}
use reqwest::{Url, Client, Version};
#[tokio::main()]
async fn main() {
let url = Url::parse("https://www.rust-lang.org/").unwrap();
let client = Client::builder().use_rustls_tls().build().unwrap();
let request = client.get(url.clone()).version(Version::HTTP_2).build().unwrap();
println!("{:?}", request.version()); // HTTP/2.0
let response = client.get(url).version(Version::HTTP_2).send().await;
match response {
Ok(_) => println!("success"),
Err(e) => eprintln!("{:?}", e),
}
}
結果
vscode ➜ /workspaces/z_rust_sandbox (master) $ cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.05s
Running `target/debug/z_rust_sandbox`
HTTP/2.0
success
愚痴
UserUnsupportedVersionでググっても全く何も出てこないしソース色々検索しても見つけられないし正直疲れた。
Discussion