AWS Lambda Function URLs のリクエストデータを CDK Watch しながら操作する
吉田 真吾 (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 メールメンバー特典の、クラウドレシピ向けクレジットコードプレゼントの入手をお勧めします。

このクラウドレシピ (ハンズオン記事) を無料でお試しいただけます »
毎月提供されるクラウドレシピのアップデート情報とともに、クレジットコードを受け取ることができます。
1. 事前準備
今回は AWS CDK v2 を初めて使う人を想定してステップバイステップで環境を構築します。ローカル環境に以下の準備をしてください。TypeScript で CDK プロジェクトを作り、Postman でリクエストをポストします。すでに環境が整っているものについては読み飛ばして大丈夫です。
- AWS CLI のインストール
- IAM ユーザーのクレデンシャルの設定
- Node.js のインストール
- IDE 環境 : エディタとコマンドラインツールが使えれば大丈夫です。今回はVSCodeを使います。
- AWS CDK v2 のインストール
- Postman のインストール
2. AWS CDK で Lambda 関数 URL をセットアップする
1. プロジェクトディレクトリの作成
任意の場所にプロジェクトのディレクトリを作成します。
$ mkdir cdk-lambda-url && cd cdk-lambda-url
2. cdk init
新しい TypeScript の CDK プロジェクトを作成します。
$ cdk init app --language typescript
:
:
Executing npm install...
✅ All done!
3. TypeScript コードのコンパイル
1、2とは別の新しいターミナルセッションを起動し、watch モードで TypeScript のコンパイルを開始します。
#新しいターミナルセッション
$ cd cdk-lambda-url
$ npm run watch
:
:
File change detected. Starting incremental compilation...
4. cdk.json にスタックを構築するリージョン指定のための context と、CDK Watch コマンドで変更監視する Lambda コードのディレクトリを指定します。
cdk.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",
(中略)
}
}
5. アプリケーション環境設定をします。
アプリのスタックを読み込んでインスタンス化する bin/cdk-lambda-url.ts において cdk.json の context に指定したリージョンを設定します。
bin/cdk-lambda-url.ts
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { CdkLambdaUrlStack } from '../lib/cdk-lambda-url-stack';
const app = new cdk.App();
const stack_region = app.node.tryGetContext("region");
new CdkLambdaUrlStack(app, 'CdkLambdaUrlStack', {
env: {
region: stack_region
}
});
6. リソース定義をします。
メインのスタック lib/cdk-lambda-url-stack.ts でアプリケーションのリソースを定義します。
lib/cdk-lambda-url-stack.ts
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
});
}
}
7. Lambda コードを作成します。
プロジェクトフォルダ直下に lambda ディレクトリを作成し、Lambda コードを lambda/hello.js に作成します。
ターミナルセッション
$ mkdir lambda && touch lambda/hello.js
lambda/hello.js
exports.handler = async function(event) {
console.log("request:", JSON.stringify(event, undefined, 2));
return {
statusCode: 200,
headers: { "Content-Type": "text/plain" },
body: `Hello, Serverless!`
};
};
8. アプリを合成 (Synthesize) します。
2 のターミナルに戻り、ここまでの定義を合成 (Synthesize) して CloudFormation テンプレートを生成します。
$ cdk synth
すると以下のように、実際に AWS にデプロイされる、合成された CloudFormation テンプレートが出力されます。
Resources:
HelloHandlerServiceRole11EF7C63:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Action: sts:AssumeRole
Effect: Allow
Principal:
Service: lambda.amazonaws.com
Version: "2012-10-17"
ManagedPolicyArns:
- Fn::Join:
- ""
- - "arn:"
- Ref: AWS::Partition
- :iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Metadata:
aws:cdk:path: CdkLambdaUrlStack/HelloHandler/ServiceRole/Resource
HelloHandler2E4FBA4D:
Type: AWS::Lambda::Function
Properties:
Code:
S3Bucket:
Fn::Sub: cdk-hnb659fds-assets-${AWS::AccountId}-ap-northeast-1
S3Key: 00252453104da6998ffc2d29815360f4109325daed492886098e64b5013dc21b.zip
Role:
Fn::GetAtt:
- HelloHandlerServiceRole11EF7C63
- Arn
Handler: hello.handler
Runtime: nodejs14.x
DependsOn:
- HelloHandlerServiceRole11EF7C63
Metadata:
aws:cdk:path: CdkLambdaUrlStack/HelloHandler/Resource
aws:asset:path: asset.00252453104da6998ffc2d29815360f4109325daed492886098e64b5013dc21b
aws:asset:is-bundled: false
aws:asset:property: Code
HelloHandlerFunctionUrl0BA407B8:
Type: AWS::Lambda::Url
Properties:
AuthType: NONE
TargetFunctionArn:
Fn::GetAtt:
- HelloHandler2E4FBA4D
- Arn
Cors:
AllowMethods:
- "*"
AllowOrigins:
- "*"
Metadata:
aws:cdk:path: CdkLambdaUrlStack/HelloHandler/FunctionUrl/Resource
HelloHandlerinvokefunctionurl81495083:
Type: AWS::Lambda::Permission
Properties:
Action: lambda:InvokeFunctionUrl
FunctionName:
Fn::GetAtt:
- HelloHandler2E4FBA4D
- Arn
Principal: "*"
FunctionUrlAuthType: NONE
Metadata:
aws:cdk:path: CdkLambdaUrlStack/HelloHandler/invoke-function-url
CDKMetadata:
Type: AWS::CDK::Metadata
Properties:
Analytics: v2:deflate64:H4sIAAAAAAAA/z2OSw+CMBCEf4v3siIk3sXEqwbjmZRSyUIfptvqoeG/2+Lj9M3O7CRTQVVDueEvKsQwFwp7iFfPxcyS1UXFdT9wiKdghEdr2PFu/vonbk5l/4uLdBqJkr8w5Bpia5XMQebCqO44kfQEh4x0QxPELH3DKcWrmQaMaMZcOgf/CH6tS7LBifRj7CBhou1zt4ddmdZPhFi4YDxqCe2Hb6ECIyfZAAAA
Metadata:
aws:cdk:path: CdkLambdaUrlStack/CDKMetadata/Default
Outputs:
FunctionUrl:
Value:
Fn::GetAtt:
- HelloHandlerFunctionUrl0BA407B8
- FunctionUrl
Parameters:
BootstrapVersion:
Type: AWS::SSM::Parameter::Value<String>
Default: /cdk-bootstrap/hnb659fds/version
Description: Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]
Rules:
CheckBootstrapVersion:
Assertions:
- Assert:
Fn::Not:
- Fn::Contains:
- - "1"
- "2"
- "3"
- "4"
- "5"
- Ref: BootstrapVersion
AssertDescription: CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI.
9. CDK Toolkit をデプロイする。
cdk bootstrap を実行し、デプロイ先の AWS 環境において CloudFormation スタックをオーケストレートするための CDKToolkit スタックをデプロイします。
$ cdk bootstrap
:
:
✅ Environment aws://<AWSアカウント>/ap-northeast-1 bootstrapped (no changes).
※すでにブートストラップ済みの場合は、no change (変更なし) が表示されます。
10. アプリケーションをデプロイします。
それでは cdk deploy を実行し、アプリをデプロイしましょう。
$ cdk deploy
:
:
Outputs:
CdkLambdaUrlStack.FunctionUrl = https://3dmzvqsb6fjzmnzdkqjqmetb4a0hreyn.lambda-url.ap-northeast-1.on.aws/
:
:
✨ Total time: 71.23s
11. 関数 URL を確認します。
スタック作成時の Outputs に、作成された関数 URL (CdkLambdaUrlStack.FunctionUrl) が表示されていることを確認します。
ブラウザで関数 URL にアクセスし、Lambda から応答が返ってくることを確認します。

3. CDK Watch しながら関数 URL へのリクエストデータを操作する
12. cdk watch コマンドを実施します。
Lambda を複数回変更してデプロイするにあたり、cdk のサブコマンド cdk watch を実行することで、ローカルの変更を自動ですばやく環境にデプロイすることができます。cdk watch は、ローカルの変更監視をおこないながら、変更を検知したときに cdk diff と cdk deploy を自動で実行します。
加えて、CloudFormation テンプレートには変更なく、Lambda コードのみ変更がある場合は hotswap モードに切り替えてデプロイすることで素早い開発体験を得ることができます。
※CDK Watchは開発時のみの利用が推奨されています。
$ cdk watch
13. 関数 URL へのリクエストペイロードを出力してみる
Lambda 関数 URL ではペイロードのフォーマットとして「API Gateway ペイロードフォーマット バージョン 2.0」に対応しています。
Lambda で受け取ったリクエストをレスポンスとしてダンプするように、hello.js に以下のようにコードを追加します。
lambda/hello.js
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)}`
};
};
お気づきでしょうか ? CDK Watch が Lambda コードの変更を検知し、数秒で Lambda コードを自動でデプロイしてくれました。
lambda/hello.js
#cdk watchしているターミナルセッション
Detected change to 'lambda/hello.js' (type: change). Triggering 'cdk deploy'
:
✨ Total time: 5.72s
再度ブラウザで関数 URL にアクセスすると、リクエストペイロードが出力されることを確認できます。
lambda/hello.js
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
}
14. 個別のパラメータを取り出してみる
引き続き Lambda コードに以下のように追記して、個別のリクエストパラメータを取り出してみましょう。hello.js を以下のように修正します。
lambda/hello.js
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)}`
};
};
cdk watch でまた自動的にデプロイされましたね。
15. Postman でリクエストする。
リクエストペイロードに form データや cookie データを載せるために Postman でリクエストをおこないます。
Postman の Cookies 画面でドメイン「on.aws」を指定して、以下を cookie の先頭に追加して「Save」します。
SESSION=xxxxxxxx; Path=/; Domain=on.aws;
Postman の (1) リクエスト URL に関数 URL と (2) 適当なパスを入力し、(3) メソッドに「POST」を指定し、(4) リクエスト Body の form-data に KEY/VALUE を入力します。
リクエストデータから、「リクエストしたパス」「送信元 IP アドレス」「cookie データ」「base64 エンコードされた body 部のデータ (をデコードしたもの)」が取り出せました。
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 を定義することで、手間なく発展させることができます。ぜひ挑戦してみてください。
筆者プロフィール

吉田 真吾
株式会社サイダス 取締役CTO
AWS Serverless Hero
証券システム構築からクラウドネイティブなシステム構築・運用などを経て現職。かたわらで JAWS-UG や Serverless Community(JP) の運営、また各種記事執筆を通じて、日本におけるサーバーレスの普及を促進。
AWS を無料でお試しいただけます