Node.js
AWS
lambda

AWS Lambda Proxy Integrationを試してみた

More than 1 year has passed since last update.

追記:2016/11/16

CORSについてを追記しました。

はじめに

2016/09/20にAPI Gatewayの新機能が発表されました。

そのうちの1つはすぐさま記事({proxy+}について)にしていましたが、もう一つを見落としていたのに最近気がつきました。他の様々な機能やサービスを検証する記事を書いているときにやたらと出てきて気になっていたので、今回試してみます。

Lambda Proxy Integration

なにそれ

見落としていた機能はLambda Proxy Integrationです。名前あってるよね?思ってる機能と名前一致してるよね?公式ドキュメントのなかを探してみても見当たらない気がするんですよね〜まあいっか。

今回試してるのは、Lambda Proxy Integrationと言っておきながらAPI Gatewayの新機能なので、API Gatewayのコンソール上のこの部分です。
この統合リクエストの中の
スクリーンショット 2016-11-09 17.35.04.png

Lambdaプロキシ統合の使用の☑の部分です。ここにチェックを入れるとLambda Proxy Integrationの機能が有効になります。

スクリーンショット 2016-11-09 17.35.16.png

この機能の僕なりの解釈としては「このGatewayに対するリクエストの情報(HTTPメソッド、クエリストリング、パス、ソースIPなど)を勝手にまとめてくれて、Lambdaに渡してくれる」機能です。

以前までは、統合リクエストのタイプがLAMBDAでした(現在も使えます)。LAMBDAにしていると、メソッドリクエストや統合リクエストに、リクエスト情報のマッピングを明示的に設定しておく必要があり、その設定の難易度がわりと高かったです。また、統合レスポンスではLambdaから返されたレスポンスボディを正規表現で検証して、HTTPステータスコードを付与し、呼び出し元へ返すようにしなければなりませんでした。

しかし、LAMBDA PROXYでは、それらの面倒な設定が不要になります(多少の制約はつきますが)。実際に試してみましょう。

試してみる

Lambdaの作成

まずはLambdaを作成します。Node.jsでいつも通り作ってください。検証用に、Lambdaのコードは下記のようにします。

handler.js
'use strict';

exports.handler = (event, context, callback) => {
    const response = new Response();
    response.body = JSON.stringify({
          "event": event,
          "context": context
      });
    var method = event.httpMethod;

    if(method == "GET"){
        var status = event.queryStringParameters.status;
        switch(status) {
        case "200":
            response.statusCode = 200;
            break;
        case "400":
            response.statusCode = 400;
            break;
        case "403": 
            response.statusCode = 403;
            break;
        case "500":
            response.statusCode = 500;
            break;
        default:
            break;
        }
    }else if(method == "POST"){
        var status = event.body.status;
        switch(status) {
        case "200":
            response.statusCode = 200;
            break;
        case "400":
            response.statusCode = 400;
            break;
        case "403": 
            response.statusCode = 403;
            break;
        case "500":
            response.statusCode = 500;
            break;
        default:
            break;
        }
    }

    callback(null, response);
};

class Response{
    constructor(){
        this.statusCode = 200;
        this.headers = {};
        this.body = "";
    }
}

Responseというクラスについては後ほど説明しますが、GETかPOSTリクエストの場合のパラメータにstatusというものを与え、その数値に応じてレスポンスのstatusCodeを変更するという単純なプログラムです。その際に、eventやcontextにどのような情報が入っているのかを同時に確認するために、bodyにそれらを入れています。

API Gatewayの作成

次に、Gatewayを作成します。下の画像のような感じになるようにします。この際に、/lambdaではLambdaプロキシ統合の使用のチェックは外し、/lambdaproxyではチェックを入れてください。接続先は先ほど作成したLambda関数にします。
スクリーンショット 2016-11-09 17.55.26.png

画像のような状態のものが作成できたら、デプロイしてください。ステージ名はdevとします。

LAMBDA:GET

デプロイされたURLにブラウザからアクセスしてみましょう。まずは/lambdaにGETアクセスします。クエリはstatus=400としてください。するとレスポンスは下記のようになると思います(固有値っぽい部分はマスクしています)。

{
"statusCode": 200,
"headers": {},
"body": "{\"event\":{},\"context\":{\"callbackWaitsForEmptyEventLoop\":true,\"logGroupName\":\"/aws/lambda/integrationTypeTest\",\"logStreamName\":\"2016/11/09/[$LATEST]xxxxxxxxxxxxxxxxxxx\",\"functionName\":\"integrationTypeTest\",\"memoryLimitInMB\":\"128\",\"functionVersion\":\"$LATEST\",\"invokeid\":\"xxxxxxxxxxxxxxxxxxx\",\"awsRequestId\":\"xxxxxxxxxxxxxxxxxxx\",\"invokedFunctionArn\":\"arn:aws:lambda:ap-northeast-1:xxxxxxxxxxxxxxxxxxx:function:integrationTypeTest\"}}"
}

これを分析すると、下記のような感じです。

  • callback関数の第二引数に渡したオブジェクトがそのまま返ってきている
  • eventには何も渡されていない(Lambdaからは何も取り出せない)
    • マッピングの設定をしていないので、何も渡されない
  • HTTPステータスは200のまま
    • レスポンスボディを正規表現で検証する設定を何もしていないのでデフォルトの200で返されている

これを正しく動作させるためには、少し長くなってしまうので今回は割愛します。

LAMBDA_PROXY:GET

次に、/lambdaproxy?status=400 にアクセスしてみましょう。

{
    "event": {
        "resource": "/lambdaproxy",
        "path": "/lambdaproxy",
        "httpMethod": "GET",
        "headers": {
            "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
            "Accept-Encoding": "gzip, deflate, sdch, br",
            "Accept-Language": "ja,en-US;q=0.8,en;q=0.6",
            "CloudFront-Forwarded-Proto": "https",
            "CloudFront-Is-Desktop-Viewer": "true",
            "CloudFront-Is-Mobile-Viewer": "false",
            "CloudFront-Is-SmartTV-Viewer": "false",
            "CloudFront-Is-Tablet-Viewer": "false",
            "CloudFront-Viewer-Country": "JP",
            "Host": "xxxxxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com",
            "Upgrade-Insecure-Requests": "1",
            "User-Agent": "xxxxxxxxxxxxx",
            "Via": "1.1 xxxxxxxxxxxxx.cloudfront.net (CloudFront)",
            "X-Amz-Cf-Id": "xxxxxxxxxxxxx",
            "X-Forwarded-For": "xxxxxxxxxxxxx",
            "X-Forwarded-Port": "443",
            "X-Forwarded-Proto": "https"
        },
        "queryStringParameters": {
            "status": "400"
        },
        "pathParameters": null,
        "stageVariables": null,
        "requestContext": {
            "accountId": "xxxxxxxxxxxxx",
            "resourceId": "xxxxxxxxxxxxx",
            "stage": "dev",
            "requestId": "xxxxxxxxxxxxx",
            "identity": {
                "cognitoIdentityPoolId": null,
                "accountId": null,
                "cognitoIdentityId": null,
                "caller": null,
                "apiKey": null,
                "sourceIp": "xxxxxxxxxxxxx",
                "accessKey": null,
                "cognitoAuthenticationType": null,
                "cognitoAuthenticationProvider": null,
                "userArn": null,
                "userAgent": "xxxxxxxxxxxxx",
                "user": null
            },
            "resourcePath": "/lambdaproxy",
            "httpMethod": "GET",
            "apiId": "xxxxxxxxxxxxx"
        },
        "body": null
    },
    "context": {
         同じなので省略
    }
}

かなり多くの情報がeventに自動で渡されています。

  • Gatewayに何も設定しなくても、必要そうな情報がeventに入っている
    • 自動なので楽ちん
  • レスポンスは、Lambdaでのresponse.bodyになっている
  • HTTPステータスコードは400になっている

下2つについて説明します。

LAMBDA_PROXYでは、この statusCode, header, bodyの形をcallback関数の第二引数に渡さなければいけません、絶対です。しかも、bodyは必ずStringである必要があります。これが、上で述べた制約です。これを、簡単にするためにclass Responseを作成しています。また、この型で返さなかった場合、HTTPステータスコードが必ず502になってしまいます。

要するに、このClass Responseの型に合わせて返せばよいということです。

この理由は、LAMBDA_PROXYでは、おそらくですが、統合レスポンスで、

  1. statusCode:400のような正規表現で検証してHTTPステータスコードを付与し
  2. headerの内容をレスポンスヘッダーにマッピングし
  3. bodyの内容だけを抽出し、レスポンスボディとして返却する

ように裏側で設定しています。よって、この型で固定されていなければ正しくマッピングできないため、502で返ってくるようです。

型を守りさえすれば、面倒な設定が不要になるので、是非活用したいところです。

LAMBDA:POST

POSTで叩いてみてください。

{
"statusCode": 200,
"headers":{},
"body": "{\"event\":{\"status\":\"200\"},\"context\": 省略
}

POSTの場合、リクエストボディの内容は自動でeventに渡されます。しかし、それ以外の情報は得られません。

LAMBDA_PROXY:POST

{
    "event": {
        "resource": "/lambdaproxy",
        "path": "/lambdaproxy",
        "httpMethod": "POST",
        "headers": {省略
        },
        "queryStringParameters": null,
        "pathParameters": null,
        "stageVariables": null,
        "requestContext": {省略
        },
        "body": "{\n \"status\": \"200\"\n}"
    },
    "context": {省略
    }

LAMBDA_PROXYのAPIをPOSTで叩くと、リクエストボディはevent.bodyに自動で入れられます。その他の情報は、GETのときと同様eventに詰めて渡されます。

ここで注意したいのは、POSTの場合、bodyの中身はオブジェクトではなくStringで渡されるということです。よって、Javascriptで扱う場合は、JSON.parse(event.body)をする必要がありますね。この点に注意すれば、GETの場合と同じように扱うことができます。

便利。

CORSについて(追記:2016/11/16)

今回作成したAPIをAjaxを使って叩く際には、CORSが有効になっていなければ望む形で利用することができません。

LAMBDAの場合だと、API Gatewayのコンソールで、メソッドを押した状態で、左上のアクションからCORSの有効化を押すと有効になりました。

LAMBDA_PROXYの場合では、上記ことをやるだけではCORSは有効になりません(2時間位はまりました)!API Gatewayから統合レスポンスがなくなるので、単純にビジネスロジックのみを書けばいいわけではなくなり、HTTPの処理を含める必要がでてきます。headerの部分に"Access-Control-Allow-Origin" : "*"を加える必要があります。

よって、上記に加えて、Lambdaの処理の方を下記のように書き換える必要があります。

handler.js
class Response{
    constructor(){
        this.statusCode = 200;
        this.headers = {"Access-Control-Allow-Origin" : "*"};
        this.body = "";
    }
}

予めheaderに入れておくことで、CORSが有効になります。

おわりに

新しく出たLambda Proxy Integrationを試してみました。この機能の僕なりの解釈としては「このGatewayに対するリクエストの情報(HTTPメソッド、クエリストリング、パス、ソースIPなど)を勝手にまとめてくれて、Lambdaに渡してくれる」機能です。

レスポンスの型を気をつける必要があるという、多少の制約はあるものの、それを補って余りあるメリット、楽さがあると思います。

ぜひ使ってみてください。

おわり