Rustに入門する〜２日目 reqwestでGET通信をする〜

# 初めに

１日目の続きです。

# 釈明

(釈明の日本語の使い方あってるのか)

本当はGoogleカレンダーからの情報取得までを予定していましたが、
方針があちこち飛びHTTP(S)リクエスト自体に以上に労力を使ったので表題の部分までになりました。

こうなった背景としては以下の通りです。

- `google-calendar3のサンプルの依存パッケージのバージョンが割と古くてコピペ＋αでどうにもならなかった
    - 正直パッケージを古いのにしてやればこんなことにはならなかったから自分が悪い
    - 一応頑張るだけがんばったけどこのままの方針でどうにかできる自信がなかった
- 利用するパッケージが`await/async`まみれだが別言語でも書く機会がなくふんわり概念止まりだったのでそこから学習が必要だった
    - あんま関係ないけどRustの非同期処理自体がようやくちょっと前に安定したらしく情報の交錯がすごかった
- `openssl-sys`を`AWS Lambda`向けにコンパイルできなかった
    - これについては回避できず今後解決していく必要があるので後述します。

あまりバージョン古いのものを使いたくないので、
今の方針としては`reqwest`を使ってなんとかしようと考えています。

`hyper`や`hyper_tls`も検討してましたが、今日これだけ詰まった後にすぐに詰まると心が折れるので、
軽量さではなく実装の楽さで選びました。
冗談抜きで`requwest`でなんとかしようまでに色々試してたら夜になってました。

## cargo-edit導入してみる

(google-calendar3を追加してますが今日終了時点では削除しています)

`Cargo.toml`には今まで手動で追加してきましたが、
これパッケージ増えてくると管理が大変なんだろうなって思ったら`cargo-edit`とやらがあるらしいです。

どうやらphpでいう`composer require`のようなこともできそうです。
(composerも最近ちょっと触っただけなのでよく知らないですが)


とりあえずインストールします。

```bash
$ cargo install cargo-edit
   Compiling subprocess v0.2.4
error[E0658]: the `#[non_exhaustive]` attribute is an experimental feature
(略)

For more information about this error, try `rustc --explain E0658`.
error: could not compile `subprocess`.
warning: build failed, waiting for other jobs to finish...
error: failed to compile `cargo-edit v0.6.0`, intermediate artifacts can be found at `/var/folders/lj/tt21hqgj7s3f0t3bgghmllkm0000gn/T/cargo-installzJRxiH`
```

まさか死ぬとは思いませんでした。
以下の記事曰くRustのバージョンが古いからとのことなのでアップデートしてやり直してみます。
(自分もちょうど`1.39.0`でした)


> cargo-editが便利だったので導入してみた
> https://blog.foresta.me/posts/setup-cargo-edit/

アップデート後無事導入できました。

```
$ rustc -V
rustc 1.43.0 (4fb7144ed 2020-04-20)
$ cargo install cargo-edit
(略)
   Installed package `cargo-edit v0.6.0` (executables `cargo-add`, `cargo-rm`, `cargo-upgrade`)
```

アンスコではなかったようですが、いい感じに補完して追加してくれるみたいです。優秀。

```
$ cargo add google_calendar3
WARN: Added `google-calendar3` instead of `google_calendar3`
    Updating 'https://github.com/rust-lang/crates.io-index' index
      Adding google-calendar3 v1.0.13 to dependencies
```

`Cargo.toml`を開きながらやったら挿入されているシーンを拝めました。
残りのパッケージもどこかでバージョンを固定しておきましょう。

```toml
[dependencies]
lambda_runtime = "*"
simple-error = "*"
serde_derive = "*"
serde = { version = "*", features = ["derive"] }
google-calendar3 = "1.0.13"
```


## Rustを理解して書く

ようやくここまできてRustを書くフェーズになりました。

### クレートとモジュールを理解する

そもそもファイル分割のやり方ってどうだ...ってなったので改めて読み返しました。

> クレートとモジュール
> https://doc.rust-jp.rs/the-rust-programming-language-ja/1.6/book/crates-and-modules.html

うまく言葉にできないですがなんとなく理解はできました。
多分Pythonでいうboto3がクレートで、その下に結びつくSESとかがそれぞれモジュールのような感じでしょうか。
最悪雑にクレートなしでモジュールを作って後から分解してもいいか...と思いましたが
サンプル読んで数分で`Cargo.toml`が混沌とする未来が見えたのでクレートをしっかり作りました。

QiitaのざっくりこうすればOK以外の記事がいまいち見当たらなかったのですが(多分探し方が悪い)、
ローカルに置いてあるクレートを使う場合は以下みたいな感じで指定すればいいらしいです。

```toml
[dependencies.my_google_controller]
path = "./lib/my_googler_controller"
```
`my_google_controller`は今回のクレートの名前です。
本記事で作るものは`requwest`で雑に問い合わせるだけですが、
今後そのまま拡張して利用したい気持ちがあるので
この中に`google_calendar`モジュールを作ります。

## Rustの`async/await`を理解してみる

かなりバージョンごとに違いがあるらしいですが、
Rustのバージョンが`1.39.0`の時点で一旦安定版になったらしいです。

> Rustが1.39でゼロコストAsync/Awaitをサポート
> https://www.infoq.com/jp/news/2019/12/rust-async-await/

内部的なところで紆余曲折あったみたいですが、
使い方だけであれば自分の認識ですが以下の認識さえあれば一旦なんとかなりそうです。

- 非同期関数の定義 : `async fn xxx()`のようにasyncをつけた関数を定義する
- 非同期関数を実行する : 通常の関数の呼び出しに加え末尾に`.await`をつける。ただし大元は別のものを使う必要あり

結局awaitはasyncの関数もしくはasyncブロック内でしか使えないようなので、
一番大元の部分は`feature`や`tokio`などを使う必要があるみたいです。

awaitだけでいけるんでしょ他の古い記事だしとか思ってやったら呼び出せてなくて頭傾げてました。
方法がいくつかあるのって一番悩ましい。


### 補足

返り値は`std::future::GenFuture<T>`でラップ？されるみたいです(Tは本来の返り値の型)
これは、docs.rsでバージョン違うの見てて最新では非同期関数であったのをasyncなしだと思って実行してた時の副産物です。

以下の関数定義に対して、
`read_application_secret(hogehoge).unwrap()`ということをしてコンパイルした時に
`method not found in impl std::future::Future`というエラーが出ました。
(同期関数だと思ってた時で`io::Result<ApplicationSecret>`でしょなんで？？？って５時間くらい悩んでました)

```rust
pub async fn read_application_secret<P: AsRef<Path>>(path: P) -> io::Result<ApplicationSecret>
```

上記から`unwrap()`を外したやつの型を調べたら以下でした。
Futureが保留してるときはこんな感じになってるようです。

```rust
std::future::GenFuture<yup_oauth2::helper::read_application_secret<&std::path::Path>::{{closure}}>
```


## `reqwest`でGET通信を行う

ただ問い合わせてターミナルに出力するだけです。
`cargo run`で一応出力ができることを確認しました。

`reqwest`が非同期関数なのでちょっとhttp飛ばすだけでもその辺の対応が必要なのが唯一ネックですね。
とは言っても数行レベルの違いですが。

`lambda!(my_handler);`のコメントアウトについては理由があるので後述します。

呼び出し元(不要そうな部分は省略しています)

```rust:main.rs
extern crate my_google_controller;

use my_google_controller::google_calendar;
use tokio;

fn main() {
    //lambda!(my_handler);
    let http_client = async{
        let str = google_calendar::get_request("https://google.co.jp".to_string());
        println!("{}",str.await);
    };

    tokio::runtime::Runtime::new().unwrap().block_on(http_client);
}
```

モジュール側

```Rust:google_calender.rs
extern crate reqwest;

pub async fn get_request(url: String) -> String{
        let response = reqwest::get(&url).await.unwrap().text().await.unwrap();
        return response
}
```

# `AWS Lambda`向けのビルドを調整する(未解決 ->解決しました)

さて、上記で`lambda!(my_handler);`のコメントアウトがあったかと思います。
これの理由としてまずローカルで動かすためです。
１日目の記事に記載しましたがローカルでlambda向けの書き方をしていると環境変数ガーとなって実行できないので、
一旦コメントアウトしローカルで実行でき状態にするためです。

ではなぜそうなったかという話になります。

## `openssl-sys`のコンパイルに失敗する

そもそもAWS LambdaはOSが違うので以下のように'--target`オプションをつけてコンパイルしたものをあげないといけない(よう)です。

```bash
cargo build --release --target x86_64-unknown-linux-musl
```
上記のコマンドを走らせてみると`openssl-sys`の部分で以下のようなエラーが出ます。

```
run pkg_config fail: "Cross compilation detected. Use PKG_CONFIG_ALLOW_CROSS=1 to override"
```

どうやらクロスコンパイルが必要なもので行うためには環境変数を設定する必要があるみたいです。
`export PKG_CONFIG_ALLOW_CROSS=1`を実行して再度コンパイルしてみますがまだダメです。

```
run pkg_config fail: "`\"pkg-config\" \"--libs\" \"--cflags\" \"openssl\"` did not exit successfully: exit code: 1\n--- stderr\nPackage openssl was not found in the pkg-config search path.\nPerhaps you should add the directory containing `openssl.pc\'\nto the PKG_CONFIG_PATH environment variable\nNo package \'openssl\' found\n"
```


> https://docs.rs/crate/openssl-sys/0.9.19

とりあえず`export OPENSSL_DIR=/usr/local/opt/openssl/`をしてみればいけそうなのでやってみたところ、
`openssl-sys`自体のコンパイルは通ったものの、プログラム本体のコンパイルがうまくいきません。
(エラーは量が凄いので省略します)

クロスコンパイルなのにMacにインストールされているopensslでいけるんの...というお気持ちがあるので、
なんとなくこの辺に原因がありそうな気がします。
最悪これがなくとも最悪Google Calendar API自体は動かせる点、(1日中詰まってたせいで脳みそが死んでいる点、）
を考慮して一旦リフレッシュのためにこの内容も保留としたいと思います。

もしくは適切なコンテナ環境とかでコンパイルをすればなんとかかるのではと考えています。

## rustlsを利用する(2020/04/26 02:30頃追記)

コメントで助言いただきました。
解決方法の一つとして`openssl`以外のライブラリでを利用することでクロスコンパイルを回避できるようです。
参考にいただいた記事は`hyper`のものでしたがそれをラップしている`reqwest`でも選べそうです。

特にTLSのパッケージを何にするというこだわりはないので`rustls`を使うことにします。
`Cargo.toml`に以下のように書くことで`optional features`を入れることができるみたいです。
`default-features`を無効にしておかないと`openssl-sys`が入ってようなので合わせて無効化しておきます。

```toml
reqwest = { version="0.10.4", default-features = false, features = ["rustls-tls"]}
```

今回は手動で書いてみましたが、`cargo-edit`でもできるか後で調べてみます。

`openssl`->`rustls`の変更であれば、今回のソースコードから変更はいらないようです。
`reqwest::get()`の部分の説明に`native TLS backend cannot be initialized`とあるので
`native TLS`を使うのであれば変更がいりそうな感じがあります。(英語力が低い)


# 2日目を終えて

色々うまくいかず、方向転換もありでなかなか進みませんでした。
コードはほとんどかけていないですが裏で崩して記事に出していない部分での調査や`async/await`の内容の理解など
Rustに対する理解は１日目より深まった気がします。

今まであまり意識したことはなかったですが、
新しい言語の触りはじめの頃はこういった開発基盤を整えるところにどうしても時間を食われがちになってしまいますね。
スクリプト言語のお手軽さやIDEのコンパイル機能のありがたさを改めて実感します。
C言語は昔少し触っただけでしたがRustでこれならあれも極めてくとすごいんだろうなって思います。

`google-calendar3`を使わなくなったこともあり、
次はGoogle Calendar APIのために全然理解していないOauth認証をなんとかしないので３日目はしばらく先になる気がします。