AWS Lambda (Preview)を試してみてつまずいた所

AmazonがPreview版として公開した新サービスAWS Lambdaを、RubyとJavaScriptからSDKを叩くことにより試してみました。
その過程で色々とハマったので備忘録としてまとめました。ほとんどがLambdaそのものよりSDKに関する内容です。

## (SDK)プログラムから日本語(UTF-8)のパラメータを渡せない(?)

AWSのサービスはSDKを使用することでJavaScript(Node.js)やRubyプログラムから呼び出すことができます。
Lambdaについても同様ですが、下記のようにパラメータに日本語が含まれていると、Lambda関数が呼び出されません。
APIはステータス202を返し、正常にリクエストは受理されます。エラーログなども残りません。
原因はよくわかっておらず、AWS側の問題か、SDKの問題かは不明です。

```JavaScript
var aws = require('aws-sdk');
aws.config.loadFromPath("./credential/aws_config.json");
var f = new aws.Lambda();
f.invokeAsync({FunctionName: "some-function", 
               InvokeArgs: JSON.stringify({key1: "日本語", key2: "buh", key3: "duh"})},
               function(e, r) {});
```

```Ruby
require 'aws-sdk'
# Configuration of REGION, KEY and SECRET goes here ...
f = Aws::Lambda::Client.new region: REGION, access_key_id: KEY, secret_access_key: SECRET
f.invoke_async function_name: "some-function",
               invoke_args: {key1: "日本語", key2: "eww", key3: "ugh"}.to_json
```

### 対策

どうにも不格好ですが、Lambda呼び出し側で`escape`、Lambda関数側で`unescape`するしかないようです。

```JavaScript
// 呼び出し側コード(JavaScript)
f.invokeAsync({FunctionName: "some-function", 
    InvokeArgs: JSON.stringify({key1: escape("日本語"), key2: "buh", key3: "duh"})},
    function(e, r) {});
```

```Ruby
# 呼び出し側コード(Ruby)
def esc(s); s.unpack("U*").map{|u| "%u" + ("%04x" % u)}.join; end
f.invoke_async function_name: "some-function",
    invoke_args: {key1: esc("日本語"), key2: "eww", key3: "ugh"}.to_json
```

```JavaScript
// Lambda関数
exports.handler = function(event, context) {
    console.log('value1 = ' + unescape(event.key1));
    console.log('value2 = ' + unescape(event.key2));
    console.log('value3 = ' + unescape(event.key3));
    context.done(null, 'Hello World');  // SUCCESS with message
};
```

## (SDK)エラー: Signature not yet current

AWS SDKを用いてプログラムからLambda関数を呼び出した際、
"Signature not yet current"エラーが出ることがありましたが、
これはPCの時刻が狂っていたためです。
時刻同期するか`nptdate`(Linux)するか手動修正するかで治りました。

参考までに、Rubyで発生したエラーは次のようなものです:

```
Aws::Lambda::Errors::InvalidSignatureException: Signature not yet current: 20150217T234105Z is still later than 20150217T231241Z (20150217T230741Z + 5 min.)
	from /home/xxx/.rbenv/versions/2.1.5/gemsets/test-gemset/gems/aws-sdk-core-2.0.25/lib/seahorse/client/plugins/raise_response_errors.rb:15:in `call'
…略…
```

## (SDK)JSONやHashをそのまま渡せない

これもSDKを使用する場合によくするミスです。
`InvokeArgs`(JavaScript)/`invoke_args`(Ruby)にJSON/Hashをそのまま渡してしまいますが、
文字列に変換する必要があります。
RubyのHashなら`to_json`メソッドで、JavaScriptオブジェクトなら`JSON.stringify`で文字列に変換してから渡します。

# (Node.js)依存モジュールがある場合はLinuxで作成する

間抜けなことにMac OSで依存モジュールを含むLambda関数をzipにまとめてアップロードしました。
LambdaはLinux環境で実行されるので、バイナリに互換性がなく、依存モジュールがうまく動きません。

# (Node.js)依存モジュールを複数のLambda関数で共有したい(要望)

Lambda関数が、単一のソースファイルだけでなく、複数のソースや依存モジュールから構成される場合、
deployment packageとしてzipファイルにまとめてこれをアップロードします。
同じようなモジュールに依存したLambda関数を複数定義する場合、
同じような構成のdeployment packageをLambda関数ごとにアップロードすることになり、
少し無駄な気がします。
最近筆者が作成した例では、async、aws-sdk、iconv、xml2jsonを使用しており、
これらの合計サイズは約21MBで、zipに圧縮すると約7MBとなりました。決して小さなサイズではありません。
(上記のうちaws-sdkだけはzipに含めなくても使用できます。)
こういう場合、複数のLambda関数でdeployment packageを共有したくなると思うのですが、
そのような機能はありません。
何かいい方法はないものでしょうか？

# 反省

まだLambdaが本来想定しているユースケース、ベストプラクティスなどは完全に理解しておらず、
使いながらどういうサービスなのか、何に使えるのかを模索している段階です。
上でNode.js依存モジュールのLambda関数間共有を要望として挙げたことは、
そもそも筆者の使い方が根本的におかしいことを示唆するものかもしれません。

# 参考リンク

- [AWS Lambda](https://aws.amazon.com/lambda/)
- [AWS Lambda Documentation](http://aws.amazon.com/jp/documentation/lambda/)
- [Best Practices for Working with AWS Lambda Functions - AWS Lambda](http://docs.aws.amazon.com/lambda/latest/dg/best-practices.html)
- [php - AWS SDK Error - Signature not yet current - Stack Overflow](http://stackoverflow.com/questions/18703486/aws-sdk-error-signature-not-yet-current)