新しいLambda functionのcontextを調べる

先週、AWS Lambdaのアップデートがされましたね！

[【AWS発表】AWS Lambda – モバイル開発のための新機能とともに全てがプロダクションに](http://aws.typepad.com/aws_japan/2015/04/aws-lambda-in-full-production-with-new-features-for-mobile-devs.html)

同期呼び出しがサポートされたことで、これでまともに2Tierとして使えるようになりました。
でも、`context.succeed`と`context.fail`って、どういう挙動させるんだろう？と気になったので`context`の中身を調べてみました。
前バージョンの`context`の中身は下記を参照してください。

* [Lambdaのcontextについて調べてみた](http://dev.classmethod.jp/cloud/research-lambda-context/)

## 検証コード

```js
exports.handler = function(event, context) {
    console.log(context);
    console.log(context.__proto__);
    console.log('awsRequestId', context.awsRequestId);
    console.log('invokeid', context.invokeid);
    console.log('logStreamName', context.logStreamName);
    console.log('succeed()', context.succeed.toString());
    console.log('fail()', context.fail.toString());
    console.log('done()', context.done.toString());
    
    context.done(null);
};
```

一度、`context`を`console.log`で出力して、表示された値をそれぞれ出力してあげる感じです。

## 出力結果

```
START RequestId: 09ecd985-e0ca-11e4-84c0-8b4f359a82ec
2015-04-12T04:11:49.738Z	09ecd985-e0ca-11e4-84c0-8b4f359a82ec	{ awsRequestId: '09ecd985-e0ca-11e4-84c0-8b4f359a82ec',
  invokeid: '09ecd985-e0ca-11e4-84c0-8b4f359a82ec',
  logStreamName: '2015/04/12/d76c581c256a434985cfe529e6274b64',
  succeed: [Function],
  fail: [Function],
  done: [Function] }
2015-04-12T04:11:49.797Z	09ecd985-e0ca-11e4-84c0-8b4f359a82ec	{}
2015-04-12T04:11:49.797Z	09ecd985-e0ca-11e4-84c0-8b4f359a82ec	awsRequestId 09ecd985-e0ca-11e4-84c0-8b4f359a82ec
2015-04-12T04:11:49.797Z	09ecd985-e0ca-11e4-84c0-8b4f359a82ec	invokeid 09ecd985-e0ca-11e4-84c0-8b4f359a82ec
2015-04-12T04:11:49.798Z	09ecd985-e0ca-11e4-84c0-8b4f359a82ec	logStreamName 2015/04/12/d76c581c256a434985cfe529e6274b64
2015-04-12T04:11:49.798Z	09ecd985-e0ca-11e4-84c0-8b4f359a82ec	succeed() function (result) {
                checkExpectedArgRange('succeed', arguments, 0, 1);
                if(isUndefined(result)) {
                    result = null;
                }
                try {
                    result = JSON.stringify(result);
                    finish_event_invoke(null, result);
                } catch(err) {
                    var errorObject = failParamToErrorObject(err)[1];
                    errorObject['errorMessage'] = "Unable to stringify body as json: " + errorObject['errorMessage'];
                    fail_event_invoke('unhandled', errorObject);
                }
            }
2015-04-12T04:11:49.798Z	09ecd985-e0ca-11e4-84c0-8b4f359a82ec	fail() function (error) {
                checkExpectedArgRange('fail', arguments, 0, 1);
                var multi_result = failParamToErrorObject(error); 
                fail_event_invoke(multi_result[0], multi_result[1]);
            }
2015-04-12T04:11:49.798Z	09ecd985-e0ca-11e4-84c0-8b4f359a82ec	done() function () {
                checkExpectedArgRange(arguments, 0, 2);
                if(isUndefinedOrNull(arguments[0]) && isUndefinedOrNull(arguments[1])) {
                    _this.succeed();
                } else if (isUndefinedOrNull(arguments[0])) {
                    _this.succeed(arguments[1]);
                } else if (isUndefinedOrNull(arguments[1])) {
                    _this.fail(arguments[0]);
                } else {
                    //support backwards compatibility by logging message here
                    console.log("Error Message: " + arguments[1]);
                    _this.fail(arguments[0]);
                }
            }
2015-04-12T04:11:49.798Z	09ecd985-e0ca-11e4-84c0-8b4f359a82ec	result: null
END RequestId: 09ecd985-e0ca-11e4-84c0-8b4f359a82ec
REPORT RequestId: 09ecd985-e0ca-11e4-84c0-8b4f359a82ec	Duration: 176.25 ms	Billed Duration: 200 ms 	Memory Size: 128 MB	Max Memory Used: 9 MB	
```

出力結果を見てみるといくつかのプロパティ、メソッドが追加されて、ログにも結果出力のログが増えてますね。
それぞれを細かくみていきます。

## context.logStreamName

```
2015-04-12T04:11:49.798Z    09ecd985-e0ca-11e4-84c0-8b4f359a82ec    logStreamName 2015/04/12/d76c581c256a434985cfe529e6274b64
```

ログストリームの名前が引き渡させるようになりました。
全く使い道がわかりませんが、このタイミングでログストリームの名前が確定されるということは、実行後にログを取得するときに待つ必要がなくなったのでは！？
まだ検証してませんがコマンドラインで実行→ログ取得を高速に回せるようになっていると良いなぁと思います。

## context.succeed

```
2015-04-12T04:11:49.798Z    09ecd985-e0ca-11e4-84c0-8b4f359a82ec    succeed() function (result) {
                checkExpectedArgRange('succeed', arguments, 0, 1);
                if(isUndefined(result)) {
                    result = null;
                }
                try {
                    result = JSON.stringify(result);
                    finish_event_invoke(null, result);
                } catch(err) {
                    var errorObject = failParamToErrorObject(err)[1];
                    errorObject['errorMessage'] = "Unable to stringify body as json: " + errorObject['errorMessage'];
                    fail_event_invoke('unhandled', errorObject);
                }
            }
```

同期呼び出しのサポートで追加された、成功時にレスポンスを返すメソッドです。
`succeed`にJSON変換可能な値を渡すとJSONに変換してレスポンスを返してくれるようになるようです。
そのため、それ以外のフォーマットでレスポンスを返すというのはできなさそうですね。

JSON変換成功時に呼び出している`finish_event_invoke`はグローバルな関数ではなく、`succeed`のスコープに隠蔽されているのでこのあたりを拡張するのは無理そうです。

## context.fail

```
2015-04-12T04:11:49.798Z    09ecd985-e0ca-11e4-84c0-8b4f359a82ec    fail() function (error) {
                checkExpectedArgRange('fail', arguments, 0, 1);
                var multi_result = failParamToErrorObject(error); 
                fail_event_invoke(multi_result[0], multi_result[1]);
            }
```

こちらも同期呼び出しのサポートで追加された、失敗時にレスポンスを返すメソッドです。
`failParamToErrorObject`で前の`context`のようにエラーの有無と、エラーメッセージ（or オブジェクト）に変換して`fail_event_invoke`渡しているのかなと妄想しますが、実際のところよくわかっていないです。
AWSの中の人にこのあたりの実装を聞きたいですね。

## context.done

```
2015-04-12T04:11:49.798Z    09ecd985-e0ca-11e4-84c0-8b4f359a82ec    done() function () {
                checkExpectedArgRange(arguments, 0, 2);
                if(isUndefinedOrNull(arguments[0]) && isUndefinedOrNull(arguments[1])) {
                    _this.succeed();
                } else if (isUndefinedOrNull(arguments[0])) {
                    _this.succeed(arguments[1]);
                } else if (isUndefinedOrNull(arguments[1])) {
                    _this.fail(arguments[0]);
                } else {
                    //support backwards compatibility by logging message here
                    console.log("Error Message: " + arguments[1]);
                    _this.fail(arguments[0]);
                }
            }
```

前のバージョンからあった`context.done`ですが実装が内部で`succeed`と`fail`を呼び出す形に変更されています。
そのため、既存の実装でも同期呼び出しは扱えそうですね。

```js
context.done(); //-> succeed
context.done(null, 'success'); //-> succeed
context.done(undefined, 'success'); //-> succeed
context.done('fail'); //-> fail
context.done('fail', null); //-> fail
context.done('fail', undefined); //-> fail
context.done(true, 'fail'); //-> fail
```

だいたい`done`の呼び出しはこんな感じですね。
まぁ、実装的にはこれまで通り、第一引数にエラーの有無を渡して、第二引数にエラーメッセージ（or オブジェクト）を渡すので良さそうです。

## result log

```
2015-04-12T04:11:49.798Z    09ecd985-e0ca-11e4-84c0-8b4f359a82ec    result: null
```

ついでにログの種類も増えていて、レスポンスとして返される値もログに出力されるようになったようです。
正直、エンジニアが見てはいけない値（個人情報系とか）をレスポンスで返す可能性を考えると、これはちょっとオフにしたい・・・。

## error log

```
START RequestId: 1ccb67ee-e0cc-11e4-bc7a-139cb7123bb7
Failure while running task: ReferenceError: finish_event_invoke is not defined
    at exports.handler (/var/task/index.js:2:17)
Process exited before completing request
ReferenceError: finish_event_invoke is not defined
END RequestId: 1ccb67ee-e0cc-11e4-bc7a-139cb7123bb7
REPORT RequestId: 1ccb67ee-e0cc-11e4-bc7a-139cb7123bb7	Duration: 221.85 ms	Billed Duration: 300 ms 	Memory Size: 128 MB	Max Memory Used: 9 MB	
```

ちなみに構文エラーや、エラーをキャッチしなかった場合は上記のようなログになります。

## まとめ

調査も完了したので、これから自作の[lambda-handler](https://github.com/k-kinzal/lambda-handler)というモジュールのアップデート作業に入ります。正直、面倒臭いです。
Lambda functionの第一引数に渡すjsonを指定したり、requireしているモジュールをモック化してLambda functionを呼び出す便利なモジュールが欲しい・・・。

## 追記

```
/var/runtime/node_modules/awslambda/bin/awslambda
/var/runtime/node_modules/awslambda/lib/awslambda.js
```

に実行系とコードがあるの見つけました。
AWS Lambdaで実行前と実行後にどうしているの気にしている人はここをcatしたりすると幸せになるかもしらんです。