AWS Lambda API を iOS アプリから呼び出す – AWS Lambda Advent Calendar 2014:5日目

2014.12.05

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

Lambda を MBaaS として使う

先日米国ラスベガスで開催された『re:Invent 2014』にて発表されたAWS Lambdaに関するエントリを12/01から12/25まで毎日1本ずつ書いていくアドベントカレンダー『AWS Lambda Advent Calendar 2014』。このエントリは5日目の内容となります。

先日4日目のエントリはj3tm0t0さんの『AWS Lambda を CoffeeScript で書いてみた』でした。

本日5日目は、Lambda API を iOS アプリから直接呼び出すという内容です。

Lambda API を使うと、Lambda ファンクションをモバイルから直接呼び出すことができたり、Lambda ファンクションの取得や登録などといったことも可能です。Lambda ファンクションを呼び出すというのを始めに想像しますが、結構試されている記事も多いので、今回は Lambda ファンクションの情報を取得し、iOS アプリに表示するという処理を実装してみたいと思います。

AWS Mobile SDK は未対応

Lambda API の呼び出しは AWS Mobile SDK を使えば楽勝!と言いたいところですが、現在(2014/12/05)の時点では Lambda は未対応です。

それでもどうしても iOS から呼んでみたい!ということで、今回は Lambda のクライアントのクラスを自作しました。実装は少し長いので、Lambda のクラス実装部分だけ載せます。

AWSLambdaModel.h

#import "AWSNetworking.h"
#import "AWSModel.h"

@class AWSLambdaGetFunctionRequest;
@class AWSLambdaGetFunctionResponse;
@class AWSLambdaFunctionConfiguration;
@class AWSLambdaFunctionCodeLocation;

@interface AWSLambdaGetFunctionRequest : AWSRequest

@property (nonatomic, strong) NSString *functionName;

@end

@interface AWSLambdaGetFunctionResponse : AWSModel

@property (nonatomic, strong) AWSLambdaFunctionConfiguration *configuration;
@property (nonatomic, strong) AWSLambdaFunctionCodeLocation *code;

@end

@interface AWSLambdaFunctionConfiguration : AWSModel

@property (nonatomic, strong) NSString *functionName;
@property (nonatomic, strong) NSString *functionARN;
@property (nonatomic, strong) NSString *configurationId;
@property (nonatomic, strong) NSString *runtime;
@property (nonatomic, strong) NSString *role;
@property (nonatomic, strong) NSString *handler;
@property (nonatomic, strong) NSString *mode;
@property (nonatomic, strong) NSString *codeSize;
@property (nonatomic, strong) NSString *functionDescription;
@property (nonatomic, strong) NSString *timeout;
@property (nonatomic, strong) NSString *memorySize;
@property (nonatomic, strong) NSString *lastModified;

@end

@interface AWSLambdaFunctionCodeLocation : AWSModel

@property (nonatomic, strong) NSString *location;
@property (nonatomic, strong) NSString *repositoryType;

@end

AWSLambdaModel.m

#import "AWSLambdaModel.h"

@implementation AWSLambdaGetFunctionRequest

+ (NSDictionary *)JSONKeyPathsByPropertyKey {
    return @{@"functionName" : @"FunctionName"};
}

@end

@implementation AWSLambdaGetFunctionResponse : AWSModel

+ (NSDictionary *)JSONKeyPathsByPropertyKey {
    return @{@"configuration" : @"Configuration",
             @"code" : @"Code"};
}

+ (NSValueTransformer *)configurationJSONTransformer {
    return [NSValueTransformer mtl_JSONDictionaryTransformerWithModelClass:[AWSLambdaFunctionConfiguration class]];
}

+ (NSValueTransformer *)codeJSONTransformer {
    return [NSValueTransformer mtl_JSONDictionaryTransformerWithModelClass:[AWSLambdaFunctionCodeLocation class]];
}

@end

@implementation AWSLambdaFunctionConfiguration : AWSModel

+ (NSDictionary *)JSONKeyPathsByPropertyKey {
    return @{@"functionName" : @"FunctionName",
             @"functionARN" : @"FunctionARN",
             @"configurationId" : @"ConfigurationId",
             @"runtime" : @"Runtime",
             @"role" : @"Role",
             @"handler" : @"Handler",
             @"mode" : @"Mode",
             @"codeSize" : @"CodeSize",
             @"functionDescription" : @"Description",
             @"timeout" : @"Timeout",
             @"memorySize" : @"MemorySize",
             @"lastModified" : @"LastModified"};
}

@end

@implementation AWSLambdaFunctionCodeLocation : AWSModel

+ (NSDictionary *)JSONKeyPathsByPropertyKey {
    return @{@"repositoryType" : @"RepositoryType",
             @"location" : @"Location"};
}

@end

AWSLambda.h

#import "AWSService.h"
#import "AWSLambdaModel.h"

@class BFTask;

@interface AWSLambda : AWSService

@property (nonatomic, strong, readonly) AWSServiceConfiguration *configuration;

+ (instancetype)defaultLambda;

- (instancetype)initWithConfiguration:(AWSServiceConfiguration *)configuration;

// Lambdaファンクションの情報の取得
- (BFTask *)getFunction:(AWSLambdaGetFunctionRequest *)request;

@end

AWSLambda.m

@implementation AWSLambda

// ...省略...

#pragma mark - Service method

- (BFTask *)getFunction:(AWSLambdaGetFunctionRequest *)request {
    return [self invokeRequest:request
                    HTTPMethod:AWSHTTPMethodGET
                     URLString:@"/2014-11-13/functions/{FunctionName}/"
                  targetPrefix:@""
                 operationName:@"GetFunction"
                   outputClass:nil];

}

@end

この他に、次のような修正を行います。

  • lambda-2014-11-11.json の追加
  • AWSServiceType に AWSServiceLambda を追加
  • AWSEndpoint#initWithRegion:service:useUnsafeURL: に分岐処理を追加

Lambda ファンクションの実装

今回はとりあえず取得できるか試したかったので、 Lambda ファンクションはかなり簡単なものです。

console.log('Loading event');

exports.handler = function(event, context) {
    context.done(null, 'Hello, iOS Client!');
};

iOS アプリ側の実装

もろもろ用意ができたところで、最後に呼び出すところの実装です。もちろん Cognito で認証します!

ViewController.m

- (void)getFunction {
    // Cognito認証
    AWSCognitoCredentialsProvider *provider
    = [AWSCognitoCredentialsProvider credentialsWithRegionType:AWSRegionUSEast1
                                                     accountId:@"xxxxxxxxxxxx"
                                                identityPoolId:@"us-east-1:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
                                                 unauthRoleArn:@"arn:aws:iam::xxxxxxxxxxxx:role/Cognito_SampleAppUnauth_DefaultRole"
                                                   authRoleArn:@"arn:aws:iam::xxxxxxxxxxxx:role/Cognito_SampleAppAuth_DefaultRole"
                                                        logins:nil];
                                                        
    // Lambdaファンクションを取得する
    AWSLambdaGetFunctionRequest *request = [AWSLambdaGetFunctionRequest new];
    request.functionName = @"helloMobileClient";
    AWSLambda *lambda = [AWSLambda defaultLambda];
    [[lambda getFunction:request] continueWithBlock:^id(BFTask *task) {
        if (task.error) {
            NSLog(@"エラー : %@", task.error);
        } else {
            NSLog(@"成功 : %@", task.result);
            AWSLambdaGetFunctionResponse *response = task.result;
            dispatch_async(dispatch_get_main_queue(), ^{
                AWSLambdaFunctionConfiguration *configuration = response.configuration;
                NSString *result = [NSString stringWithFormat:@"configurationId = %@\nfunctionName = %@\nfunctionDescription = %@\nfunctionARN = %@\nhandler = %@\ncodeSize = %@\nmemorySize = %@\nmode = %@\nlastModified = %@\nrole = %@\nruntime = %@\ntimeout = %@", configuration.configurationId, configuration.functionName, configuration.functionDescription, configuration.functionARN, configuration.handler, configuration.codeSize, configuration.memorySize, configuration.mode, configuration.lastModified, configuration.role, configuration.runtime, configuration.timeout];
                self.label.text = result;
            });
        }
        return nil;
    }];
}

アプリを実行してログを見てみると、取得出来てるはずです!

lambda-function

まとめ

AWS Mobile SDK が対応していないけどどうしても試したい!っていう方はぜひ参考にしていただければと思います。クラスの設計自体は既存のサービスクラスと同じように実装しているので、今後もし AWS Mobile SDK 側がアップデートで Lambda を対応したとしても、利用するクラスの実装コードはきっとそのままで動くはずです!多分w

ちなみに AWS Mobile SDK の実装ソースをちょっと解読できたので、個人的にもタメになりました。

明日6日目はyoshidashingoさんによる「既存のアーキテクチャのどこかを置き換えてみる」です。お楽しみに!