NTTテクノクロス株式会社の渡邉洋平です。
この間、HonoがAWS Lambdaでも動くと知ったので、今回は試していきます。
What's Hono?
Honoは@yusukebe氏を中心にOSSで開発されている、Edge向けのWebフレームワークです。
Hono - [炎] means flame🔥 in Japanese - is a small, simple, and ultrafast web framework for the Edges. It works on Cloudflare Workers, Fastly Compute@Edge, Deno, Bun, Vercel, Lagon, Node.js, and others. Fast, but not only fast.
この辺の資料も読むとわかる通り、標準技術を極力用いて「どこでも動く」を実現することが目的の一つである様子がわかります。
検証
今回は以下の2通りのアーキテクチャパターンでHonoでの動作を試します。
- AWS Lambda Function URLs
- Amazon API Gateway + AWS Lambda
本検証ではHono v3.1.8を利用しています
1. GET
Hono
公式に従って、以下のコマンドで簡単にテンプレートを作成することができます。
npm create hono@latest my-app
Which template do you want to use? › aws-lambda
my-app
の中身を見ると、npm run deploy
で直接Lambda Functionにデプロイできるようデザインされていることがわかります。
{
"type": "module",
"scripts": {
"build": "esbuild --bundle --outfile=./dist/index.js --platform=node --target=node18 ./src/index.ts",
"zip": "zip -j lambda.zip dist/index.js",
"update": "aws lambda update-function-code --zip-file fileb://lambda.zip --function-name hello",
"deploy": "run-s build zip update"
},
"devDependencies": {
"esbuild": "^0.17.11",
"npm-run-all": "^4.1.5"
},
"dependencies": {
"hono": "^3.1.6"
}
}
CDK
Hono標準でデプロイ機構が組み込まれているとはいえ、Typescriptで書いたコードだけでなくIAM RoleやAPI Gatewayまで含めた一式を扱う時はCDKが便利なので、今回は簡単にCDK環境を整備します。
mkdir cdk
cd cdk
cdk init app -l typescript
npm i hono
mkdir lambda
cp ../my-app/src/index.ts lambda/
Lambda用のコードは、まずはmy-app/src/index.ts
の内容をそのまま使います。
import { Hono } from 'hono'
import { handle } from 'hono/aws-lambda'
const app = new Hono()
app.get('/', (c) => c.text('Hello Hono!'))
export const handler = handle(app)
Honoで書いたコードを動かすLambda環境は以下で定義して、cdk deploy
コマンドでデプロイします。
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as apigw from 'aws-cdk-lib/aws-apigateway'
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
export class CdkStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const fn = new NodejsFunction(this, 'lambda', {
entry: 'lambda/index.ts',
handler: 'handler',
runtime: lambda.Runtime.NODEJS_18_X,
});
fn.addFunctionUrl({
authType: lambda.FunctionUrlAuthType.NONE
})
new apigw.LambdaRestApi(this, 'myapi', {
handler: fn,
});
}
}
動作
Lambda、API Gateway、どちらのエンドポイントでもレスポンスが想定通りに返却されることがわかりました。
# Lambda Function URLs
curl https://4tlatzqa463rz42z3ytb2ingua0twkik.lambda-url.ap-northeast-1.on.aws/
Hello Hono!
# API Gateway
curl https://tof1s221v8.execute-api.ap-northeast-1.amazonaws.com/prod/
Hello Hono!
2. POST
ソースコード
チュートリアル相当が動くのは分かったので、少しだけ下の機能をhonoで追加します。
- ロガーの追加
- GETの内容でRuntime情報を返却するように変更
- POSTの追加
import { Hono } from 'hono'
import { handle } from 'hono/aws-lambda'
+ import { logger } from 'hono/logger'
const app = new Hono()
+ interface UserBody {
+ user: string;
+ }
+ app.use('*', logger())
app.get('/', (c) => {
- return c.text('Hello Hono!'))
+ return c.text(`Hono is running on ${c.runtime}\n`)
})
+ app.post('/', async (c) => {
+ const { user } = await c.req.json<UserBody>();
+ if (!user) {
+ return c.text('Please Setting "user"', 400)
+ }
+ return c.json({"user": user})
+ })
export const handler = handle(app)
あくまで開発向け機能だが、一度AWS環境が作られていれば、コードの差分はCDKのhotswap
モードで更新できる。
cdk deploy --hotswap
動作(GET)
# Lambda Function URLs
curl https://4tlatzqa463rz42z3ytb2ingua0twkik.lambda-url.ap-northeast-1.on.aws/
Hono is running on node
# API Gateway
curl https://tof1s221v8.execute-api.ap-northeast-1.amazonaws.com/prod/
Hono is running on node
どちらも想定通り動作しました。
公式サイトの記述を見るにRuntimeはnode
かOther
かと想定していましたが、Lambda(Node.js)ではnodeと判定されるようです。
動作(POST)
続いてPOSTでもリクエストを投げたところAPIGatewayは正常に動きました。 しかし、Lambda Function URLsは上手くいかない様子。
# Lambda Function URLs
curl -X POST -H 'Content-Type: application/json' -d '{ "user": "fuga" }' https://4tlatzqa463rz42z3ytb2ingua0twkik.lambda-url.ap-northeast-1.on.aws/
Internal Server Error
# API Gateway
curl -X POST -H 'Content-Type: application/json' -d '{ "user": "fuga" }' https://tof1s221v8.execute-api.ap-northeast-1.amazonaws.com/prod/
{"user":"fuga"}
curl -X POST -H 'Content-Type: application/json' -d '{ "name": "fuga" }' https://tof1s221v8.execute-api.ap-northeast-1.amazonaws.com/prod/
Please Setting "user"
こんな時の為にLoggerを追加していたので、CloudWatch Logsを確認すると以下のログが出ていました。
{
"errorType": "TypeError",
"errorMessage": "Request with GET/HEAD method cannot have body.",
"stack": [
"TypeError: Request with GET/HEAD method cannot have body.",
" at new Request (node:internal/deps/undici/undici:7171:17)",
" at createRequest (/var/task/index.js:1570:10)",
" at Runtime.handler (/var/task/index.js:1540:17)",
" at Runtime.handleOnceNonStreaming (file:///var/runtime/index.mjs:1086:29)"
]
}
ん? Lambda Function URLsでのリクエストはGET/HEADとして連携しているので、Body部と不整合を起こしたのだろうか。-d
なしで再度POSTを投げてみます。
curl -X POST -H 'Content-Type: application/json' https://4tlatzqa463rz42z3ytb2ingua0twkik.lambda-url.ap-northeast-1.on.aws/
Hono is running on node
やはりGETとして解釈されていることがわかりました。Hono、Function URLのいずれかの制約なのかもしれません。
Hono側のコードを確認すると、AWS Lambda用のAdaptorは、API Gatewayベースでのリクエストのみを利用する前提に見えます。
https://github.com/honojs/hono/blob/main/src/adapter/aws-lambda/handler.ts
現時点では、Lambda Function URLやLambda@Edgeでは利用しない方が良さそうです。
まとめ
今回はHono × CDKでの動作を簡単に確認できました。
今年の「AWS Dev Day 2023 Tokyo」でも作者様の話が聞けそうなので、楽しみにしています。