メンチカツには醤油でしょ!!

AWS/Java/Node.js/Spreadsheets/Docker/Jenkins/コミュニティ・勉強会レポを主とした技術系ブログ

AWS LambdaでCannot find module (Windowsからgulp-zipでupload)

そもそものよくある問題は次の参考サイトへ

qiita.com

lealog.hateblo.jp

Windowsでのgulp-zipが問題?

Windowsでgulp-zipをした際に、Cannot find moduleが出ます。
シンプルにモジュール1つで試してみましたが、ダメでした。

github.com

コンソールからテスト

{
  "errorMessage": "Cannot find module 'moment'",
  "errorType": "Error",
  "stackTrace": [
    "Function.Module._load (module.js:417:25)",
    "Module.require (module.js:497:17)",
    "require (internal/module.js:20:19)",
    "Object. (/var/task/index.js:2:16)",
    "Module._compile (module.js:570:32)",
    "Object.Module._extensions..js (module.js:579:10)",
    "Module.load (module.js:487:32)",
    "tryModuleLoad (module.js:446:12)",
    "Function.Module._load (module.js:438:3)"
  ]
}

テスト実行時のCloudWatchのログは

Unable to import module 'index': Error
at Function.Module._load (module.js:417:25)
at Module.require (module.js:497:17)
at require (internal/module.js:20:19)
at Object. (/var/task/index.js:2:16)
at Module._compile (module.js:570:32)
at Object.Module._extensions..js (module.js:579:10)
at Module.load (module.js:487:32)
at tryModuleLoad (module.js:446:12)
at Function.Module._load (module.js:438:3)

indexなのかrequireのmoduleなのか。

だいたいがhandler名の問題だが…

// hello.js
exports.world = (event, context, callback) => {
  //
}

だとhello.worldで呼ぶべき、ともありますが、今回そんな感じではなさそう。
上記にも引用した AWSのLambdaで、zipにしたコードを動かす時にハマったこと - console.lealog(); に従って、WindowsのGit Bashで chmod +x index.js してみたが、うまくいかない。

Macだとうまくいくんだけど…う〜ん。。

ん、もしや…!?

WindowsだとダメでMacだとうまくいく

Windowsでnpm run make

zipアップ⇒だめ。
S3アップ⇒だめ。

Macでnpm run make

zipアップ⇒とおる。

 

gulp-zip ぁゃしぃ。。。

 

stackoverflow.com

.pipe(zip('simple-lambda.zip', { compress: false })) を試す。
⇒変わらずエラー

7-Zip(圧縮・解凍ソフト 7-Zip)で圧縮
⇒成功

ますます gulp-zip ぁゃしぃ。。。

これで解決

stackoverflow.com

gulp
    .src(['src/**'], { nodir: true, dot: true })
    .pipe(zip('simple-lambda.zip'))
    .pipe(gulp.dest('build/'));

nodirとdotの指定が必要でした。

HTTP Request Headerが正しく(?)送信されなかった問題

先頭の方は当初の記事で、訂正は後半に書きました。

そもそもリクエストヘッダーは大文字小文字を気にしないと、RFC 2616に書いてありますので、ロジック側で両方取れるように工夫すべきでした。

正しく送信されないのは小文字になってしまうから (訂正前)

X-My-Apps-Tokenx-my-apps-tokenになり、
API Gatewayのカスタムオーソライザー設定でのチェックは通過するが、
Lambdaのカスタムオーソライザーで下記のようなコードでトークンを取得しようとした際にうまく取得できません。

const myAppsToken = event.headers['X-My-Apps-Token'];

なお、API Gatewayのカスタムオーソライザーの設定では、
Lambda イベントペイロードにリクエストを設定していました。

アプリ版のPostmanを使用すると…

環境にもよると思います。

Windows + Chrome Postman : NG
Mac + Chrome Postman : ?
Windows + Postman Apps : OK
Mac + Postman Apps : OK

Chrome AppsのPostmanは既に非推奨なので、アップデートしましょう。 

Postmanだけでなくクライアントの仕様に依存

WindowsのGit Bashcurlしても同じ問題が発生しました。
あとで確認したら、Macのターミナルでも小文字で送信されてきました。。

デバッグ大事

Lambdaの先頭でconsole.log(event);してCloudWatchを見ましょう。

■ ここからが訂正版です ■

そもそも今回の原因が、API GatewayのカスタムオーソライザーからLambdaを呼び出す流れだったのですが、Lambdaの中で、

const myAppsToken = event.headers['X-My-Apps-Token'];

と書いていて取れないのが原因でした。大文字小文字を気にしないので

const myAppsToken = event.headers['x-my-apps-token'];

でも取れるべきです。
ここの記述をどうするのかベストなのかは、色々ありそうですが、
設計で小文字に寄せておけば丸められても問題なさそうです。

そもそも、カスタムオーソライザーの設定で、Lambdaイベントペイロードを、
トークンではなくリクエスで設定していたため、このような実装をしなければなりませんでしたが、トークン以外に拾うものがないため設定を戻しました。

リクエストではなくトークで設定した場合のLambdaの実装は

const token = event.authorizationToken;

となります。

当初はRequest Bodyの内容まで取ってトークンとbodyの内容の整合まで検証することを期待していたのでこういう設定にしていましたが、結局Request Bodyは取れないことが判ったので、むしろLambdaイベントペイロードにリクエストを設定するケースって何だろう…とも思います。

参考

stackoverflow.com

AWS API Gatewayにカスタムオーソライザーを設定してみたら、判りにくい事があった

1. オーソライザーの設定方法

f:id:ryoichi0102:20180218203159p:plain

この位置…なんかわかりにくい…

2. Execution failed due to configuration error: Invalid JSON in response

戻りのJSONが良くないのですが、どう良くないかって話ですよね。

https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/use-custom-authorizer.html#api-gateway-custom-authorizer-output

https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/api-gateway-control-access-using-iam-policies-to-invoke-api.html#api-gateway-calling-api-permissions

とりあえず動かす場合は、

callback(null, {
  "principalId": "ユーザーを特定できる文字列なら何でも良し",
  "policyDocument": {
    "Version": "2012-10-17",
    "Statement": [
      {
        "Action": "execute-api:*",
        "Effect": "Allow",
        // "Resource": "arn:aws:execute-api:ap-northeast-1:000000000000:yourApiGatewayId:*"
        "Resource": "*"
      }
    ]
  },
  "context": {
    "stringKey": "stringval",
    "numberKey": 123,
    "booleanKey": true
  }
});
}

contextにDynamo.getの戻り値(data)をセットするとうまくいかないです!!

"context": data

これハマりました。
(多分だけどJSON.stringify(data)すればいけるんだろうな…と後で思った)

policyDocumentのstatementにはIAMの指定とかではなく、IAMポリシーを作成してそれを指定するわけでもないんですね。

3. カスタムオーソライザーが呼び出されない

コンソール上のテストでは呼び出されるのに、Postmanやcurlだと呼び出されない。

⇒サイドメニュー"リソース" クリック
⇒カスタムオーソライザーを適用したいメソッドをクリック
⇒認証にて、作成したカスタムオーソライザーを指定

全部に自動的に効くわけじゃないんですね。。。面倒。

4. Invalid authorizer ID specified. Setting the authorization type to CUSTOM or COGNITO_USER_POOLS requires a valid authorizer.

画面リフレッシュしたら設定できることもあるかも知れません。
自分の場合は、オーソライザーを同じ名前で作り直したので、裏でそれが更新されていなかったのかも。

5. "Message": "User is not authorized to access this resource" が返ってくる

policyDocumentはちゃんと返してると思ったんですが、

"Resource": "*"

にしたら通りました。
少しづつ狭めて確認していきましょう。