Qtの応用 - AWS Lambda

提供:MochiuWiki - SUSE, Electronic Circuit, PCB
2025年11月21日 (金) 16:16時点におけるWiki (トーク | 投稿記録)による版 (→‎Lambda関数の呼び出し)
ナビゲーションに移動 検索に移動

概要



AWS SDK for C++のインストール

Qtの応用_-_AWS#AWS_SDK_for_C++のインストールのページを参照すること。


Lambdaクライアントの初期化

指定されたリージョンでLambdaクライアントを初期化する。

 #include <aws/lambda/LambdaClient.h>
 
 QString region = "<リージョン名>"
 
 // Aws::Client::ClientConfiguration
 // AWS SDKのクライアント設定を保持するオブジェクト
 // リージョン、エンドポイント、タイムアウト、リトライ設定などを指定できる
 Aws::Client::ClientConfiguration clientConfig;
 
 // clientConfig.region
 // Lambda関数が配置されているAWSリージョンを指定する
 // 例 : "us-east-1", "ap-northeast-1", "eu-west-1"
 clientConfig.region = region.toStdString();
 
 // Aws::Lambda::LambdaClient
 // AWS Lambda APIとの通信を行うクライアントクラス
 // 全てのLambda操作 (Invoke, ListFunctions等) はこのクライアントを通じて実行される
 auto lambdaClient = std::make_unique<Aws::Lambda::LambdaClient>(clientConfig);



Lambda関数の呼び出し

Lambda関数を呼び出し、レスポンスを取得する。

以下の例では、同期呼び出し、非同期呼び出し、ドライランに対応している。

 #include <aws/lambda/LambdaClient.h>
 #include <aws/lambda/model/InvokeRequest.h>
 
 // Aws::Lambda::Model::InvokeRequest
 // Lambda関数呼び出しリクエストを表すクラス
 // 関数名、ペイロード、呼び出しタイプ、ログタイプ等を設定する
 Aws::Lambda::Model::InvokeRequest request;
 
 // SetFunctionName()
 // 呼び出すLambda関数の名前またはARN(Amazon Resource Name)を指定する
 // 例 : "my-function" または "arn:aws:lambda:us-east-1:123456789012:function:my-function"
 request.SetFunctionName(functionName.toStdString());
 
 // InvocationType (呼び出しタイプ) の設定
 // Lambda関数の呼び出し方法を指定する列挙型
 if (invocationType == "RequestResponse") {
    // RequestResponse - 同期呼び出し
    // 関数の実行完了を待ち、結果(レスポンスペイロード)を返す
    // リアルタイムで結果が必要な場合に使用
    request.SetInvocationType(Aws::Lambda::Model::InvocationType::RequestResponse);
 }
 else if (invocationType == "Event") {
    // Event - 非同期呼び出し
    // 関数をイベントキューに追加し、すぐに制御を返す(結果は返らない)
    // 処理の完了を待つ必要がない場合に使用
    request.SetInvocationType(Aws::Lambda::Model::InvocationType::Event);
 }
 else if (invocationType == "DryRun") {
    // DryRun - テスト呼び出し
    // 実際には関数を実行せず、アクセス許可のみを検証する
    // 本番実行前の権限チェックに使用
    request.SetInvocationType(Aws::Lambda::Model::InvocationType::DryRun);
 }
 
 // LogType(ログタイプ)の設定
 // 実行ログの取得方法を指定する列挙型
 if (logType == "Tail") {
    // Tail - 最後の4KBの実行ログをBase64エンコードして返す
    // デバッグ時に関数の実行状況を確認する場合に使用
    request.SetLogType(Aws::Lambda::Model::LogType::Tail);
 }
 else {
    // None - ログを返さない
    // ログが不要な場合に使用 (デフォルト)
    request.SetLogType(Aws::Lambda::Model::LogType::None);
 }
 
 // SetBody() - ペイロード (入力データ) の設定
 // Lambda関数に渡すデータを設定する (JSON形式が一般的)
 
 // Aws::MakeShared
 // AWS SDKのメモリアロケータを使用してshared_ptrを作成する関数
 // 第1引数は識別用のタグ (メモリトラッキング用)
 std::shared_ptr<Aws::IOStream> payload_stream = Aws::MakeShared<Aws::StringStream>("PayloadStream");
 *payload_stream << payload.toStdString();
 request.SetBody(payload_stream);
 
 // Invoke() - Lambda関数を実際に呼び出すメソッド
 // 戻り値はOutcomeオブジェクト (成功 / 失敗の結果を保持)
 //
 // Outcome
 // AWS SDK for C++の結果を表すテンプレートクラス
 // 成功時はGetResult(), 失敗時はGetError()でアクセスする
 auto outcome = m_lambdaClient->Invoke(request);
 
 // IsSuccess() - 呼び出しが成功したかをチェック
 if (outcome.IsSuccess()) {
    // GetResult() - 成功時の結果オブジェクトを取得
    const auto& result = outcome.GetResult();
 
    // GetStatusCode()
    // HTTPステータスコードを取得
    // 通常、成功時は200が返る
    int statusCode = result.GetStatusCode();
 
    // GetFunctionError()
    // Lambda関数内でエラーが発生した場合、エラータイプを返す
    // "Handled" - 関数コード内でキャッチされたエラー
    // "Unhandled" - 関数コードでキャッチされなかったエラー
    // 空文字列 - エラーなし
    std::string functionError = result.GetFunctionError();
 
    // GetPayload()
    // Lambda関数から返されたレスポンスデータのストリームを取得
    // 通常、JSON形式のデータが返される
    auto& payload_stream = result.GetPayload();
    std::stringstream ss;
    ss << payload_stream.rdbuf();
    std::string response_payload = ss.str();
 
    // GetLogResult()
    // 実行ログ (Base64エンコード済み) を取得
    // LogType::Tailを指定した場合のみ利用可能
    // デコードすることで関数の実行ログ (print文やエラーメッセージ等) を確認できる
    std::string logResult = result.GetLogResult();
 
    return true;
 }
 else {
    // GetError() - 失敗時のエラー情報を取得
    // GetMessage() - エラーメッセージを取得
    std::cerr << "✗ 呼び出し失敗: " << outcome.GetError().GetMessage() << std::endl;
    return false;
 }



Lambda関数の一覧の取得

アカウント内に存在するLambda関数の一覧を取得する。
関数の基本情報 (ランタイム、メモリ、タイムアウト等) も同時に取得することができる。

 #include <aws/lambda/LambdaClient.h>
 #include <aws/lambda/model/ListFunctionsRequest.h>
 
 // Aws::Lambda::Model::ListFunctionsRequest
 // Lambda関数一覧取得リクエストを表すクラス
 // オプションで最大取得数 (MaxItems) や ページネーショントークン (Marker) を設定できる
 Aws::Lambda::Model::ListFunctionsRequest request;
 
 // ListFunctions()
 // アカウント内のLambda関数一覧を取得するメソッド
 // 指定したリージョン内の全ての関数情報を返す
 auto outcome = m_lambdaClient->ListFunctions(request);
 
 // IsSuccess() : 呼び出しが成功したかをチェック
 if (outcome.IsSuccess()) {
    // GetResult() - 成功時の結果オブジェクトを取得
    const auto& result = outcome.GetResult();
 
    // GetFunctions()
    // Lambda関数設定のベクトル (std::vector) を返す
    // 各要素はFunctionConfigurationオブジェクト (個々のLambda関数の設定情報)
    const auto& functions = result.GetFunctions();
 
    // 各関数の情報を取得
    for (const auto& func : functions) {
       // GetFunctionName()
       // Lambda関数の名前を取得
       // 例 : "my-function", "data-processor", "api-handler"
       std::string functionName = func.GetFunctionName();
 
       // GetHandler()
       // Lambda関数のハンドラを取得
       // ハンドラは関数のエントリーポイントを指定する
       // 例 : "index.handler" (Node.js), "lambda_function.lambda_handler" (Python)
       std::string handler = func.GetHandler();
 
       // GetRuntime()
       // Lambda関数のランタイム環境を列挙型で取得
       // 例 : Python3_9, nodejs18_x, java17, dotnet6, go1_x, ruby3_2
       Aws::Lambda::Model::Runtime runtime = func.GetRuntime();
 
       // RuntimeMapper::GetNameForRuntime()
       // Runtime列挙型を文字列表現に変換するユーティリティメソッド
       // 例 : Runtime::Python3_9 → "python3.9"
       std::string runtimeName = Aws::Lambda::Model::RuntimeMapper::GetNameForRuntime(runtime);
 
       // GetMemorySize()
       // Lambda関数に割り当てられたメモリサイズ (MB単位) を取得
       // 設定可能範囲 : 128[MB]~10,240[MB] (10[GB])
       // メモリサイズに比例してCPUパワーも増加する
       int memorySize = func.GetMemorySize();
 
       // GetTimeout()
       // Lambda関数のタイムアウト時間 (秒単位) を取得
       // 設定可能範囲: 1秒~900秒 (15分)
       // この時間を超えると関数の実行が強制終了される
       int timeout = func.GetTimeout();
 
       // GetLastModified()
       // 関数が最後に更新された日時をISO 8601形式で取得
       // 例 : "2024-01-15T10:30:45.123+0000"
       std::string lastModified = func.GetLastModified();
 
       // GetFunctionArn()
       // Lambda関数のAmazon Resource Name (ARN) を取得
       // ARNは関数を一意に識別するグローバル識別子
       // 例 : "arn:aws:lambda:us-east-1:123456789012:function:my-function"
       std::string functionArn = func.GetFunctionArn();
 
       // GetDescription()
       // Lambda関数の説明文を取得
       // コンソールやCLIで設定した関数の説明を返す
       std::string description = func.GetDescription();
 
       // その他にも取得可能な情報:
       // - GetCodeSize()    : 関数コードのサイズ (バイト単位)
       // - GetRole()        : 関数に関連付けられたIAMロールのARN
       // - GetVersion()     : 関数のバージョン番号
       // - GetLayers()      : 関数にアタッチされたレイヤーの情報
       // - GetEnvironment() : 環境変数の設定
    }
 
    return true;
 }
 else {
    // GetError() - 失敗時のエラー情報を取得
    std::cerr << "✗ 取得失敗: " << outcome.GetError().GetMessage() << std::endl;
    return false;
 }



エラーハンドリング

全ての操作はOutcomeオブジェクトを返すため、必ず IsSuccess メソッドで確認する。

 auto outcome = m_lambdaClient->Invoke(request);
 
 // 必ず成功 / 失敗を確認
 if (!outcome.IsSuccess()) {
    // GetError() : エラー情報オブジェクトを取得
    auto error = outcome.GetError();
 
    // GetErrorType() : エラーの種類を列挙型で取得
    // 例 : INVALID_PARAMETER_VALUE, RESOURCE_NOT_FOUND, ACCESS_DENIED
    std::cerr << "エラーコード: " << error.GetErrorType() << std::endl;
 
    // GetMessage() : エラーの詳細メッセージを取得
    // ユーザーに表示可能な分かりやすいエラー説明
    std::cerr << "メッセージ: " << error.GetMessage() << std::endl;
 
    // GetExceptionName() : AWS APIが返した例外の名前を取得
    // 例 : "ResourceNotFoundException", "InvalidParameterValueException"
    std::cerr << "例外名: " << error.GetExceptionName() << std::endl;
 }



サンプルコード

以下の例では、AWS Lambdaの関数呼び出しとレスポンスの取得を行っている。
全ての設定 (リージョン、関数名、ペイロード) を任意の設定ファイルから読み込んでいる。

  • 呼び出しタイプ
    • RequestResponse
      同期呼び出し
      関数の実行結果を待ってレスポンスを返す。
    • Event
      非同期呼び出し
      関数をキューに入れてすぐに制御を返す。
    • DryRun
      テスト呼び出し
      関数を実行せずにアクセス許可を検証する。

  • ログタイプ
    • Tail
      最後の4KBの実行ログをBase64エンコードして返す。
    • None
      ログを返さない。

  • ペイロード形式
    ペイロードは有効なJSON形式である必要がある。
     #  :
     
     {
       "body": {
         "message": "Hello Lambda!"
       },
       "queryStringParameters": {
         "param1": "value1"
       }
     }
    


 # config.iniファイル

 [AWS]
 # AWSリージョン (必須)
 region=us-east-1

 [Lambda]
 # Lambda関数名 (必須)
 function_name=my-lambda-function

 # 呼び出しタイプ (RequestResponse, Event, DryRun)
 invocation_type=RequestResponse

 # ペイロード (JSON形式)
 payload={"key1": "value1", "key2": "value2"}

 # ログタイプ (None または Tail)
 log_type=Tail


 # CMakeLists.txtファイル

 cmake_minimum_required(VERSION 3.16)
 project(<プロジェクト名> VERSION 1.0 LANGUAGES CXX)

 set(CMAKE_CXX_STANDARD 17)
 set(CMAKE_CXX_STANDARD_REQUIRED ON)

 # Qt設定
 set(CMAKE_AUTOMOC ON)
 set(CMAKE_AUTORCC ON)
 set(CMAKE_AUTOUIC ON)

 # Qtの検索
 find_package(Qt6 COMPONENTS Core QUIET)
 if (NOT Qt6_FOUND)
    find_package(Qt5 5.15 REQUIRED COMPONENTS Core)
 endif()

 # AWS SDK for C++の検索
 find_package(AWSSDK REQUIRED COMPONENTS lambda)

 # 実行ファイル
 add_executable(<プロジェクト名>
    main.cpp
 )

 # Qt6とQt5の両方に対応
 if (Qt6_FOUND)
    target_link_libraries(<プロジェクト名>
       Qt6::Core
       ${AWSSDK_LINK_LIBRARIES}
    )
 else()
    target_link_libraries(<プロジェクト名>
       Qt5::Core
       ${AWSSDK_LINK_LIBRARIES}
    )
 endif()

 # インストール
 install(TARGETS <プロジェクト名>
    RUNTIME DESTINATION bin
 )


 #include <QCoreApplication>
 #include <QCommandLineParser>
 #include <QSettings>
 #include <QFileInfo>
 #include <QDebug>
 #include <QJsonDocument>
 #include <QJsonObject>
 #include <iostream>
 #include <aws/core/Aws.h>
 #include <aws/lambda/LambdaClient.h>
 #include <aws/lambda/model/InvokeRequest.h>
 #include <aws/lambda/model/ListFunctionsRequest.h>

 // 設定情報を保持する構造体
 struct LambdaConfig {
    QString region;
    QString functionName;
    QString invocationType;
    QString payload;
    QString logType;
 };

 // 設定ファイルを読み込む関数
 bool loadConfig(const QString& configPath, LambdaConfig& config)
 {
    QFileInfo fileInfo(configPath);
    if (!fileInfo.exists()) {
       std::cerr << "エラー: 設定ファイルが見つかりません: " << configPath.toStdString() << std::endl;
       return false;
    }

    QSettings settings(configPath, QSettings::IniFormat);
    settings.setIniCodec("UTF-8");

    // AWS設定
    settings.beginGroup("AWS");
    config.region = settings.value("region").toString();
    settings.endGroup();

    // Lambda設定
    settings.beginGroup("Lambda");
    config.functionName = settings.value("function_name").toString();
    config.invocationType = settings.value("invocation_type", "RequestResponse").toString();
    config.payload = settings.value("payload", "{}").toString();
    config.logType = settings.value("log_type", "None").toString();
    settings.endGroup();

    // 必須項目のチェック
    if (config.region.isEmpty()) {
       std::cerr << "エラー: [AWS] region が設定されていません" << std::endl;
       return false;
    }
    if (config.functionName.isEmpty()) {
       std::cerr << "エラー: [Lambda] function_name が設定されていません" << std::endl;
       return false;
    }

    std::cout << "設定ファイル読み込み完了: " << configPath.toStdString() << std::endl;
    std::cout << "  リージョン: " << config.region.toStdString() << std::endl;
    std::cout << "  関数名: " << config.functionName.toStdString() << std::endl;
    std::cout << "  呼び出しタイプ: " << config.invocationType.toStdString() << std::endl;

    return true;
 }

 class LambdaManager {
 private:
    std::unique_ptr<Aws::Lambda::LambdaClient> m_lambdaClient;

 public:
    LambdaManager(const QString& region)
    {
       Aws::Client::ClientConfiguration clientConfig;
       clientConfig.region = region.toStdString();
       m_lambdaClient = std::make_unique<Aws::Lambda::LambdaClient>(clientConfig);

       std::cout << "Lambdaクライアント初期化完了 (リージョン: " << region.toStdString() << ")" << std::endl;
    }

    bool invokeFunction(const QString& functionName, const QString& payload, 
                       const QString& invocationType, const QString& logType)
    {
       std::cout << "\n=== Lambda関数呼び出し開始 ===" << std::endl;
       std::cout << "  関数名: " << functionName.toStdString() << std::endl;
       std::cout << "  ペイロード: " << payload.toStdString() << std::endl;

       // ペイロードのJSON検証
       QJsonParseError parseError;
       QJsonDocument jsonDoc = QJsonDocument::fromJson(payload.toUtf8(), &parseError);
       if (parseError.error != QJsonParseError::NoError) {
          std::cerr << "エラー: ペイロードが有効なJSONではありません: " 
                    << parseError.errorString().toStdString() << std::endl;
          return false;
       }

       Aws::Lambda::Model::InvokeRequest request;
       request.SetFunctionName(functionName.toStdString());

       // 呼び出しタイプの設定
       if (invocationType == "RequestResponse") {
          request.SetInvocationType(Aws::Lambda::Model::InvocationType::RequestResponse);
       }
       else if (invocationType == "Event") {
          request.SetInvocationType(Aws::Lambda::Model::InvocationType::Event);
       }
       else if (invocationType == "DryRun") {
          request.SetInvocationType(Aws::Lambda::Model::InvocationType::DryRun);
       }

       // ログタイプの設定
       if (logType == "Tail") {
          request.SetLogType(Aws::Lambda::Model::LogType::Tail);
       }
       else {
          request.SetLogType(Aws::Lambda::Model::LogType::None);
       }

       // ペイロードの設定
       std::shared_ptr<Aws::IOStream> payload_stream = 
          Aws::MakeShared<Aws::StringStream>("PayloadStream");
       *payload_stream << payload.toStdString();
       request.SetBody(payload_stream);

       auto outcome = m_lambdaClient->Invoke(request);

       if (outcome.IsSuccess()) {
          const auto& result = outcome.GetResult();

          std::cout << "✓ 呼び出し成功!" << std::endl;
          std::cout << "\n--- レスポンス ---" << std::endl;
          std::cout << "  ステータスコード: " << result.GetStatusCode() << std::endl;

          // 関数エラーの確認
          if (!result.GetFunctionError().empty()) {
             std::cout << "  関数エラー: " << result.GetFunctionError() << std::endl;
          }

          // レスポンスペイロードの取得
          auto& payload_stream = result.GetPayload();
          std::stringstream ss;
          ss << payload_stream.rdbuf();
          std::string response_payload = ss.str();

          if (!response_payload.empty()) {
             std::cout << "  ペイロード: " << response_payload << std::endl;

             // JSONの整形表示
             QJsonDocument responseDoc = QJsonDocument::fromJson(QString::fromStdString(response_payload).toUtf8());
             if (!responseDoc.isNull()) {
                std::cout << "  整形済みペイロード:\n" 
                         << responseDoc.toJson(QJsonDocument::Indented).toStdString() << std::endl;
             }
          }

          // ログの表示 (LogType=Tailの場合)
          if (!result.GetLogResult().empty()) {
             std::cout << "\n--- 実行ログ (Base64デコード済み) ---" << std::endl;
             // Base64デコード
             QByteArray logData = QByteArray::fromBase64(QByteArray::fromStdString(result.GetLogResult()));
             std::cout << logData.toStdString() << std::endl;
          }

          return true;
       }
       else {
          std::cerr << "✗ 呼び出し失敗: " << outcome.GetError().GetMessage() << std::endl;
          return false;
       }
    }

    bool listFunctions()
    {
       std::cout << "\n=== Lambda関数一覧取得 ===" << std::endl;

       Aws::Lambda::Model::ListFunctionsRequest request;
       auto outcome = m_lambdaClient->ListFunctions(request);

       if (outcome.IsSuccess()) {
          const auto& result = outcome.GetResult();
          const auto& functions = result.GetFunctions();

          std::cout << "✓ 取得成功! (" << functions.size() << "個の関数)" << std::endl;

          if (functions.empty()) {
             std::cout << "  登録されている関数がありません" << std::endl;
          }
          else {
             std::cout << "\n--- 関数一覧 ---" << std::endl;
             for (const auto& func : functions) {
                std::cout << "  • " << func.GetFunctionName() << std::endl;
                std::cout << "    ランタイム: " << Aws::Lambda::Model::RuntimeMapper::GetNameForRuntime(func.GetRuntime()) << std::endl;
                std::cout << "    ハンドラ: " << func.GetHandler() << std::endl;
                std::cout << "    メモリ: " << func.GetMemorySize() << " MB" << std::endl;
                std::cout << "    タイムアウト: " << func.GetTimeout() << " 秒" << std::endl;
                std::cout << "    最終更新: " << func.GetLastModified() << std::endl;
                std::cout << std::endl;
             }
          }

          return true;
       }
       else {
          std::cerr << "✗ 取得失敗: " << outcome.GetError().GetMessage() << std::endl;
          return false;
       }
    }
 };

 int main(int argc, char *argv[])
 {
    QCoreApplication app(argc, argv);
    QCoreApplication::setApplicationName("AWS Lambda Manager");
    QCoreApplication::setApplicationVersion("1.0");

    // コマンドライン引数のパーサーを設定
    QCommandLineParser parser;
    parser.setApplicationDescription("AWS Lambda関数呼び出しツール (設定ファイルベース)");
    parser.addHelpOption();
    parser.addVersionOption();

    // オプション定義
    QCommandLineOption invokeOption(QStringList() << "i" << "invoke", "Lambda関数を呼び出し");
    parser.addOption(invokeOption);

    QCommandLineOption listOption(QStringList() << "l" << "list", "Lambda関数の一覧を取得");
    parser.addOption(listOption);

    QCommandLineOption configOption(QStringList() << "c" << "config", "設定ファイルのパス (デフォルト: config.ini)", "config", "config.ini");
    parser.addOption(configOption);

    parser.process(app);

    // AWS SDKの初期化
    Aws::SDKOptions options;
    Aws::InitAPI(options);

    int result = 0;

    {
       // 設定ファイルのパスを取得
       QString configPath = parser.value(configOption);

       // 設定を読み込む
       LambdaConfig config;
       if (!loadConfig(configPath, config)) {
          std::cerr << "\n設定ファイルの例:" << std::endl;
          std::cerr << "  [AWS]" << std::endl;
          std::cerr << "  region=ap-northeast-1" << std::endl;
          std::cerr << "  " << std::endl;
          std::cerr << "  [Lambda]" << std::endl;
          std::cerr << "  function_name=my-function" << std::endl;
          std::cerr << "  invocation_type=RequestResponse" << std::endl;
          std::cerr << "  payload={\"key\": \"value\"}" << std::endl;
          std::cerr << "  log_type=Tail" << std::endl;
          Aws::ShutdownAPI(options);
          return 1;
       }

       // Lambdaマネージャーを初期化
       LambdaManager lambdaManager(config.region);

       if (parser.isSet(invokeOption)) {
          // Lambda関数呼び出し
          if (!lambdaManager.invokeFunction(config.functionName, config.payload, 
                                           config.invocationType, config.logType)) {
             result = 1;
          }
       }
       else if (parser.isSet(listOption)) {
          // Lambda関数一覧取得
          if (!lambdaManager.listFunctions()) {
             result = 1;
          }
       }
       else {
          std::cerr << "エラー: --invoke または --list を指定してください" << std::endl;
          parser.showHelp(1);
       }
    }

    // AWS SDKのクリーンアップ
    Aws::ShutdownAPI(options);

    return result;
 }


 # 実行例
 
 # Lambda関数を呼び出し
 ./lambda-manager --invoke --config config.ini

 # Lambda関数の一覧を取得
 ./lambda-manager --list --config config.ini

 # 別の設定ファイルを使用
 ./lambda-manager --invoke --config /path/to/custom-config.ini