[Rust] hyperのClient機能のまとめ 

## [Rust]  hyperのClient機能のまとめ  

RustのHttpクライアントとして、[hyper](https://github.com/hyperium/hyper)や[reqwest](https://github.com/seanmonstar/reqwest)がスター数も多く有名です。

この記事ではhyperについて紹介していきます。

ちなみにhyperはサーバーサイドの機能を併せ持つ低レベルに動くライブラリですが、クライアント機能に絞って紹介をさせてください。

## hyperとは

hyperは低レベルでhttpリクエストを行うことができるクレート(ライブラリ)です。

低レベルということで高機能に柔軟に動かすことができますが、用件がシンプルなものであれば、hyperを内部で呼び出している[reqwest](https://github.com/seanmonstar/reqwest)なども検討してみても良いと思います。

非同期型で動作し、クライアント、サーバー両方のAPIを提供していて、本番環境においても広く使われています。



## 使用方法

まず、Cargo.tomlに依存関係を追記します。

```toml
[dependencies]
hyper = { version = "0.14", features = ["full"] }
tokio = { version = "1", features = ["full"] }
```

## Getでのリクエスト

tokioのmain関数内にシンプルなリクエスト構文を記述しています。

```rust
use hyper::Client;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    // This is where we will setup our HTTP client requests.
    // Still inside `async fn main`...
    let client = Client::new();

    // Parse an `http::Uri`...
    let uri = "http://httpbin.org/ip".parse()?;

    // Await the response...
    let resp = client.get(uri).await?;

    println!("Response: {}", resp.status());
   
    Ok(())
}
```

### 実行結果

```shell
% cargo run                                      
    Blocking waiting for file lock on build directory
    Finished dev [unoptimized + debuginfo] target(s) in 4.70s
     Running `target/debug/demo`
Response: 200 OK
```

次に、レスポンスのbodyが標準出力されるように変更を加えます。

### 変更後のコード

```rust
use hyper::Client;
use hyper::body::HttpBody as _;
use tokio::io::{stdout, AsyncWriteExt as _};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {

  	let client = Client::new();
    let uri = "http://httpbin.org/ip".parse()?;
    let mut resp = client.get(uri).await?;

  	while let Some(chunk) = resp.body_mut().data().await {
        stdout().write_all(&chunk?).await?;
    }

    Ok(())
}
```

### 実行結果

```shell
% cargo run                                     
   Compiling demo v0.1.0 (/Users/ishikawashou/Desktop/work/note/RustNote/demo)
    Finished dev [unoptimized + debuginfo] target(s) in 4.72s
     Running `target/debug/demo`
{
  "origin": "60.76.119.106"
}
```



## Postでのリクエスト

クライアントに渡すリクエストを生成し、Postリクエストを行います。

``Request::builder()``を使うことで非常に簡単にヘッダー付きのリクエストを生成することができます。


```rust
use hyper::{Client,Body, Method, Request};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let req = Request::builder()
        .method(Method::POST)
        .uri("http://httpbin.org/post")
        .header("content-type", "application/json")
        .body(Body::from(r#"{"library":"hyper"}"#))?;

    let client = Client::new();

    let resp = client.request(req).await?;

    println!("Response: {}", resp.status());
    Ok(())
}
```

### 実行結果

```shell
% cargo run                                   
   Compiling demo v0.1.0 (/Users/ishikawashou/Desktop/work/note/RustNote/demo)
    Finished dev [unoptimized + debuginfo] target(s) in 3.62s
     Running `target/debug/demo`
Response: 200 OK
```



## 並列リクエスト

今まではawaitを用いてかなり同期的にリクエストを行なっていましたが、複数のリクエストを並行して行うこともできます。

まず、[futures](https://www.google.com/search?q=rust+future&sxsrf=ALeKk02mvY40HaONzyYv0m2Ru7fnA-2Udg%3A1625035466324&ei=yhLcYLOdE5GUr7wP38iEUA&oq=rust+futur&gs_lcp=Cgdnd3Mtd2l6EAMYADIFCAAQywEyBQgAEMsBMgUIABDLATIFCAAQywEyBQgAEMsBMgUIABDLATIFCAAQywEyBQgAEMsBMgUIABDLATICCAA6BwgAEEcQsAM6BAgjECc6BQgAEJECOgIILjoECAAQQ0oECEEYAFC0k1hYj6BYYNmnWGgDcAJ4AIABygGIAaQPkgEGMC4xMC4xmAEAoAEBqgEHZ3dzLXdpesgBCMABAQ&sclient=gws-wiz#:~:text=Future%20in%20std,trait.Future.html)を使うのでCargo.tomlを更新します。

```toml
[dependencies]
hyper = { version = "0.14", features = ["full"] }
tokio = { version = "1", features = ["full"] }
futures = "0.3"
```

[async](https://rust-lang.github.io/async-book/03_async_await/01_chapter.html)を使い非同期ブロックを生成し、以下のように並列にリクエスト処理を行うことができます。

```rust
use hyper::{Client, Uri};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let client = Client::new();

    let ip_fut = async {
        let resp = client.get(Uri::from_static("http://httpbin.org/ip")).await?;
        hyper::body::to_bytes(resp.into_body()).await
    };
    let headers_fut = async {
        let resp = client.get(Uri::from_static("http://httpbin.org/headers")).await?;
        hyper::body::to_bytes(resp.into_body()).await
    };
    
    // Wait on both them at the same time:
    let (ip, headers) = futures::try_join!(ip_fut, headers_fut)?;

    let ip_body = String::from_utf8(ip.to_vec()).expect("response was not valid utf-8");
    println!("ip_body: {}", ip_body);

    let headers_body = String::from_utf8(headers.to_vec()).expect("response was not valid utf-8");
    println!("headers_body: {}", headers_body);

    Ok(())
}
```

### 実行結果

```shell
% cargo run
   Compiling demo v0.1.0 (/Users/ishikawashou/Desktop/work/note/RustNote/demo)
    Finished dev [unoptimized + debuginfo] target(s) in 4.37s
     Running `target/debug/demo`
ip_body: {
  "origin": "60.76.119.106"
}

headers_body: {
  "headers": {
    "Host": "httpbin.org", 
    "X-Amzn-Trace-Id": "Root=1-60dc0ede-62d5d0fa3966f7a94c0d0f02"
  }
}
```



## TLSを使ったHTTPSへの接続

hyperのデフォルトのクライアントでは、TLSの実装が搭載されていないため、HTTPアドレスにしかアクセスできません。

[Client::new()](https://docs.rs/hyper/0.11.1/hyper/client/struct.Client.html) が[HttpConnector](https://docs.rs/hyper/0.11.1/hyper/client/struct.HttpConnector.html)を返すことからもそのような実装がされていることがわかります。

hyperではプラグインとして[HttpsConnector](https://docs.rs/hyper-tls/0.5.0/hyper_tls/struct.HttpsConnector.html#:~:text=Struct%20hyper_tls%3A%3A-,HttpsConnector,-%5B)を備えたhyper-tls crateを提供していますのでそれを使いましょう。

```rust
use hyper_tls::HttpsConnector;
use hyper::Client;

#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), Box<dyn std::error::Error>>{
    let https = HttpsConnector::new();
    let client = Client::builder().build::<_, hyper::Body>(https);

    let res = client.get("https://hyper.rs".parse()?).await?;
    assert_eq!(res.status(), 200);
    Ok(())
}
```

以上になります。何か質問、間違い等ございましたら[@ishishow](https://twitter.com/_ishishow)までご連絡ください。
また、この記事はhyperの[getting started](https://hyper.rs/guides/client/configuration/
)を参考にしています。

## 参考文献
https://hyper.rs/guides/client/configuration/

https://github.com/hyperium/hyper

https://blog.logrocket.com/the-state-of-rust-http-clients/