はじめに
JAWS Frameworkを使ってお手軽にLambda+API GatewayでHelloWorldするメモ で、
とりあえず感触は掴めたので、実際にちょっとしたWebServiceを作ってみました。
前提として、上記記事の手順は経ているものとして、
JAWSを使ったサービス開発の時のハマリポイントや悩んだことのメモを共有します。
作ったWebServiceの概要
機能
- ファイルをWebブラウザからS3へUpload (Downloadパスワードと有効期限を指定)
- Upload Keyというのが別途必要で、 期限付きUpload Key を Master Key から作成できる
- 有効期限付きのDownload Link が 表示される
- そのLinkへ行くと、パスワードが求められ、有効期限内ならばファイルがDownloadできる
- Uploadしたファイルは7日間で自動的に削除される(有効期限とは無関係)
仕組み
- API Gateway + Lambda + S3 で動いている
File Upload時の動作
-
createsessiontoken
API を呼びだして Upload用のCredentialを取得 - ブラウザから直接S3にUpload (パスワードはこのURL(S3 ObjectKey)に反映されている)
- Uploadした ObjectKey で
creategetobjecturl
API を呼びだして S3の期限付きURLを作成 - S3の期限付きURL をちょっと加工して、Download URLを作成
File Download時の動作
- Formに入力してもらうパスワードとDownload URLに埋めてあるパラメータから、 S3の期限付きURL を復元してRedirect
- パスワードが間違っていると S3からエラーと冷たく言われる・・・
ソースコード
【本題】JAWSを使った開発の時のハマリポイントや悩んだこと
- JAWS version: 1.3.3
参考になるソースコード
なかなか情報が少ないですが、
https://github.com/awsm-org/awsm-images がありました。
sha256などのパッケージの導入方法
今回 sha256
の npm パッケージを使ったのですが、これを index.js からどう参照するのか、ということです。
そんなに難しいことはなく、
npm install --save sha256
として、
var sha256 = require("sha256");
などとすればOKでした。
環境情報の入れ方、参照方法
これがなかなか悩みました。
設定や取得や一覧は マニュアルにも書いてあるとおり、
% jaws env set dev all <KEY> <VAL>
などでいけますが、プログラム中からの参照をどうすれば良いのか。
わかれば簡単で、 HASH_SECRET
という環境変数は process.env.HASH_SECRET
で参照できます。
awsm.jsonのenvVarsの意味
最初 awsm.jsonのenvVars
の意味がわからなかったのですが、プログラム中で環境変数を参照している場合ここに記述しておくと、
% jaws env list dev all
としたときに 「定義されていない変数を警告してくれる」 機能があります。
定義し忘れると、環境が変わった時に実行時エラーになって辛いので、忘れずに書いておくと良いと思います。
自前ロジックの共有
複数の Lambda Functionから参照される自前のロジックをどこにおいてどう共有すればよいか。
ですが、 index.js
の 一つ上のDirに upload_key.js
などとして
var UploadKey = function(secret) {
...
};
exports.UploadKey = UploadKey;
とかいておけば、
var AWS = require('aws-sdk'),
keyUtil = require('../upload_key').UploadKey(process.env.HASH_SECRET);
というように参照できます。
Error Responseの定義
Web APIとして動作する場合には、HTTP Statusが大きな意味を持ちます。
LambdaのResponse をどうやって、 API Gatewayの HTTP Statusとして反映させるか。
いくつか方法がありますが、 awsm.json
に
"Responses": {
"400:.*": {
"statusCode": "400",
"responseParameters": {
"method.response.header.Access-Control-Allow-Origin": "'*'"
}
},
と書いておくと、 callbackのエラー文字列に 400:.*
の時に Status 400にMappingしてくれる定義を作ってくれます。(デプロイしたあとAPI Gatewayの設定を見るとわかります)
そうすると、 cb('400: Bad Request!!', null);
という Callbackを返せば、HTTP Statusが400でクライアント側に返るようになります。
CORSの設定
Web BrowserからAPIを呼び出す場合、 CORSの対応をする必要がある場合があります。
それも awsm.json
の responseParameters
にこのように
"default": {
"statusCode": "200",
"responseParameters": {
"method.response.header.Access-Control-Allow-Origin": "'*'",
"method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization'",
"method.response.header.Access-Control-Allow-Methods": "'POST'"
},
"responseModels": {},
"responseTemplates": {
"application/json": ""
}
}
書いておくとGETなどのときはOKなのですが、
POST リクエストの場合に発生する pre-flight の OPTIONS メソッドに対する定義を同時に行うことができません。
https://github.com/jaws-framework/JAWS/issues/222
などでも話されているようですが、現時点ではJAWSではまだ未対応のようです。
※ 対応されるのは時間の問題でしょうが…
AWS の WebConsoleでAPI Gatewayの設定を行えばOKなのですが、
多少楽ができる方法を書いておきます.
JAWSを使って OPTIONSの設定を少しだけ楽をする
-
対象APIの
awsm.json
のMEHTODをOPTIONS
に変更して endpoint をデプロイする(→ API GatewayのMethodにOPTIONSが追加される(POSTも残っている)) -
このままだとエラーになるので、もう一度
POST
などと書き戻して endpoint をデプロイする(API Gatewayの OPTIONSの設定は残る) -
AWS の WebConsoleを開く
-
Integration Request の Type を Mock に変更する
-
今度はIntegration Responseの中を開いて, Header Mappingsの Mapping valueが空になっている(Mockにしたから)ので、そこを適切に埋める
- WebConsoleから手動でAPIをデプロイする
という感じです。
一回だけやっておけばOKなので、まあなんとか。
event.jsonの意味
(追記:2015/10/27)
コメント頂いたのでここにも書いておきます。
event.js
は jaws run
を実行した時に index.js
の
// Export For Lambda Handler
module.exports.run = function (event, context, cb) {
...
event に渡されるJSONみたいです。Localの簡易動作確認用です。
jaws run
は例えばこんな感じに使います。
cd aws_modules/fileexchange/creategetobjecturl
jaws run
値を変えてテストしようとすると、
今は毎回event.json
を書き換えないといけないのが少し面倒ですが、いずれ引数でファイルを指定できたりいろいろ工夫がされるのではないかと思います。
※ TOPのディレクトリにtests/
というのも作られるけど、使い方は不明・・・
API GatewayからLambdaへの QueryString や POST Dataの渡し方
(追記:2015/10/27)
API Gatewayの書き方が独特で私はあまり詳しくないですが、例えばこういう風に書くことができます
GET リクエストの QueryStringの場合
"RequestTemplates": {
"application/json": "{\"key\":\"$input.params('key')\", \"objKey\":\"$input.params('objKey')\"}"
},
Escapeが色々酷くて見難いですが、
これによって、 QueryStringの key
と objKey
の値が、 event.key
と event.objKey
にMappingされてLambda側に渡ってきます。
どういう書き方ができるかはAWS公式のDocument を見ると良いと思います。
POSTリクエストでBODYがJSON形式の場合
この場合何も書かなくてもそのまま Lambda側の event
Objectに渡ってくるようです。
深く理解できてないので、なんでなのかよくわかりませんが。
この辺は今後もちょくちょく仕様が変わるんじゃないかなぁ、という気がします。
さいごに
このくらいのサービスをだいたい1日で作れたので、JAWSはなかなか実用的だと思います。
最初は戸惑いますが、慣れると試行錯誤サイクルも早くなるし、やってみる価値はあると思います!