🤖

Reqwestでhttp/2通信するときの注意(Rust)

2024/04/09に公開


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