🚛
Reqwestを用いたRustのhttp通信方法メモ
Rustでhttp通信をするためにreqwestを調べた結果の基本的機能の使用の流れとサンプルコードを備忘のため残しておきます。[reqwest 0.12.3]
cookie指定等のいくつかの個人的要点を抑えた簡潔なコードがあまり見当たらなかったので普通に使えるようになるまでなかなか苦労しました。
前提
- reqwestを何も指定せずに使用すると非同期版となる
- "blocking" featureを使用すると同期版を使用できる
- しかし"blocking"のクライアントには
chunk
メソッドが存在せず扱いが面倒
- しかし"blocking"のクライアントには
- 非同期版を使用するデメリットは若干tokioの記述が増えるだけ
- 非同期関数呼び出しに
await
を記述するだけであればほぼ同期版のように書ける
- 非同期関数呼び出しに
- 以上より非同期版の使用を前提とする
Cargo.toml
[dependencies]
reqwest = { version = "0.12", features = ["native-tls-alpn","cookies","gzip","brotli","deflate"]}
tokio = { version = "1", features = ["macros","rt"]}
基本的な使用の流れ
-
ClientBuilder
を使用してhttp通信を担当するClient
を作成する- このタイミングでcookieとデフォルトヘッダーを指定する
-
Client
を使用して1回の通信内容を作成するRequestBuilder
を作成する- このタイミングで追加ヘッダーとボディ、クエリストリングを指定する
-
RequestBuilder
で通信を実行して応答となるResponse
を得る -
Response
を取り扱う
サンプルコード
use reqwest::{Url, ClientBuilder, Client, RequestBuilder, Response, cookie::Jar, header::{self, HeaderMap, HeaderName, HeaderValue}};
use std::{collections::HashMap, sync::Arc, time::Duration};
#[tokio::main(flavor = "current_thread")]
async fn main() {
let url = Url::parse("https://localhost/").unwrap();
// create default headers
let mut default_headers: HeaderMap = HeaderMap::new();
default_headers.insert(header::ACCEPT_ENCODING, HeaderValue::from_static("gzip, deflate, br"));
// create cookies
let cookie_str = "cookie1=foo; cookie2=bar";
let cookies = Arc::new(Jar::default());
cookies.add_cookie_str(cookie_str, &url);
// get client builder and gen client
let client_builder: ClientBuilder = Client::builder(); // also can use ClientBuilder::new()
let client: Client = client_builder
.default_headers(default_headers)
.cookie_provider(cookies)
.timeout(Duration::from_secs(30))
.build()
.unwrap();
// create onetime headers, and else
let mut onetime_headers: HeaderMap = HeaderMap::new();
onetime_headers.insert(HeaderName::try_from("Custom-Header").unwrap(), HeaderValue::from_static("header value"));
let body = r#"{ "body_value": 1 }"#;
let queries = HashMap::from([("q1".to_string(),"1".to_string()),("q2".to_string(),"2".to_string())]);
// get request builder and send request
let request_builder: RequestBuilder = client.post(url); // also can use get, or else
let mut response: Response = request_builder
.headers(onetime_headers)
.body(body)
.query(&queries)
.send()
.await
.unwrap();
// get response as string
let res_str = match response.headers().get(header::TRANSFER_ENCODING) {
Some(v) if v == "chunked" => {
let mut raw_res = Vec::new();
while let Some(chunk) = response.chunk().await.unwrap() {
chunk
.to_vec()
.into_iter()
.for_each(|x| raw_res.push(x));
}
String::from_utf8(raw_res).unwrap()
},
_ => response.text().await.unwrap()
};
println!("Response is : \n{}", res_str);
}
補足
- "native-tls-alpn"のfeature指定で自動でhttp/2,http/1.1のどちらかで通信してくれるようになる
- "gzip","brotli","deflate"のfeature指定で自動で各種形式でエンコード/デコードしてくれる
- サンプルではACCEPT_ENCODINGを手動でヘッダーに追加しているが、自動でしてくれる
- 不確かだがchunkedなdeflateエンコーディング応答はエラーとなるような挙動が確認された
- ヘッダーを何も指定しないhttp/1.1のGETヘッダーは以下が設定される
- Host
- Content-Length: 0
- Accept: */*
Discussion