19
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

[Rust] ureq - reqwest の軽量な代替クレート

Posted at

はじめに

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 を用いればクッキーなどのセッションの状態をリクエスト間で保持できるようです.

19
16
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?