JAWS FrameworkでWebブラウザからS3にファイルをアップロードして、有効期限付きのダウンロード付きURLとパスワードを生成するサービスを作ってみた、時の開発メモ


はじめに
========

[JAWS Frameworkを使ってお手軽にLambda+API GatewayでHelloWorldするメモ](http://qiita.com/mokemokechicken/items/f81c886053f7d81a655d) で、
とりあえず感触は掴めたので、実際にちょっとしたWebServiceを作ってみました。

前提として、上記記事の手順は経ているものとして、
JAWSを使ったサービス開発の時のハマリポイントや悩んだことのメモを共有します。

作ったWebServiceの概要
-----------
### 機能
* ファイルをWebブラウザからS3へUpload (Downloadパスワードと有効期限を指定)
	* Upload Keyというのが別途必要で、 **期限付きUpload Key** を **Master Key** から作成できる
* **有効期限付きのDownload Link** が 表示される
* そのLinkへ行くと、パスワードが求められ、有効期限内ならばファイルがDownloadできる
* Uploadしたファイルは７日間で自動的に削除される(有効期限とは無関係)

![cap.png](https://qiita-image-store.s3.amazonaws.com/0/11124/3369977d-b6ff-e29e-dd28-85aad0991b04.png "cap.png")

### 仕組み
* API Gateway + Lambda + S3 で動いている

#### File Upload時の動作

1.  `createsessiontoken` API を呼びだして Upload用のCredentialを取得
2. ブラウザから直接S3にUpload (パスワードはこのURL(S3 ObjectKey)に反映されている)
3. Uploadした ObjectKey で `creategetobjecturl` API を呼びだして **S3の期限付きURLを作成**
4. **S3の期限付きURL** をちょっと加工して、Download URLを作成

#### File Download時の動作

1. Formに入力してもらうパスワードとDownload URLに埋めてあるパラメータから、 **S3の期限付きURL** を復元してRedirect
	* パスワードが間違っていると S3からエラーと冷たく言われる・・・

### ソースコード
https://github.com/mokemokechicken/FileTransfer

【本題】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` などとして

```javascript:upload_key.js
var UploadKey = function(secret) { 
	...
};

exports.UploadKey = UploadKey;
```

とかいておけば、

```javascript:index.js
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`に

```awsm.json
      "Responses": {
        "400:.*": {
          "statusCode": "400",
          "responseParameters": {
            "method.response.header.Access-Control-Allow-Origin": "'*'"
          }
        },
```

と書いておくと、 callbackのエラー文字列に `400:.*` の時に Status 400にMappingしてくれる定義を作ってくれます。(デプロイしたあとAPI Gatewayの設定を見るとわかります)
![API_Gateway.png](https://qiita-image-store.s3.amazonaws.com/0/11124/ba419ae2-e623-b7ef-3b73-d1f8b12e38ec.png "API_Gateway.png")

そうすると、 `cb('400: Bad Request!!', null);` という Callbackを返せば、HTTP Statusが400でクライアント側に返るようになります。

CORSの設定
---------

Web BrowserからAPIを呼び出す場合、 CORSの対応をする必要がある場合があります。

それも `awsm.json` の `responseParameters` にこのように

```awsm.json
        "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の設定を少しだけ楽をする

1. 対象APIの`awsm.json`のMEHTODを `OPTIONS` に変更して endpoint をデプロイする（→ API GatewayのMethodにOPTIONSが追加される(POSTも残っている))
2. このままだとエラーになるので、もう一度 `POST` などと書き戻して endpoint をデプロイする(API Gatewayの OPTIONSの設定は残る)
3. AWS の WebConsoleを開く
4. **Integration Request** の Type を **Mock** に変更する

	![API_Gateway.png](https://qiita-image-store.s3.amazonaws.com/0/11124/7e4434a7-b744-2695-f451-c136270be2ff.png "API_Gateway.png")

5. 今度は**Integration Response**の中を開いて, **Header Mappings**の **Mapping value**が空になっている(Mockにしたから）ので、そこを適切に埋める

![API_Gateway.png](https://qiita-image-store.s3.amazonaws.com/0/11124/9900a26a-485e-b434-46f6-1bd0758a40f5.png "API_Gateway.png")

6. WebConsoleから手動でAPIをデプロイする

という感じです。
一回だけやっておけばOKなので、まあなんとか。

event.jsonの意味 
---------------
(追記:2015/10/27)
コメント頂いたのでここにも書いておきます。
`event.js`は `jaws run` を実行した時に `index.js` の

```javascript:index.js
// Export For Lambda Handler
module.exports.run = function (event, context, cb) {
...
```

event に渡されるJSONみたいです。Localの簡易動作確認用です。
`jaws run`は例えばこんな感じに使います。

```sh:cmd
cd aws_modules/fileexchange/creategetobjecturl
jaws run
```

値を変えてテストしようとすると、
今は毎回`event.json`を書き換えないといけないのが少し面倒ですが、いずれ引数でファイルを指定できたりいろいろ工夫がされるのではないかと思います。

※ TOPのディレクトリに`tests/` というのも作られるけど、使い方は不明・・・

API GatewayからLambdaへの QueryString や POST Dataの渡し方
-----------
(追記:2015/10/27)

API Gatewayの書き方が独特で私はあまり詳しくないですが、例えばこういう風に書くことができます

### GET リクエストの QueryStringの場合

```json:awsm.json
      "RequestTemplates": {
        "application/json": "{\"key\":\"$input.params('key')\", \"objKey\":\"$input.params('objKey')\"}"
      },
```

Escapeが色々酷くて見難いですが、
これによって、 QueryStringの `key` と `objKey` の値が、 `event.key` と `event.objKey` にMappingされてLambda側に渡ってきます。

どういう書き方ができるかは[AWS公式のDocument](http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html) を見ると良いと思います。

### POSTリクエストでBODYがJSON形式の場合
この場合何も書かなくてもそのまま Lambda側の `event` Objectに渡ってくるようです。
深く理解できてないので、なんでなのかよくわかりませんが。
この辺は今後もちょくちょく仕様が変わるんじゃないかなぁ、という気がします。

さいごに
=======

このくらいのサービスをだいたい１日で作れたので、JAWSはなかなか実用的だと思います。
最初は戸惑いますが、慣れると試行錯誤サイクルも早くなるし、やってみる価値はあると思います！