LoginSignup
2
0

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-04-25

初めに

1日目の続きです。

釈明

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

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

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

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

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

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

cargo-edit導入してみる

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

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

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

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

$ 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を開きながらやったら挿入されているシーンを拝めました。
残りのパッケージもどこかでバージョンを固定しておきましょう。

[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以外の記事がいまいち見当たらなかったのですが(多分探し方が悪い)、
ローカルに置いてあるクレートを使う場合は以下みたいな感じで指定すればいいらしいです。

[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ブロック内でしか使えないようなので、
一番大元の部分はfeaturetokioなどを使う必要があるみたいです。

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>でしょなんで???って5時間くらい悩んでました)

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

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

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

reqwestでGET通信を行う

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

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

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

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

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);
}

モジュール側

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);のコメントアウトがあったかと思います。
これの理由としてまずローカルで動かすためです。
1日目の記事に記載しましたがローカルでlambda向けの書き方をしていると環境変数ガーとなって実行できないので、
一旦コメントアウトしローカルで実行でき状態にするためです。

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

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

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

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が入ってようなので合わせて無効化しておきます。

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に対する理解は1日目より深まった気がします。

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

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

2
0
4

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
2
0