AWS

【ハンズオン】Lambda プロキシ統合を使用した Hello World REST API の構築

「Hello, World!」 Lambda 関数を作成

  1. Lambda コンソール (https://console.aws.amazon.com/lambda/) にサインインします。
  2. AWS ナビゲーションバーで、[region (リージョン)] を選択します (アジアパシフィック(東京) ap-northeast-1など)。

注意

Lambda 関数を作成したリージョンを書き留めます。これは、API を作成するときにLambda 関数とリージョンを合わせるためです。

3. ナビゲーションペインで、[関数] を選択します。

4. [関数の作成] を選択します。

5. 一から作成を選択します。

6. [基本的な情報] で、以下の作業を行います。
 a. [関数名] に GetStartedLambdaProxyIntegration と入力します。
 b. [デフォルトの実行ロールの変更]で[AWS ポリシーテンプレートから新しいロールを作成]を選択します。
 c. [ロール名] に GetStartedLambdaBasicExecutionRole と入力します。
 d. [ポリシーテンプレート - オプション] フィールドは空白のままにします。

 下のようが画面になるはずです(※AWSのコンソールはレイアウト、表示文字など頻繁に更新(改善)されますので、読者様が見ている画面と異なる場合があります。)

 e. [関数の作成] を選択します。

このような画面に遷移します。

7. インラインコードエディタの [Function code (関数コード)] に、以下のコードをコピーして貼り付けます。

'use strict';
console.log('Loading hello world function');
 
export const handler = async (event) => {
    let name = "you";
    let city = 'World';
    let time = 'day';
    let day = '';
    let responseCode = 200;
    console.log("request: " + JSON.stringify(event));
    
    if (event.queryStringParameters && event.queryStringParameters.name) {
        console.log("Received name: " + event.queryStringParameters.name);
        name = event.queryStringParameters.name;
    }
    
    if (event.queryStringParameters && event.queryStringParameters.city) {
        console.log("Received city: " + event.queryStringParameters.city);
        city = event.queryStringParameters.city;
    }
    
    if (event.headers && event.headers['day']) {
        console.log("Received day: " + event.headers.day);
        day = event.headers.day;
    }
    
    if (event.body) {
        let body = JSON.parse(event.body)
        if (body.time) 
            time = body.time;
    }
 
    let greeting = `Good ${time}, ${name} of ${city}.`;
    if (day) greeting += ` Happy ${day}!`;

    let responseBody = {
        message: greeting,
        input: event
    };
    
    // The output from a Lambda proxy integration must be 
    // in the following JSON object. The 'headers' property 
    // is for custom response headers in addition to standard 
    // ones. The 'body' property  must be a JSON string. For 
    // base64-encoded payload, you must also set the 'isBase64Encoded'
    // property to 'true'.
    let response = {
        statusCode: responseCode,
        headers: {
            "x-custom-header" : "my custom header value"
        },
        body: JSON.stringify(responseBody)
    };
    console.log("response: " + JSON.stringify(response))
    return response;
};

8. [Deploy] を選択します。

「Hello, World!」 APIを作成

  1. https://console.aws.amazon.com/apigateway で API Gateway コンソールにサインインします。
  2. API Gateway を初めて使用する場合は、サービスの特徴を紹介するページが表示されます。[REST API] で、[構築] を選択します。ポップアップ表示されたら、[OK] を選択します。API Gateway を使用するのが初めてではない場合、[API] を選択します。[REST API] で、[構築] を選択します。

3. 以下の手順で空の API を作成します。
 a. [Create new API (新しい API の作成)] で、[New API (新しい API)] を選択します。
 b. [Settings (設定)] で以下のように設定します。
  ・[API 名] に「LambdaSimpleProxy」と入力します。
  ・必要に応じて、[Description (説明)] フィールドに説明を入力します。それ以外の場合は空のままにします。
  ・[Endpoint Type (エンドポイントタイプ)] を [Regional (リージョン別)] に設定したままにします。

このような画面になっているはずです。

 c. [Create API] を選択します。

4. helloworld リソースを以下のように作成します。
 a. [Resources (リソース)] ツリーのルートリソース (/) を選択します。
 b. [Actions (アクション)] ドロップダウンメニューから [Create Resource (リソースの作成)] を選択します。

 c. [Configure as proxy resource (プロキシリソースとして設定)] チェックボックスはオフのままにします。
 d. [Resource Name (リソース名)] に helloworld と入力します。
 e. [Resource Path (リソースパス)] を [/helloworld] に設定したままにします。
 f. [Enable API Gateway CORS (API Gateway CORS を有効にする)] チェックボックスはオフのままにします。

このような画面になっているはずです。

 g. [リソースの作成] を選択します。

5. プロキシ統合では、任意の HTTP メソッドを表すすべての ANY メソッドをキャッチオールがらリクエスト全体がそのままバックエンド Lambda 関数に送信されます。実際の HTTP メソッドは、実行時にクライアントによって指定されます。ANY メソッドでは、単一の API メソッドのセットアップを DELETEGETHEADOPTIONSPATCHPOST および PUT のサポートされるすべての HTTP メソッドに使用できます。ANY メソッドを設定するには、次の操作を行います。

 a. [Resources (リソース)] リストで、[/helloworld] を選択します。
 b. [アクション] メニューから、[メソッドの作成] を選択します。

 c. ドロップダウンメニューから [ANY] を選択し、チェックマークアイコンを選択します。

 d. [Integration type (統合タイプ)] を [Lambda Function (Lambda 関数)] に設定したままにします。
 e. [Use Lambda Proxy integration (Lambda プロキシ統合の使用)] を選択します。
 f. [Lambda Region (Lambda リージョン)] ドロップダウンメニューから、GetStartedLambdaProxyIntegration Lambda 関数を作成したリージョンを選択します。
 g. [Lambda Function (Lambda 関数)] フィールドに任意の文字を入力し、ドロップダウンメニューから [GetStartedLambdaProxyIntegration] を選択します。
 h. [デフォルトタイムアウトの使用] チェックボックスをオンのままにしておきます。

このような画面になっているはずです。

 i. [保存] を選択します。
 j. [Add Permission to Lambda Function (Lambda 関数に権限を追加する)] のプロンプトが表示されたら、[OK] を選択します。

このような画面になっているはずです。

API をデプロイしてテストする

API Gateway コンソールでの API のデプロイ

  1. [アクション] ドロップダウンメニューから [API のデプロイ] を選択します。

2. [デプロイされるステージ] で、[新しいステージ] を選択します。

3. [Stage name (ステージ名)] に test と入力します。

4. 必要に応じて、[Stage description (ステージの説明)] に入力してください。

5. 必要に応じて、[Deployment description (デプロイの説明)] に入力してください。

このような画面になっているはずです。

6. [デプロイ] を選択します。

7. API の [Invoke URL (URL の呼び出し)] をメモしておきます。

ブラウザと cURL を使用して Lambda プロキシ統合で API をテストする

クエリ文字列パラメータのみを使用して GET リクエストをテストするには、API の helloworld リソースの URL をブラウザのアドレスバーに入力します。下記は例です。

https://171rkafwuj.execute-api.ap-northeast-1.amazonaws.com/test/helloworld?name=yasushi&city=osushi

それ以外のメソッドの場合は、POSTMAN や cURL などの高度な REST API テストユーティリティを使用する必要があります。AWSのチュートリアルでは cURL を使用していましたので、私はPOSTMANを使用してみます。POSTMANにサインインされていることを前提としています(Googleアカウントでサインアップするとお手軽です)。

POSTMAN を使用してデプロイされた API をテストするには

  1. Workspacesタブから[My Workspace]を選択します。

2. [Import]を選択します。

3. 次の cURL コマンドをコピーしてターミナルウィンドウに貼り付け、171rkafwujを API の API ID に、ap-northeast-1 を API がデプロイされているリージョンに置き換えます。

curl -v -X POST \
  'https://171rkafwuj.execute-api.ap-northeast-1.amazonaws.com/test/helloworld?name=yasushi&city=osushi' \
  -H 'content-type: application/json' \
  -H 'day: Thursday' \
  -d '{ "time": "evening" }'

4. [Send]を選択します。

以下のようなペイロードで正常なレスポンスを受け取ります。

{
    "message": "Good evening, yasushi of osushi. Happy Thursday!",
    "input": {
        "resource": "/helloworld",
        "path": "/helloworld",
        "httpMethod": "POST",
        "headers": {
            "Accept": "*/*",
            "Accept-Encoding": "gzip, deflate, br",
            "Cache-Control": "no-cache",
            "Content-Type": "application/json",
            "day": "Thursday",
            "Host": "171rkafwuj.execute-api.ap-northeast-1.amazonaws.com",
            "Postman-Token": "12345678-1234-1234-1234-1234567890",
            "User-Agent": "PostmanRuntime/7.32.3",
            "X-Amzn-Trace-Id": "Root=1-23456789-12345678901234567890123456789",
            "X-Forwarded-For": "54.86.50.139",
            "X-Forwarded-Port": "443",
            "X-Forwarded-Proto": "https"
        },
        "multiValueHeaders": {
            "Accept": [
                "*/*"
            ],
            "Accept-Encoding": [
                "gzip, deflate, br"
            ],
            "Cache-Control": [
                "no-cache"
            ],
            "Content-Type": [
                "application/json"
            ],
            "day": [
                "Thursday"
            ],
            "Host": [
                "171rkafwuj.execute-api.ap-northeast-1.amazonaws.com"
            ],
            "Postman-Token": [
                "12345678-1234-1234-1234-1234567890"
            ],
            "User-Agent": [
                "PostmanRuntime/7.32.3"
            ],
            "X-Amzn-Trace-Id": [
                "Root=1-23456789-12345678901234567890123456789"
            ],
            "X-Forwarded-For": [
                "54.86.50.139"
            ],
            "X-Forwarded-Port": [
                "443"
            ],
            "X-Forwarded-Proto": [
                "https"
            ]
        },
        "queryStringParameters": {
            "city": "osushi",
            "name": "yasushi"
        },
        "multiValueQueryStringParameters": {
            "city": [
                "osushi"
            ],
            "name": [
                "yasushi"
            ]
        },
        "pathParameters": null,
        "stageVariables": null,
        "requestContext": {
            "resourceId": "aaaaaa",
            "resourcePath": "/helloworld",
            "httpMethod": "POST",
            "extendedRequestId": "aaaaaaaaaaaaaaa=",
            "requestTime": "16/Aug/2023:01:47:40 +0000",
            "path": "/test/helloworld",
            "accountId": "123456789012",
            "protocol": "HTTP/1.1",
            "stage": "test",
            "domainPrefix": "171rkafwuj",
            "requestTimeEpoch": 1234567890123,
            "requestId": "test-invoke-request",
            "identity": {
                "cognitoIdentityPoolId": null,
                "accountId": null,
                "cognitoIdentityId": null,
                "caller": null,
                "sourceIp": "54.86.50.139",
                "principalOrgId": null,
                "accessKey": null,
                "cognitoAuthenticationType": null,
                "cognitoAuthenticationProvider": null,
                "userArn": null,
                "userAgent": "PostmanRuntime/7.32.3",
                "user": null
            },
            "domainName": "171rkafwuj.execute-api.ap-northeast-1.amazonaws.com",
            "apiId": "171rkafwuj"
        },
        "body": "{ \"time\": \"evening\" }",
        "isBase64Encoded": false
    }
}

前述のメソッドリクエストで POST から PUT に変更した場合も同じレスポンスを受け取ります。

さらに自己学習

ここまではほとんどAWSのデベロッパーガイド通りにハンズオンを実施してきました。ここからは、API Gateway、Lambda関数、POSTMANをいじってみます。サーバレスの動くベースは出来上がっているので、それらをいじってみて動きがどう変わるかを見ることによって理解を深めます。

API Gateway をさわる

API > LambdaSimpleProxy > リソース >ANY > メソッドテスト でテストをしてみます。これまでは、API > LambdaSimpleProxy > ステージ >test のURLの呼び出しを元にテストをしていましたが、今回は直接メソッドを呼び出してみます。FQDN(Fully Qualified Domain Name、ホスト名+ドメイン名のこと)で検索するかグローバルIP検索するかの違いと同じようなイメージを持っています。

メソッドから[POST]を選択し、[テスト]ボタンを押下します。

ほぼ黒塗りにしましたが、とりあえずレスポンスが返ってきていることはわかります。レスポンス本文に "message":"Good day, you of World." と表示されており、ペイロード '{ "time": "evening" }' なしの状態で返ってくる値が入っている(デフォルト値)。

リクエスト本文で { "time": "evening" } を入力して[テスト]ボタンを押下すると、レスポンス本文に "message":"Good evening, you of World." と表示されました。

ハンズオンの cURL コマンド1行目では、下記のように name=yasushi&city=osushi と記載していました。この部分もリクエスト本文に入れてテストしてみたいと思います。

https://171rkafwuj.execute-api.ap-northeast-1.amazonaws.com/test/helloworld?name=yasushi&city=osushi

リクエスト本文

{ 
  "time": "evening",
  "name": "yasushi",
  "city": "osushi"
}

レスポンス本文抜粋

"message":"Good evening, you of World."

あれ、Good day の day は evening に置き換わっているのに、you と World が yasushi と osushi に置き換わってない(想定してた動きと違う)。リクエスト本文を { "time": "evening" } に戻し、クエリ文字列に name=yasushi&city=osushi を入力してテストしてみます。

今度はリクエストの表示が cURL のようになり、想定通りの動きになっていました。

想定通りの動きになったとはいえ、クエリ文字列とリクエスト本文について理解していないので勉強しないといけませんね。

クエリ文字列(Query String):URLの末尾に「?」を付け、それに続くキーと値のペアのこと。クライアントは、API Gatewayに対して特定の情報をリクエストするために、このクエリ文字列を使用します。

例えば、今回の例でいうと下の部分がクエリ文字列です。helloworld ページの name が yasushi で、city が osushi のページを取ってきて!というクエリ(命令)です。

name=yasushi&city=osushi

リクエスト本文(Request Body):引数みたいなもの。JSONやXMLなどの形式で表現されることが一般的です。API Gatewayを介してバックエンドサービス(今回でいうとLambda関数)に送信されるデータを指します。

例えば、今回の例でいうと { "time": "evening" } がリクエスト本文で、time を evening に置き換えてレスポンスして!ということだったんでしょうね。

今回のハンズオンでいうと、クライアントからクエリ文字列でリクエストを受け取るかリクエスト本文で受け取るかは、Lambda関数を見てみないとわからないはず。なので、Lambda関数を見てみます。

Lambda関数 をさわる

Node.js は触ったことがないのですが、Python は良く触ります。なので、コードはなんとなく読めました。

Node.js で let 変数 は変数宣言ということも勉強になりました。Pythonでいうvarやね、使わへんけど。

呼び出し時にカスタマイズできる引数(下記)がわかりましたね。

  • queryStringParameters は name と city で指定されていること。
  • headers で day が指定されていること
  • body で time が指定されていること

これらの引数を増やしたり、greeting 変数宣言で表示形式を変えたりして遊べそうですね。

POSTMAN をさわる

POSTMAN を使用するのは初でしたが、ハンズオンとでここまでくるとPOSTMANの良さがわかってきます。

API Gateway や cURL コマンド指定の時はクエリ文字列は ?Key=Value 、ヘッダーは Key:Value 、リクエスト本文は JSON と何かと書き方を意識してリクエストを送信しないといけませんでした。しかし、POSTMANではGUIでそれらをほぼ意識せず指定できるのでとても楽です。

POSTMANでのクエリ文字列の指定
POSTMANでのヘッダーの指定
POSTMANでのリクエスト本文の指定

リクエスト送信履歴も簡単に見られるし、もちろん再実行もできます。

他にも response はファイルでダウンロードできたり、リクエスト送信とその結果は Collections というフォルダのようなところで管理もできる。Collection に権限をつけて誰が見られる等の設定もできそうなので、「API Gateway のテストしといたんで結果を見といてください。Collection に入れています。」的なやり取りを上司やお客様とできそうですね。

参考

チュートリアル: Lambda プロキシ統合を使用した Hello World REST API の構築 - Amazon API Gateway

-AWS
-, , , ,