AWS LambdaをiOSアプリから直接叩く

# EC2を使わずにちょっとしたAPIを叩きたい
iOS等のモバイルアプリから叩くWebAPIはいつもEC2で実装していましたが、ちょっとしたAPIを作るにもいちいちサーバを立てなければいけないのは大変。と思っていたところLambdaがMobile SDKから叩けるようになっていたようなので、どんなものなのか試してみました。

#全体像
今回登場するAWSサービスは、Amazon Cognito、Lambda、IAMです。CognitoでiOSのMobile-SDKからのアクセス権限と認証の制御を行い、直接Lambdaを叩きます。IAMはCognitoの権限登録を行う時に出てくるので一応書いています。

![Lambda.png](https://qiita-image-store.s3.amazonaws.com/0/61034/fd7cb7d3-bd3a-a6bb-4d83-720df1ba6bd0.png)

#0. 手順
iPhoneアプリからLambdaを叩くまでのセットアップは以下の流れで行います。

1. Amazon CognitoでIdentity Poolを作成する
2. iOSアプリから叩くLambda Functionを作成する
3. Cognitoのアクセス権限にLambda Functionへのアクセス権を追加
4. iOSからLambdaを叩く

# 1. Amazon CognitoでIdentity Poolを作成する
Cogniteは2015/06/21現在「米国東部(バージニア北部)」と「EU(アイルランド)」でのみ利用可能です。今回は「米国東部(バージニア北部)」で作成しました。
Create new identity poolをクリックします。
Identity pool nameを自由に入力し、Unauthenticated identitiesのEnable access to unauthenticated identitiesにチェックを入れます。ここにチェックを入れることによって、iOSからゲストユーザとしてアクセスできるようになります。Cognitoでは他の複数のIDプロバイダとも連携できますが、今回は使用しないのでAuthentication providers のところは何もいじらずに Create poolをクリックします。

![poolid_s.png](https://qiita-image-store.s3.amazonaws.com/0/61034/74586f01-d97a-a97c-0434-8871e4d2e041.png)

次に作成したIdentity Poolに対応するIAM Roleをアサインします。Cognitoは認証済みのユーザと未認証のユーザで別々にアクセス権限を与えられるようになっています。今回はそれぞれCognito_TestAppAuth_RoleとCognito_TestAppUnauth_Roleという名前でロールを新規作成しています。

![poolIam.png](https://qiita-image-store.s3.amazonaws.com/0/61034/0515e9d6-2622-d3f3-3fd1-9ec136101e5d.png)

これで一旦Identity Poolの作成は終わりです。またLambdaファンクションを登録した後に、IAM Roleを追加しに戻ってきます。

# 2. iOSアプリから叩くLambda Functionを作成する
Lambdaは2015/06/21現在「米国東部(バージニア北部)」と「米国西武(オレゴン)」、「EU(アイルランド)」でのみ利用可能です。今回はCognitoと同様の「米国東部(バージニア北部)」で作成しました。
Create a Lambda functionをクリックし、Lambda関数を作成します。

FunctionNameを入力します。今回は"TestFunction"としました。この名前は後でiOSから叩く時に使用するので覚えておいてください。

肝心の関数内の処理ですが、今回はテンプレートを使用しました。引数をAWS上のログに出力し、一部の変数をリターンするだけの処理を行っています。

RoleではこのLambda関数から叩けるサービスの権限設定が出来ます。例えばこのLambda関数からDynamoDBにデータを投入したい場合等は、このIAM RoleにDynamoDBへのアクセス権限を与えれば良いでしょう。今回はとりあえず"Basic execution role"でIAM Roleを新規作成しました。デフォルトでログへのアクセス権限が付与されているようです。ここでRoleを新規作成する時に別タブが開くので、ブラウザのポップアップを許可しないと作成できません。(それに気づかず30分くらいはまった)

![LambdaCreate_s.png](https://qiita-image-store.s3.amazonaws.com/0/61034/ad828b3c-e9e7-4158-fdb7-331e2535a5f0.png)

設定できたらCreate Lambda functionをクリックします。

![LambdaFunction_s.png](https://qiita-image-store.s3.amazonaws.com/0/61034/7b018c5b-89c2-6404-a828-c7228ae25735.png)

作成したLambda関数のFunction ARNをコピーしておきます。この後Cognitoのアクセス権限を追加する時に使用します。

# 3. Cognitoのアクセス権限にLambda Functionへのアクセス権を追加
ここでは1.で作成したCognitoのIdentity poolに2.で作成したLambda関数へのアクセス権限を付与します。IAMを開くと、以下のように1.で作成したRoleが確認できます。

![IAM_list.png](https://qiita-image-store.s3.amazonaws.com/0/61034/ee2164fb-1724-21fb-e063-5d6d02102168.png)

今回はゲストユーザからLambdaを叩くために、"Cognito_TestAppUnauth_Role"にLambda関数へのアクセス権限を追加します。

Roleの詳細を開き、ロールポリシーの作成をクリックします。
![RoleIam.png](https://qiita-image-store.s3.amazonaws.com/0/61034/16ec0c53-06e5-5f2b-5379-a750648a3c6d.png)

Policy GeneratorでLambdaの権限を追加します。アクションには関数を叩くための"InvokeFunction"にチェックし、ARNには2.でコピーしたFunction ARNを入力します。

![role.png](https://qiita-image-store.s3.amazonaws.com/0/61034/ffc2dad0-84c3-f277-e5f6-01df3d1f14e4.png)

これでゲストユーザから2.で作成したLambda関数が叩けるようになりました。

# 4. iOSからLambdaを叩く
最後にiOSからLambdaを叩きます。まずAWSのSDKをiOSに組み込みます。
以下を参考にCocoaPodsでインストールすると楽でしょう。
http://docs.aws.amazon.com/mobile/sdkforios/developerguide/install-ios-sdk.html
Xcodeからプロジェクトを作成し、プロジェクトのルートディレクトリに"Podfile"を作成。

```text:Podfile
source 'https://github.com/CocoaPods/Specs.git'
pod 'AWSCore'
pod 'AWSAutoScaling'
pod 'AWSCloudWatch'
pod 'AWSDynamoDB'
pod 'AWSEC2'
pod 'AWSElasticLoadBalancing'
pod 'AWSKinesis'
pod 'AWSLambda'
pod 'AWSMachineLearning'
pod 'AWSMobileAnalytics'
pod 'AWSS3'
pod 'AWSSES'
pod 'AWSSimpleDB'
pod 'AWSSNS'
pod 'AWSSQS'
pod 'AWSCognito'
```
あとはプロジェクトのルートディレクトリで以下叩けばOKです。

```text
pod install
```
CocoaPodsのインストールは以下等を参考にさせていただきました。
http://qiita.com/satoken0417/items/479bcdf91cff2634ffb1


インストールが終わったら、xcworkspaceを開き、コードを追記していきます。
まずCognitoの初期化を行います。AWS Management Console上から対象のIdentity Poolを選択すればサンプルコードがコピペ出来るようになっています。とりあえずAppDelegateに貼付けて初期化してみます。

```objc
#import "AppDelegate.h"
#import <AWSCore/AWSCore.h>
#import <AWSCognito/AWSCognito.h>

@interface AppDelegate ()

@end

@implementation AppDelegate


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    // Initialize the Amazon Cognito credentials provider
    AWSCognitoCredentialsProvider *credentialsProvider = [[AWSCognitoCredentialsProvider alloc]
                                                          initWithRegionType:AWSRegionUSEast1
                                                          identityPoolId:@"us-east-1:xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"];
    AWSServiceConfiguration *configuration = [[AWSServiceConfiguration alloc] initWithRegion:AWSRegionUSEast1 credentialsProvider:credentialsProvider];
    [AWSServiceManager defaultServiceManager].defaultServiceConfiguration = configuration;
    
    // Override point for customization after application launch.
    return YES;
}
```

次にLambdaを叩きたいクラスに以下をインポートします。

```objc
#import <AWSLambda/AWSLambda.h>
```

AWSLambdaInvokerでLambda関数を叩きます。invokeFunctionの引数に2.で作成したLambda関数のFunction Nameが対応しています。

```objc
AWSLambdaInvoker *lambdaInvoker = [AWSLambdaInvoker defaultLambdaInvoker];
    NSDictionary *parameters = @{@"key1" : @"value1",
                                 @"key2" : @"value2",
                                 @"key3" : @"value3",
                                 @"isError" : @NO};
    
    [[lambdaInvoker invokeFunction:@"TestFunction" JSONObject:parameters] continueWithBlock:^id(BFTask *task) {
        if (task.error) {
            NSLog(@"Error: %@", task.error);
            if ([task.error.domain isEqualToString:AWSLambdaInvokerErrorDomain]
                && task.error.code == AWSLambdaInvokerErrorTypeFunctionError) {
                NSLog(@"Function error: %@", task.error.userInfo[AWSLambdaInvokerFunctionErrorKey]);
            }
        }
        if (task.exception) {
            NSLog(@"Exception: %@", task.exception);
        }
        if (task.result) {
            NSLog(@"Result: %@", task.result);
        }
        return nil;
    }];

```

Xcodeのログに以下が出力されれば成功です。

```text
Result: value1
```

# まとめ
今回はiOSアプリからLambdaを叩くまでをやりました。これだけだとまだ何も出来ていないので、引き続きLambda経由で色々なAWSサービスを叩いてみようと思います。

# 参考
- AWS Black Belt Techシリーズ Amazon Cognito / Amazon Mobile Analytics
http://www.slideshare.net/AmazonWebServicesJapan/aws-black-belt-tech-amazon-cognito-amazon-mobile-analytics

- AWS Mobile SDK for iOS Developer Guide
http://docs.aws.amazon.com/mobile/sdkforios/developerguide/Welcome.html