メインコンテンツに移動

builders.flash

AWS コミュニティ通信

AWS Lambda Function URLs のリクエストデータを CDK Watch しながら操作する

2022-06-03 | 吉田 真吾 (AWS Serverless HERO)

AWS Serverless Hero 吉田です。

ひさしぶりに AWS Lambda に大型アップデートがやってきました !
2022 年 4 月 6 日、Lambda 関数を HTTPS で実行できる「AWS Lambda 関数 URL (Function URLs)」機能が追加されました。

HTTPS エンドポイントが追加されることで関数 URL が生成されます。CORS を有効にすることができるため、オリジン配信元から当該関数 URL にイベントデータを POST することもできますし、呼び出し時に認証なしあるいは IAM 認証を設定することもできます。

これにより簡易的な API を Lambda のみで実現できるようになったため、Webhook やアクセスカウンターなどの小さな Web アプリ機能を素早く簡単に実装可能です。

詳細は こちらのブログ を確認してください。


 ツイート » | シェア » | はてブ »

ご注意

本記事で紹介する AWS サービスを起動する際には、料金がかかります。builders.flash メールメンバー特典の、クラウドレシピ向けクレジットコードプレゼントの入手をお勧めします。

*ハンズオン記事およびソースコードにおける免責事項 »

builders.flash メールメンバー登録

builders.flash メールメンバー登録で、毎月の最新アップデート情報とともに、AWS を無料でお試しいただけるクレジットコードを受け取ることができます。

今すぐ登録 »

1. 事前準備

今回は AWS CDK v2 を初めて使う人を想定してステップバイステップで環境を構築します。ローカル環境に以下の準備をしてください。TypeScript で CDK プロジェクトを作り、Postman でリクエストをポストします。すでに環境が整っているものについては読み飛ばして大丈夫です。

2. AWS CDK で Lambda 関数 URL をセットアップする

プロジェクトディレクトリの作成

任意の場所にプロジェクトのディレクトリを作成します。

bash
$ mkdir cdk-lambda-url && cd cdk-lambda-url

cdk init

新しい TypeScript の CDK プロジェクトを作成します。

bash
$ cdk init app --language typescript
:
:
Executing npm install...
✅ All done!

TypeScript コードのコンパイル

1、2とは別の新しいターミナルセッションを起動し、watch モードで TypeScript のコンパイルを開始します。

bash
#新しいターミナルセッション
$ cd cdk-lambda-url
$ npm run watch
:
:
File change detected. Starting incremental compilation...

cdk.json

cdk.json にスタックを構築するリージョン指定のための context と、CDK Watch コマンドで変更監視する Lambda コードのディレクトリを指定します。

json
{
  "app": "npx ts-node --prefer-ts-exts bin/cdk-lambda-url.ts",
  "watch": {
    "include": [
      "**",
    ],
    "exclude": [
      "README.md",
      "cdk*.json",
      "**/*.d.ts",
      "tsconfig.json",
      "package*.json",
      "yarn.lock",
      "node_modules",
      "test"
    ]
  },
  "context": {
    "region": "ap-northeast-1",
    (中略)
  }
}

アプリケーション環境設定をします。

アプリのスタックを読み込んでインスタンス化する bin/cdk-lambda-url.ts において cdk.json の context に指定したリージョンを設定します。
Missing alt text value

リソース定義をします。

メインのスタック lib/cdk-lambda-url-stack.ts でアプリケーションのリソースを定義します。

typescript
import { Stack, StackProps, CfnOutput } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as lambda from 'aws-cdk-lib/aws-lambda';

export class CdkLambdaUrlStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    const hello = new lambda.Function(this, 'HelloHandler', {
      runtime: lambda.Runtime.NODEJS_14_X,
      code: lambda.Code.fromAsset('lambda'),
      handler: 'hello.handler'
    });

    const fnUrl = hello.addFunctionUrl({
      authType: lambda.FunctionUrlAuthType.NONE,
      cors: {
        allowedMethods: [lambda.HttpMethod.ALL],
        allowedOrigins: ["*"],
      },
    });

    new CfnOutput(this, 'FunctionUrl', {
      value: fnUrl.url
    });
  }
}

Lambda コードを作成します。

プロジェクトフォルダ直下に lambda ディレクトリを作成し、Lambda コードを lambda/hello.js に作成します。

bash
$ mkdir lambda && touch lambda/hello.js

lambda/hello.js

lambda/hello.js

typescript
exports.handler = async function(event) {
    console.log("request:", JSON.stringify(event, undefined, 2));
  
    return {
      statusCode: 200,
      headers: { "Content-Type": "text/plain" },
      body: `Hello, Serverless!`
    };
};

アプリを合成 (Synthesize) します。

2 のターミナルに戻り、ここまでの定義を合成 (Synthesize) して CloudFormation テンプレートを生成します。

bash
$ cdk synth

CloudFormation テンプレート

すると以下のように、実際に AWS にデプロイされる、合成された CloudFormation テンプレートが出力されます。

CDK Toolkit をデプロイする。

cdk bootstrap を実行し、デプロイ先の AWS 環境において CloudFormation スタックをオーケストレートするための CDKToolkit スタックをデプロイします。 ※すでにブートストラップ済みの場合は、no change (変更なし) が表示されます。

bash
$ cdk bootstrap
:
:
✅  Environment aws://<AWSアカウント>/ap-northeast-1 bootstrapped (no changes).

アプリケーションをデプロイします。

それでは cdk deploy を実行し、アプリをデプロイしましょう。

bash
$ cdk deploy
:
:
Outputs:
CdkLambdaUrlStack.FunctionUrl = https://3dmzvqsb6fjzmnzdkqjqmetb4a0hreyn.lambda-url.ap-northeast-1.on.aws/
:
:
✨  Total time: 71.23s

関数 URL を確認します。

スタック作成時の Outputs に、作成された関数 URL (CdkLambdaUrlStack.FunctionUrl) が表示されていることを確認します。

ブラウザで関数 URL にアクセスし、Lambda から応答が返ってくることを確認します。

Missing alt text value

3. CDK Watch しながら関数 URL へのリクエストデータを操作する

cdk watch コマンドを実施します。

Lambda を複数回変更してデプロイするにあたり、cdk のサブコマンド cdk watch を実行することで、ローカルの変更を自動ですばやく環境にデプロイすることができます。cdk watch は、ローカルの変更監視をおこないながら、変更を検知したときに cdk diff と cdk deploy を自動で実行します。 加えて、CloudFormation テンプレートには変更なく、Lambda コードのみ変更がある場合は hotswap モードに切り替えてデプロイすることで素早い開発体験を得ることができます。 ※CDK Watchは開発時のみの利用が推奨されています。

bash
$ cdk watch

関数 URL へのリクエストペイロードを出力してみる

Lambda 関数 URL ではペイロードのフォーマットとして「API Gateway ペイロードフォーマット バージョン 2.0」に対応しています。 Lambda で受け取ったリクエストをレスポンスとしてダンプするように、hello.js に以下のようにコードを追加します。

typescript
exports.handler = async function(event) {
    console.log("request:", JSON.stringify(event, undefined, 2));
  
    return {
      statusCode: 200,
      headers: { "Content-Type": "text/plain" },
      body: `Hello, Serverless!
        \nrequest payload:\n${JSON.stringify(event, undefined, 2)}`
    };
};

Lambda コードの自動デプロイ

お気づきでしょうか ? CDK Watch が Lambda コードの変更を検知し、数秒で Lambda コードを自動でデプロイしてくれました。

typescript
#cdk watchしているターミナルセッション
Detected change to 'lambda/hello.js' (type: change). Triggering 'cdk deploy'
:
✨  Total time: 5.72s

リクエストペイロード

再度ブラウザで関数 URL にアクセスすると、リクエストペイロードが出力されることを確認できます。

javascript
Hello, Serverless!
      
request payload:
{
  "version": "2.0",
  "routeKey": "$default",
  "rawPath": "/",
  "rawQueryString": "",
  "headers": {
    "sec-fetch-mode": "navigate",
    "sec-fetch-site": "cross-site",
    "accept-language": "ja,en-US;q=0.9,en;q=0.8,hu;q=0.7",
    "x-forwarded-proto": "https",
    "x-forwarded-port": "443",
    "x-forwarded-for": "xxx.xxx.xxx.xxx",
    "sec-fetch-user": "?1",
    "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
    "sec-ch-ua": "\" Not A;Brand\";v=\"99\", \"Chromium\";v=\"100\", \"Google Chrome\";v=\"100\"",
    "sec-ch-ua-mobile": "?0",
    "x-amzn-trace-id": "Root=1-6273c271-12e1f1fe5aba4371516b744e",
    "sec-ch-ua-platform": "\"macOS\"",
    "host": "3dmzvqsb6fjzmnzdkqjqmetb4a0hreyn.lambda-url.ap-northeast-1.on.aws",
    "upgrade-insecure-requests": "1",
    "accept-encoding": "gzip, deflate, br",
    "sec-fetch-dest": "document",
    "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36"
  },
  "requestContext": {
    "accountId": "anonymous",
    "apiId": "3dmzvqsb6fjzmnzdkqjqmetb4a0hreyn",
    "domainName": "3dmzvqsb6fjzmnzdkqjqmetb4a0hreyn.lambda-url.ap-northeast-1.on.aws",
    "domainPrefix": "3dmzvqsb6fjzmnzdkqjqmetb4a0hreyn",
    "http": {
      "method": "GET",
      "path": "/",
      "protocol": "HTTP/1.1",
      "sourceIp": "39.110.219.221",
      "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36"
    },
    "requestId": "960ac841-7b8b-4feb-b7fc-f8c29e0cdf2d",
    "routeKey": "$default",
    "stage": "$default",
    "time": "05/May/2022:12:26:25 +0000",
    "timeEpoch": 1651753585437
  },
  "isBase64Encoded": false
}

個別のパラメータを取り出してみる

引き続き Lambda コードに以下のように追記して、個別のリクエストパラメータを取り出してみましょう。hello.js を以下のように修正します。 cdk watch でまた自動的にデプロイされましたね。

javascript
exports.handler = async function(event) {
    console.log("request:", JSON.stringify(event, undefined, 2));
  
    return {
      statusCode: 200,
      headers: { "Content-Type": "text/plain" },
      body: `Hello, Serverless!
        \npath: ${event.requestContext.http.path}
        \nfrom: ${event.requestContext.http.sourceIp}
        \ncookies: ${event.cookies}
        \nbody: ${event.isBase64Encoded ? Buffer.from(event.body, 'base64').toString() : event.body}
        \nrequest payload:\n${JSON.stringify(event, undefined, 2)}`
    };
};

Postman でリクエストする。

リクエストペイロードに form データや cookie データを載せるために Postman でリクエストをおこないます。

Postman の Cookies 画面でドメイン「on.aws」を指定して、以下を cookie の先頭に追加して「Save」します。

SESSION=xxxxxxxx; Path=/; Domain=on.aws;
Missing alt text value

入力

Postman の (1) リクエスト URL に関数 URL と (2) 適当なパスを入力し、(3) メソッドに「POST」を指定し、(4) リクエスト Body の form-data に KEY/VALUE を入力します。

リクエストデータを POST

「Send」をクリックして関数 URL にリクエストデータを POST します。

body 部のデータ

リクエストデータから、「リクエストしたパス」「送信元 IP アドレス」「cookie データ」「base64 エンコードされた body 部のデータ (をデコードしたもの)」が取り出せました。

bash
Hello, Serverless!
        
path: /tenant-a/user-a
        
from: xxx.xxx.xxx.xxx
        
cookies: SESSION=xxxxxxxx
        
body: ----------------------------002623005989495685913338
Content-Disposition: form-data; name="whoami"

yoshidashingo
----------------------------002623005989495685913338--

        
request payload:
{
  "version": "2.0",
  "routeKey": "$default",
  "rawPath": "/tenant-a/user-a",
  "rawQueryString": "",
  "cookies": [
    "SESSION=xxxxxxxx"
  ],
  "headers": {
    "x-amzn-trace-id": "Root=1-6273c75d-063bc336309f79ac73770d04",
    "cookie": "SESSION=xxxxxxxx",
    "x-forwarded-proto": "https",
    "postman-token": "b2ee7f00-51f1-4fb5-b6b9-80163ef2ac3a",
    "host": "5gavmkcouhzi3uv7bkqytxv4u40ivtss.lambda-url.ap-northeast-1.on.aws",
    "x-forwarded-port": "443",
    "content-type": "multipart/form-data; boundary=--------------------------002623005989495685913338",
    "x-forwarded-for": "xxx.xxx.xxx.xxx",
    "accept-encoding": "gzip, deflate, br",
    "accept": "*/*",
    "user-agent": "PostmanRuntime/7.29.0"
  },
  "requestContext": {
    "accountId": "anonymous",
    "apiId": "5gavmkcouhzi3uv7bkqytxv4u40ivtss",
    "domainName": "5gavmkcouhzi3uv7bkqytxv4u40ivtss.lambda-url.ap-northeast-1.on.aws",
    "domainPrefix": "5gavmkcouhzi3uv7bkqytxv4u40ivtss",
    "http": {
      "method": "POST",
      "path": "/tenant-a/user-a",
      "protocol": "HTTP/1.1",
      "sourceIp": "xxx.xxx.xxx.xxx",
      "userAgent": "PostmanRuntime/7.29.0"
    },
    "requestId": "59671e53-7ef1-4662-9c13-932ae75699d0",
    "routeKey": "$default",
    "stage": "$default",
    "time": "05/May/2022:12:47:25 +0000",
    "timeEpoch": 1651754845241
  },
  "body": "LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLTAwMjYyMzAwNTk4OTQ5NTY4NTkxMzMzOA0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJ3aG9hbWkiDQoNCnlvc2hpZGFzaGluZ28NCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0wMDI2MjMwMDU5ODk0OTU2ODU5MTMzMzgtLQ0K",
  "isBase64Encoded": true
}

4. まとめ

いかがでしたでしょうか ? この記事では、AWS Lambda 関数 URLs が CDK で簡単に定義して構築できること、CDK Watch で手間なく自動的にスタックのコードがデプロイできること、関数 URLs のリクエストデータが API Gateway ペイロードフォーマット バージョン 2.0 に対応しており、簡易的な Web アプリの構築に便利そうであることの 3 点が確認できたと思います。

実際の Web フロント部分も、上記の CDK スタックに S3 ホスティングや CloudFront を定義することで、手間なく発展させることができます。ぜひ挑戦してみてください。

吉田 真吾

証券システム構築からクラウドネイティブなシステム構築・運用などを経て現職。かたわらで JAWS-UG や Serverless Community(JP) の運営、また各種記事執筆を通じて、日本におけるサーバーレスの普及を促進。
A casual portrait of a man standing with arms crossed, smiling, in front of a textured wall and window blinds.

Did you find what you were looking for today?

Let us know so we can improve the quality of the content on our pages