RustとLambdaでなんか作る 前編


最近、カメネタばかりでしたがひと段落着いたので新ネタ行ってみましょう。
で、最近気になってるのがRust.

Rustって?

wikipediaさんによるとこんな感じです。

Rust(ラスト)はMozillaが支援する[2][3]オープンソースのシステムプログラミング言語である。

Rust言語は速度、並行性、安全性を言語仕様として保証するC言語、C++に代わるシステムプログラミング(英語版)に適したプログラミング言語を目指している[4]。2006年の開発初期はグレイドン・ホアレの個人プロジェクトだったが、2009年にMozillaが開発に関わり始めてMozilla Researchの公式プロジェクトとなった[2]。2015年に1.0版がリリースされるまでにいくつもの破壊的な仕様変更があったが、1.0版以降は基本的には後方互換を保って6週間間隔で定期的にリリースされている。プロジェクトはオープンソースのコミュニティベース開発で進行しており[5]、言語仕様(検討段階含む)、ソースコード、ドキュメントはオープンソースライセンスで公開されている[6]。

Rustはマルチパラダイムプログラミング言語であり、手続き型プログラミング、オブジェクト指向プログラミング、関数型プログラミングなどの実装手法をサポートしている。基本的な制御フローはC言語に似ているが、ほぼ全ての命令文が式(expression)であるという点においてはML言語に似ている。コンパイル基盤にMIRとLLVMを用いており[7]、実行時速度性能はC言語と同等程度である[8]。強力な型システムとリソース管理の仕組みにより、メモリセーフ(英語版)な安全性が保証されている。

Rustは2016年、2017年のStack Overflow Developer Surveyで「最も愛されているプログラミング言語」で一位を獲得している[9][10]。一方で、Rustは学習難易度が高い言語とも考えられており[11]、2017年ロードマップでは学習曲線の改善を目的として挙げていた[12]。

で、何やります?

Serverless FrameworkでAWS Lambdaファンクションをデプロイする(Rust版)

Lambdaのカスタムランタイムとして使えるらしいです。
となればweb apiは作れるな。最近google homeのアプリ作ってないし、webhookをRustで作りつつ、なんか一個作ってみますか。
というわけで、

ゴール: Rust使ったgoogle homeのアプリを一個リリースする

とします。
まあ、たぶん遅いでしょうけどね。少し大きめなパッケージになると思うので。また、余力があればLambda Layersに乗っけときたい。
しかし何のアプリを作るかは何も決めてないというめちゃくちゃなスタートであります。

serverless rust

https://github.com/softprops/serverless-rust
クラスメソッドさんブログに倣い、上記をセットアップします。

まず、$ npm i -D serverless-rust

で、まずこのサンプルをやってみようと思ったんだけどnpxが使えないといけないらしい。
ただ、npxはnpmの5.2.0からの機能??

$ node --version
v6.14.3
$ npm --version
3.10.10

まあ、入れた時にもどっかで引っかかるやろうなと思ってたけどね。
さて、yumで入れてるんだけどどうしたもんか。
とりあえずユーザー権限なnvmでやってしまおう。

$ curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh | bash
$source ~/.bashrc
$nvm install stable
$ which node
~/.nvm/versions/node/v11.7.0/bin/node
$ which npx
~/.nvm/versions/node/v11.7.0/bin/npx

お、なんかいけそう。
あとはこれに沿って。
https://github.com/softprops/serverless-aws-rust

$ npx serverless install \
  --url https://github.com/softprops/serverless-aws-rust \
  --name my-new-app \
  && cd my-new-app \
  && npm i \
  && npx serverless deploy

ちなみにdeployの時にdockerを起動していなくてはいけなかったから起動したのと、ユーザー権限でdockerコマンド打てなかったのでsudo usermod -a -G docker ec2-userしてしまっております。

環境

そしてここにきて環境。整ってきましたという事で。
というかdeploy長くて待ってるの暇なので。

  • Amazon Linux 2(ec2) Linux 4.14.88-88.73.amzn2.x86_64
  • nodejs v11.7.0
  • npm 6.5.0

テスト

はい。deploy終わりました。関数できてた。

なんか動いてるっぽい。
ちなみにソースはこんな感じでした。

main.rs
use lambda_runtime::{error::HandlerError, lambda, Context};
use serde_json::Value;

fn main() {
    lambda!(handler)
}

fn handler(
    event: Value,
    _: Context,
) -> Result<Value, HandlerError> {
    Ok(event)
}

#[cfg(test)]
mod tests {
    use super::*;
    use serde_json::json;

    #[test]
    fn handler_handles() {
        let event = json!({
            "answer": 42
        });
        assert_eq!(
            handler(event.clone(), Context::default()).expect("expected Ok(_) value"),
            event
        )
    }
}

まあ、なんとなくeventに入ってきたのをオウム返ししてるのかなって思う。
じゃあこっちは?
https://github.com/softprops/serverless-aws-rust-http

use lambda_http::{lambda, IntoResponse, Request};
use lambda_runtime::{error::HandlerError, Context};
use serde_json::json;

fn main() {
    lambda!(handler)
}

fn handler(
    _: Request,
    _: Context,
) -> Result<impl IntoResponse, HandlerError> {
    // `serde_json::Values` impl `IntoResponse` by default
    // creating a application/json response
    Ok(json!({
    "message": "Go Serverless v1.0! Your function executed successfully!"
    }))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn handler_handles() {
        let request = Request::default();
        let expected = json!({
        "message": "Go Serverless v1.0! Your function executed successfully!"
        })
        .into_response();
        let response = handler(request, Context::default())
            .expect("expected Ok(_) value")
            .into_response();
        assert_eq!(response.body(), expected.body())
    }
}

なるほど。lambda_httpっていうのを使わなくちゃいけないのか。
ためしに、さっきのechoのやつのserverless.xmlいじってapi gatewayのリソースは追加してみてたんだけどhttp経由はエラーになってうまく動かなかった。

ためしにmain.rsだけ書き換えて、またdeployしてみる。

・・したら怒られた。

error[E0432]: unresolved import `lambda_http`
 --> src/main.rs:1:5
  |
1 | use lambda_http::{lambda, IntoResponse, Request};
  |     ^^^^^^^^^^^ use of undeclared type or module `lambda_http`

ライブラリ足りんか。
Cargo.tomlにlambda_http = { git = "https://github.com/awslabs/aws-lambda-rust-runtime.git" }を追記してもう一度・・・
やったらまたlabmda_runtimeがどうのっていわれてlambda_runtime = "0.1"ってなってたのをlambda_runtime = { git = "https://github.com/awslabs/aws-lambda-rust-runtime.git" }に書き換える。githubの方がversion新しいんやろか。少なくともhttpのを相違があるんやろな。
というわけで・・

いけたー!

curl -XGET  https://xxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/hello
{"message":"Go Serverless v1.0! Your function executed successfully!"}

ちなみにserverless.ymlはこんなんなってます。regionとhttpのとこ有効にした程度ですね。(コメントはだいたい省略)

# Welcome to Serverless!

service: my-new-app # NOTE: update this with your service name
provider:
  name: aws
  runtime: rust
  memorySize: 128
# you can overwrite defaults here
  stage: dev
  region: ap-northeast-1

# you can define service wide environment variables here
  environment:
    variable1: value1

package:
  individually: true

plugins:
  - serverless-rust

functions:
  hello:
    # handler value syntax is `{cargo-package-name}.{bin-name}`
    # or `{cargo-package-name}` for short when you are building a
    # default bin for a given package.
    handler: hello
#    The following are a few example events you can configure
#    NOTE: Please make sure to change your handler code to work with those events
#    Check the event documentation for details
    events:
      - http:
          path: hello
          method: get

あと、mod testsのとこ、動作に直接は関係ないよね?ってとこをハッキリさせたくて一回消してみてます。

とりあえず本日はhttp経由で叩けたとこまで。あっさりですみません。
Rustはまだぜんぜんわかってません。とりあえずなんか受け取ってjson返せればgoogle homeアプリぐらいはいけそうなんだけど。
そのあたりは中編でなんとか。