初めに
1日目の続きです。
釈明
(釈明の日本語の使い方あってるのか)
本当は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も最近ちょっと触っただけなのでよく知らないですが)
とりあえずインストールします。
$ 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ブロック内でしか使えないようなので、
一番大元の部分は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>
でしょなんで???って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);
のコメントアウトについては理由があるので後述します。
呼び出し元(不要そうな部分は省略しています)
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);
}
モジュール側
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"
とりあえず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日目はしばらく先になる気がします。