6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

RustとLambdaでなんか作る 中編

Last updated at Posted at 2019-01-20

前回の続きです。

Rustがlambdaで動いてapi gateway経由でHello Worldするとこまでやりました。
本日は、ひとまずhttps://github.com/awslabs/aws-lambda-rust-runtime/tree/master/lambda-http/examples に乗ってるbasic.rsを見てみる。
まだなんとなくわかる程度です。

basic.rsをdeployしてみる

basic.rs
use std::error::Error;

use lambda_http::{lambda, IntoResponse, Request, RequestExt, Response};
use lambda_runtime::{error::HandlerError, Context};
use log::{self, error};
use simple_logger;

fn main() -> Result<(), Box<dyn Error>> {
    simple_logger::init_with_level(log::Level::Debug).unwrap();
    lambda!(my_handler);

    Ok(())
}

fn my_handler(e: Request, c: Context) -> Result<impl IntoResponse, HandlerError> {
    Ok(match e.query_string_parameters().get("first_name") {
        Some(first_name) => format!("Hello, {}!", first_name).into_response(),
        _ => {
            error!("Empty first name in request {}", c.aws_request_id);
            Response::builder()
                .status(400)
                .body("Empty first name".into())
                .expect("failed to render response")
        }
    })
}

Cargo.tomlにlog = "^0.4"simple_logger = "^1"を追記してます。

実行してみると・・

$ curl -XGET  "https://xxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/hello?aaa"
Empty first name
$ curl -XGET  "https://xxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/hello?first_name=bob"
Hello, bob!

なるほどー。
lambda_httpのrequest.rsみると・・

/// Internal representation of an Lambda http event from both
/// both ALB and API Gateway proxy event perspectives
#[doc(hidden)]
#[derive(Deserialize, Debug, Default)]
#[serde(rename_all = "camelCase")]
pub(crate) struct LambdaRequest<'a> {
    pub(crate) path: Cow<'a, str>,
    #[serde(deserialize_with = "deserialize_method")]
    pub(crate) http_method: Method,
    #[serde(deserialize_with = "deserialize_headers")]
    pub(crate) headers: HeaderMap<HeaderValue>,
    /// For alb events these are only present when
    /// the `lambda.multi_value_headers.enabled` target group setting turned on
    #[serde(default, deserialize_with = "deserialize_multi_value_headers")]
    pub(crate) multi_value_headers: HeaderMap<HeaderValue>,
    #[serde(deserialize_with = "nullable_default")]
    pub(crate) query_string_parameters: StrMap,
    /// For alb events these are only present when
    /// the `lambda.multi_value_headers.enabled` target group setting turned on
    #[serde(default, deserialize_with = "nullable_default")]
    pub(crate) multi_value_query_string_parameters: StrMap,
    /// ALB events do not have path parameters.
    #[serde(default, deserialize_with = "nullable_default")]
    pub(crate) path_parameters: StrMap,
    /// ALB events do not have stage variables.
    #[serde(default, deserialize_with = "nullable_default")]
    pub(crate) stage_variables: StrMap,
    pub(crate) body: Option<Cow<'a, str>>,
    #[serde(default)]
    pub(crate) is_base64_encoded: bool,
    pub(crate) request_context: RequestContext,
}

って書いてあったりするから、たぶんeのオブジェクトからはbodyもとれる。たぶん。

いじる

少しいじって動作検証です。
まずPOSTにapi gateway対応するようにしてjson投げてみました。

curl -XPOST  "https://xxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/hello?first_name=bob" -d @package.json
Hello, bob!

で、https://github.com/awslabs/aws-lambda-rust-runtime/blob/master/lambda-http/src/ext.rs にexampleがあったのでそれを参考にいじってみた結果、とりあえず動いた。

xy.json
{
  "name": "bob",
  "x": 1,
  "y": 2
}
main.rs
use std::error::Error;

use lambda_http::{lambda, Request, Response, Body};
use lambda_runtime::{error::HandlerError, Context};
use log::{self, info};
use simple_logger;

#[macro_use]
extern crate serde_derive;
extern crate serde;
extern crate serde_json;

#[derive(Debug,Deserialize,Default)]
struct Args {
  #[serde(default)]
  x: usize,
  #[serde(default)]
  y: usize,
  #[serde(default)]
  name: String
}

fn main() -> Result<(), Box<dyn Error>> {
    simple_logger::init_with_level(log::Level::Info).unwrap();
    lambda!(my_handler);

    Ok(())
}

fn my_handler(e: Request, c: Context) -> Result<Response<Body>, HandlerError> {
  let args: Args = serde_json::from_slice(e.body().as_ref()).unwrap();
  info!("{:#?}", args);
  Ok(
   Response::new(
      format!(
       "{}: {} + {} = {}",
       args.name,
       args.x,
       args.y,
       args.x + args.y
      ).into()
    )
  )
}
$  curl -XPOST -H "Accept: application/json" -H "Content-type: application/json" "https://xxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/hello?first_name=bob" -d @xy.json
bob: 1 + 2 = 3

おっけー。json読めてる。
Rustはまだわかってないので見よう見まねですが。

Hello worldしてみる

そんなわけでgoogle homeに適当なサンプル作ってjson投げてみる事にします。
このへん書いてると大変なのでサックリとでお許しください。

actions on google

  • サンプルアプリ作る

58.png

カテゴリは深く考えず、Games & funにしときました。
Display nameはとりあえずRust Sample。また後でちゃんと作ります。

  • Custom intent

buildメニューではCustom intentを選択し、Dialog Flowを開きます。

59.png

dialog flow

  • Enable Webhook

アプリのネームと同じprojectを作ったのち、おもむろにDefault Welcome Intentを開いて、Enable webhook call for this intentを有効にし、いきなりwebhookに投げる設定にします。
なお、defaultのレスポンスはややこしいから消しました。

60.png

  • webhook url

fullfillmentを開いて、webhookの所にurlを入力します。

61.png

RequestとResponse

Dialog flowから投げられるRequestと期待されてるResponseを確認しておきます。
Welcomeの例

{
  "responseId": "e7036551-378f-4f7c-a177-3a715936c011",
  "queryResult": {
    "queryText": "こんにちは",
    "action": "input.welcome",
    "parameters": {},
    "allRequiredParamsPresent": true,
    "intent": {
      "name": "projects/rust-sample-f3e72/agent/intents/xxxxxxxx-523d-4aa6-a864-49a6ce3236c3",
      "displayName": "Default Welcome Intent"
    },
    "intentDetectionConfidence": 1,
    "languageCode": "ja"
  },
  "originalDetectIntentRequest": {
    "payload": {}
  },
  "session": "projects/rust-sample-f3e72/agent/sessions/xxxxxxxx-14f5-22d0-e2c2-0831282d5ce2"
}
{
  "fulfillmentText": "Welcome to my agent!",
  "outputContexts": []
}

ざっくりいうと、queryResult.queryTextを受け取ってfulfillmentTextを返せればおっけー。
jsonは一つネストしてるなー。どうやって取り出すんだろ。

やってみる

なんか適当にやってみたらとりあえずできました。

:main.rs
use std::error::Error;

use lambda_http::{lambda, Request, Response, Body};
use lambda_runtime::{error::HandlerError, Context};
use log::{self, info};
use simple_logger;

#[macro_use]
extern crate serde_derive;
extern crate serde;
extern crate serde_json;

#[derive(Debug,Deserialize,Default)]
struct QueryResult {
  #[serde(default)]
  queryText: String
}
#[derive(Debug,Deserialize,Default)]
struct Req {
  #[serde(default)]
  queryResult: QueryResult
}

fn main() -> Result<(), Box<dyn Error>> {
    simple_logger::init_with_level(log::Level::Info).unwrap();
    lambda!(my_handler);

    Ok(())
}

fn my_handler(e: Request, c: Context) -> Result<Response<Body>, HandlerError> {
  let query: Req = serde_json::from_slice(e.body().as_ref()).unwrap();
  info!("{:#?}", query);
  Ok(
   Response::new(
      format!(
       r#"{{"fulfillmentText": "you said {}"}}"#,
       query.queryResult.queryText
      ).into()
    )
  )
}

62.png

でもアプリ側で日本語が文字化けしてる。。

こういう感じの問題でしょうか。
https://qiita.com/aoriso/items/a16a981d00d041c8f6a2

Lambdaで設定するレスポンスのヘッダに"charset=UTF8"を追加したら解決した

とありますので、

  Ok(
   Response::builder()
     .status(200)
     .header("Content-Type", "application/json; charset=UTF-8")
     .body(
       format!(
         r#"{{"fulfillmentText": "you said {}"}}"#,
         query.queryResult.queryText
         ).into()
      )
     .expect("none")
  )

Response返すとこでContent-Type指定してやったら直りました。

63.png

ちなみにCargo.tomlはこんな感じになってます。

[package]
name = "hello"
version = "0.1.0"
edition = "2018"

[dependencies]
lambda_runtime = { git = "https://github.com/awslabs/aws-lambda-rust-runtime.git" }
lambda_http = { git = "https://github.com/awslabs/aws-lambda-rust-runtime.git" }
log = "^0.4"
simple_logger = "^1"
serde = "^1.0"
serde_derive = "^1.0"
serde_json = "^1.0"

そんなわけで中編はここまで。
もう中編だけど何作るかは決まっておりません。

しかし、C言語、C++に代わる言語って話だったけど、触ってみた感じはnodeとかの方が似てるかなー。と感じる。
でもってRustはまだexampleから見よう見まねしてるだけの段階なので何もわかっていません。作りながら調べつつ覚えていく感じなので生暖かく見守っていてくださいませ。

6
1
0

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
6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?