Server-Sent Events(SSE)をLambdaから実行する
はじめに
最近、チャットアプリケーションを作成しており、ChatGPT のようなリアルタイムストリーミング応答機能を実装したいと技術調査をしていました。
この機能を実装するために、Server-Sent Events(SSE)を使うことにしました。
そこで、AWS Lambda から SSE を実行する方法を調査しました。
本記事では、Hono ベースの SSE サーバを AWS Lambda Web Adapter を使い、 AWS Lambda から実行する方法を紹介します。
アーキテクチャ
今回、Lambda で SSE を実行するにあたり、以下の技術を使用しました。
Hono の SSE コードは以下です。
Hono の SSE コード
app.post("/sse", (c) => {
return streamSSE(c, async (stream) => {
console.log("クライアントが接続しました");
// ウェルカムメッセージ
await stream.writeSSE({
data: JSON.stringify({ message: "接続しました!" }),
});
// 10回データを送信
for (let i = 0; i < 10; i++) {
try {
const data = {
timestamp: new Date().toISOString(),
randomValue: Math.floor(Math.random() * 100),
message: `現在日時 ${new Date().toLocaleString()}`,
};
await stream.writeSSE({
data: JSON.stringify(data),
});
// 2秒待機
await stream.sleep(2000);
} catch (error) {
console.log("クライアントが切断しました");
break;
}
}
});
});
アーキテクチャ図

今回のアーキテクチャは CloudFront + Lambda@Edge + Lambda です。
Lambda の Function URL を CloudFront でプロキシし、CloudFront 経由でないと Lambda にアクセスできないようにしています。
また、 CloudFront のオリジンアクセスコントロール(OAC) と Lambda@Edge を使用して、x-amz-content-sha256を付与しています。
AWS CDK のコードも以下に残しておきます。
※ Lambda、CloudFront、OAC の設定のみです。Lambda@Edge はコンソールから作りました。
AWS CDK のコード
import * as cdk from "aws-cdk-lib";
import * as cloudfront from "aws-cdk-lib/aws-cloudfront";
import * as origins from "aws-cdk-lib/aws-cloudfront-origins";
import * as ecr from "aws-cdk-lib/aws-ecr";
import * as lambda from "aws-cdk-lib/aws-lambda";
import type { Construct } from "constructs";
export class TestStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// ECR Repository
const repository = new ecr.Repository(this, "TestRepository", {
repositoryName: "test-repository",
removalPolicy: cdk.RemovalPolicy.DESTROY,
});
// Lambda Function
const lambdaFunction = new lambda.Function(this, "TestLambda", {
runtime: lambda.Runtime.FROM_IMAGE,
code: lambda.Code.fromEcrImage(repository, {
tagOrDigest: "latest",
}),
handler: lambda.Handler.FROM_IMAGE,
timeout: cdk.Duration.minutes(5),
memorySize: 150,
environment: {
AWS_LWA_INVOKE_MODE: "response_stream",
},
});
repository.grantPull(lambdaFunction);
// Lambda Function URL
const lambdaFunctionUrl = lambdaFunction.addFunctionUrl({
authType: lambda.FunctionUrlAuthType.AWS_IAM,
cors: {
allowedOrigins: ["*"],
allowedMethods: [lambda.HttpMethod.ALL], // OPTIONS含め全許可
allowedHeaders: ["*"],
},
invokeMode: lambda.InvokeMode.RESPONSE_STREAM,
});
// CloudFront Distribution with OAC
const oac = new cloudfront.CfnOriginAccessControl(
this,
"OriginAccessControl",
{
originAccessControlConfig: {
name: "OAC for Lambda Function URLs",
originAccessControlOriginType: "lambda",
signingBehavior: "always",
signingProtocol: "sigv4",
},
}
);
const distribution = new cloudfront.Distribution(this, "TestDistribution", {
defaultBehavior: {
origin:
origins.FunctionUrlOrigin.withOriginAccessControl(lambdaFunctionUrl),
allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL,
},
});
// OACをCloudFront Distributionに関連付け
const cfnDist = distribution.node
.defaultChild as cloudfront.CfnDistribution;
cfnDist.addPropertyOverride(
"DistributionConfig.Origins.0.OriginAccessControlId",
oac.attrId
);
new cdk.CfnOutput(this, "CloudFrontUrl", {
value: `https://${distribution.domainName}`,
});
}
}
なぜ Lambda の Function URL を CloudFront でプロキシするのか?
Lambda でレスポンスストリーミングを使用するには、Function URL を使用する必要があります。
ちなみに、API Gateway はサポートされていないようです。
ただ、Function URL を直接叩くのはセキュリティ的に弱くなります。
そこで、CloudFront でプロキシすることで、CloudFront 経由でないと Lambda にアクセスできないようにします。
さらに、WAF を適用し、セキュリティを強化できることも、本構成のメリットです。
CloudFront OAC と Lambda@Edge を使用した x-amz-content-sha256ヘッダーの付与
本構成でPOSTメソッド、PUTメソッドを使用する場合、Lambda の Function URL にリクエストを送信する際に、x-amz-content-sha256ヘッダーを付与する必要があります。
そのため、以下の流れで CloudFront OAC と Lambda@Edge を使用して、x-amz-content-sha256ヘッダーを付与しています。
- CloudFront のオリジンアクセスコントロール (OAC)を使用した SigV4 署名プロトコルでリクエストを署名。
- CloudFront をトリガーとした Lambda@Edge でリクエストボディのハッシュ値を計算し、
x-amz-content-sha256ヘッダーを付与。 - Lambda の Function URL にリクエストを送信。
ハッシュ値の計算コードは以下の記事を参考に作成しました。
確認ができないと、403エラーが返ってくるので注意してください。
Lambda の呼び出しモードの設定
レスポンスストリーミングを使用するために以下を設定します。
- Lambda の呼び出しモード(
InvokeMode)をRESPONSE_STREAMに設定 - 環境変数
AWS_LWA_INVOKE_MODEをresponse_streamに設定
結果
実行した結果は以下になります。

おわりに
以上、AWS Lambda から SSE を実行する方法を紹介しました。
参考記事
Discussion