前回の続きです。
Rustがlambdaで動いてapi gateway経由でHello Worldするとこまでやりました。
本日は、ひとまずhttps://github.com/awslabs/aws-lambda-rust-runtime/tree/master/lambda-http/examples に乗ってるbasic.rsを見てみる。
まだなんとなくわかる程度です。
basic.rsをdeployしてみる
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があったのでそれを参考にいじってみた結果、とりあえず動いた。
{
  "name": "bob",
  "x": 1,
  "y": 2
}
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
- サンプルアプリ作る
 
カテゴリは深く考えず、Games & funにしときました。
Display nameはとりあえずRust Sample。また後でちゃんと作ります。
- Custom intent
 
buildメニューではCustom intentを選択し、Dialog Flowを開きます。
dialog flow
- Enable Webhook
 
アプリのネームと同じprojectを作ったのち、おもむろにDefault Welcome Intentを開いて、Enable webhook call for this intentを有効にし、いきなりwebhookに投げる設定にします。
なお、defaultのレスポンスはややこしいから消しました。
- webhook url
 
fullfillmentを開いて、webhookの所にurlを入力します。
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は一つネストしてるなー。どうやって取り出すんだろ。
やってみる
なんか適当にやってみたらとりあえずできました。
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()
    )
  )
}
でもアプリ側で日本語が文字化けしてる。。
こういう感じの問題でしょうか。
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指定してやったら直りました。
ちなみに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から見よう見まねしてるだけの段階なので何もわかっていません。作りながら調べつつ覚えていく感じなので生暖かく見守っていてくださいませ。





