はじめに
個人的なメモなので、間違っている可能性が高い記事です。
質問やマサカリがあれば、コメントか@miyagaw61までお願いします。
futures
Future
(計算結果が非同期で返るResult
のようなもの)を提供するためのクレートです。
tokio_core
非同期ネットワーククレートです。
hyper
HTTPリクエストをするためのクレートです。
hyper_tls
HTTPSリクエストをするためのクレートです。
GETリクエスト(HTTP)実装
宣言
extern crate futures;
extern crate tokio_core;
extern crate hyper;
URL作成
let url = "http://hogehoge";
let url = url.parse::<hyper::Uri>().unwrap();
スキームチェック
if url.scheme() != Some("http") {
println!("This example only works with 'http' URLs.");
return;
}
非同期イベントループ作成
let mut core = tokio_core::reactor::Core::new().unwrap();
非同期イベントループのハンドラを作成
ハンドラとは、イベントループを参照する変数を表します。
let handle = core.handle();
クライアント作成
非同期処理では、このように引数にイベントループのハンドラを渡してFuture
が実装された返り値を返すメソッドをもつインスタンスを作成する必要があります。
let client = Client::new(&handle);
イベントの定義
Future
が実装された型(以降Future
)を用いてイベントループ上で実行させるイベントを定義します。
Future
がFuture
を返しそれがFuture
を返す、というようにチェインさせていきます。
and_then()
はFuture
を引数にとるクロージャを引数にとります。
and_then()
は引数のクロージャに与えられたFuture
をアンラップして、クロージャを実行させ、クロージャが返したResult
をFuture
にラップ(変換かも?)して返します。
and_then()
は引数がF
( F: FnOnce(Self::Item) -> B
, B: IntoFuture<Item=(), Error=Self::Error>
) なので、and_then()
に渡すクロージャは最後にItem=()
なResult
であるOk(())
を返す必要があります(FnOnce
はクロージャを表します)(Result
型はIntoFuture
traitが実装されています)。
extern crate encoding_rs;
use encoding_rs::*;
let work = client.get(url).and_then(|res| {
res.body().for_each(|chunk| {
let (s, _, _) = UTF_8.decode(&chunk);
println!("{}", s);
Ok(())
})
});
res.body()
はBody
を返します。Body
単体では複雑な構造体です。
Body.for_each()
はBody
から1チャンクを取得し引数に与えたクロージャをループ処理するメソッドです。
for_each()
はForEach<Self, F, U>
(U: IntoFuture<Item = (), Error = Self::Error>
)を返すため、for_each()
もItem=()
であるResult
を返す必要があります。
チャンクがどういう区切りで分けられているかはわかりませんが、チャンクそのものはバイト列です。to_vec()
メソッドでu8
のベクタにすることができます。
また、Body
の扱い方ですが、次のような書き方も可能です。
res.body().concat2().and_then(|body| {
let (s, _, _) = UTF_8.decode(&body);
println!("{}", s);
Ok(())
})
この書き方では、concat2()
メソッドによってチャンクを全て結合しています。
チャンクという馴染みのない概念を忘れることができるため、こちらの方が直感的かもしれません。
GETリクエスト(HTTPS)実装
宣言
extern crate hyper_tls;
クライアント生成
クライアント生成を以下の式に置き換えるだけで後は何も変える必要はありません。
extern crate hyper_tls;
let client = hyper::Client::configure()
.connector(hyper_tls::HttpsConnector::new(4, &handle).unwrap())
.build(&handle);
その他のリクエスト方法
Request
インスタンスを作成し、request()
メソッドを使用する方法
let req = hyper::Request::new(hyper::Method::Get, url);
let work = client.request(req).and_then(|res| { /* ... */ } );
この方法であれば、GETリクエストだけでなくその他のメソッド(POSTなど)のリクエストも可能になります。
今後はこの方法が主流になりそう?
POSTリクエスト実装
自作ヘッダ付与(推奨)
Request.headers_mut().set_raw()
メソッドを用います。
let mut req = hyper::Request::new(hyper::Method::Post, url);
req.headers_mut().set_raw("User-Agent", "archiveis (https://github.com/pastpages/archiveis)");
let work = client.request(req).and_then(|res| { /* ... */ });
core.run(work).unwrap();
自作ヘッダ付与(非推奨)
header!
マクロを使用する方法です。
マクロを使用するために、最初のextern crate hyper
で#[macro_use]
を付与する必要があります。
header!
マクロでヘッダ付与関数を作成し、Request.headers_mut().set()
に渡します。
#[macro_use]
extern crate hyper;
let mut req = hyper::Request::new(hyper::Method::Post, url);
header! { (MyUserAgent, "User-Agent") => [String] }
req.headers_mut().set(MyUserAgent("archiveis (https://github.com/pastpages/archiveis)".to_string()).to_owned());
let work = client.request(req).and_then(|res| { /* ... */ });
core.run(work).unwrap();
一般的なヘッダ
User-Agent
やHost
などの一般的なヘッダにおいては、ユーザ側でマクロを使わなくても既にヘッダ付与関数が用意されていることがあります。
extern crate hyper;
let mut req = hyper::Request::new(hyper::Method::Post, url);
req.headers_mut().set(hyper::header::UserAgent::new("archiveis (https://github.com/pastpages/archiveis)".to_string()).to_owned());
let work = client.request(req).and_then(|res| { /* ... */ });
core.run(work).unwrap();
ボディ付与
extern crate jsonway;
let json = jsonway::object(|json| {
json.set("url", "https://www.kernel.org".to_string());
}).unwrap().to_string();
req.set_body(json);