はじめに
reqwest はよくできたクレートなんですが, ちょっと気軽にスクレイピングしたいみたいな用途では初回ビルドに時間がかかりすぎるしディスクも食うのがちょっと嫌です, 特に非力な Surface 使っているときには... 別に blocking API で十分ですよね. なので代わりになるようなクレートはないかなと思ったら, そのものズバリなスレッドが先月 (2019-10-15) フォーラムに立っていました.
このスレッドで挙がっていたのは isahc, surf, ureq, attohttpc で, 前 2 つは async しているため今の目的には適いません. attohttpc も悪くなさそうですが, 本記事では ureq = "0.11.2"
を試してみます.
ureq について
ureq はミニマルな依存性を持つことを目指して開発中の HTTP/HTTPS リクエストクレートです. 依存ツリーが肥大化することを嫌って現状では async 対応していません.
フィーチャーフラグ
ureq には以下のフィーチャーフラグがあります.
- tls (デフォルト)
- json
- charset
依存ツリー
本当に依存性がミニマルか確認しましょう. ureq のみに依存するプロジェクトを作成して cargo-tree
します.
$ cargo tree
hello-req v0.1.0 (/home/osanshouo/misc/req)
└── ureq v0.11.2
├── base64 v0.10.1
│ └── byteorder v1.3.2
├── chunked_transfer v1.0.0
├── cookie v0.12.0
│ ├── time v0.1.42
│ │ └── libc v0.2.65
│ │ [dev-dependencies]
│ │ └── winapi v0.3.8
│ └── url v1.7.2
│ ├── idna v0.1.5
│ │ ├── matches v0.1.8
│ │ ├── unicode-bidi v0.3.4
│ │ │ └── matches v0.1.8 (*)
│ │ └── unicode-normalization v0.1.9
│ │ └── smallvec v0.6.13
│ │ └── maybe-uninit v2.0.0
│ ├── matches v0.1.8 (*)
│ └── percent-encoding v1.0.1
├── lazy_static v1.4.0
├── qstring v0.7.0
│ └── percent-encoding v2.1.0
├── rustls v0.16.0
│ ├── base64 v0.10.1 (*)
│ ├── log v0.4.8
│ │ └── cfg-if v0.1.10
│ ├── ring v0.16.9
│ │ ├── lazy_static v1.4.0 (*)
│ │ ├── libc v0.2.65 (*)
│ │ ├── spin v0.5.2
│ │ └── untrusted v0.7.0
│ │ [build-dependencies]
│ │ └── cc v1.0.47
│ │ [dev-dependencies]
│ │ └── libc v0.2.65 (*)
│ ├── sct v0.6.0
│ │ ├── ring v0.16.9 (*)
│ │ └── untrusted v0.7.0 (*)
│ └── webpki v0.21.0
│ ├── ring v0.16.9 (*)
│ └── untrusted v0.7.0 (*)
│ [dev-dependencies]
│ └── log v0.4.8 (*)
├── url v2.1.0
│ ├── idna v0.2.0
│ │ ├── matches v0.1.8 (*)
│ │ ├── unicode-bidi v0.3.4 (*)
│ │ └── unicode-normalization v0.1.9 (*)
│ ├── matches v0.1.8 (*)
│ └── percent-encoding v2.1.0 (*)
├── webpki v0.21.0 (*)
└── webpki-roots v0.18.0
└── webpki v0.21.0 (*)
確かに重そうな依存性はありません. HTTPS 通信が不要な場合, デフォルトで有効なフラグ tls
をオフにすることで rustls への依存性も解消できます.
とはいえ多少のディスクは必要です. rustls
なしだと
$ du --max-depth=1 -h ./target/
155M ./target/debug
116M ./target/release
で, rustls ありだと 100 MB ほど追加されます. reqwest
に比べるとずっと軽いのも確かですが. ちなみにビルド時間はノート PC でデバッグビルドに 10.2 秒程度でした (依存クレートのダウンロード時間を除く).
使い方
GET リクエスト
GET リクエスト を投げるのはとても簡単です. ureq::get 関数に URL を入れて ureq::Request を作成し, .call()
するだけです. 例として Gaia DR1 による恒星の位置・年周視差データを取得してみます.
let url = "http://cdn.gea.esac.esa.int/Gaia/gdr1/gaia_source/csv/GaiaSource_000-000-000.csv.gz";
let resp = ureq::get(url).call();
そうすると ureq::Response が得られるので, ステータスエラーでないことを確認して中身をバッファーに取得します. テキストデータなら直接 .into_string()
できますが, いまは圧縮データなので Vec<u8>
として扱う必要があります. .into_reader()
によりリーダーを取得し, std::io::Read
トレイトを通じてバイナリをバッファーに書き込みます.
use std::io::Read;
if resp.ok() {
// バッファーの確保
let mut buf = {
let len = resp.header("Content-Length").unwrap()
.parse::<usize>().unwrap();
vec![ 0; len ]
};
// レスポンスの内容をバッファーに書き込む
let mut resp = resp.into_reader();
resp.read_exact(&mut buf).unwrap();
/* snip */
} else {
println!("Status error: {:?}", resp.status_line());
}
(なぜか read_to_end
だとうまくいかなかったので Content-Length
の長さのバッファーを用意して read_exact
しています.) 後はファイル出力するなり解凍して解析するなりお好みでどうぞ.
ヘッダーの指定
HTTP リクエストのヘッダーを設定する必要がある場合. ureq::Request の set メソッドを使います. 例えばこんな感じ.
let resp = ureq::get(url)
.set("Accept", "application/gzip")
.call();
他のメソッド
ureq は POST リクエスト や PUT リクエスト といった他の HTTP メソッドにも対応しています. 使い方は GET とほぼ同じで, 主な違いは送信する中身を .send
, .send_bytes
または .send_json
メソッドで設定して送る (従って .call()
不要) ことだけです. 詳細はドキュメントを参照してください.
さらに ureq::Agent を用いればクッキーなどのセッションの状態をリクエスト間で保持できるようです.