feat(nitro): add support for lambda v2 payload format (#3070)

Co-authored-by: Pooya Parsa <pyapar@gmail.com>
This commit is contained in:
Daniel Roe 2022-02-07 12:25:05 +00:00 committed by GitHub
parent 68a227af03
commit 614e87e9f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 65 additions and 8 deletions

View File

@ -24,6 +24,10 @@ Or directly use the `NITRO_PRESET` environment variable when running `nuxt build
NITRO_PRESET=lambda npx nuxt build
```
::alert
AWS Lambda [defaults to payload version v2](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html). This Nitro preset supports both v1 and v2 payloads.
::
### Entrypoint
When running `nuxt build` with the Lambda preset, the result will be an entry point that exports a handler function that responds to an event and returns a response.
@ -36,5 +40,5 @@ It can be used programmatically or as part of a deployment.
import { handler } from './.output/server'
// Use programmatically
const { statusCode, headers, body } = handler({ path: '/' })
const { statusCode, headers, body } = handler({ rawPath: '/' })
```

View File

@ -73,6 +73,7 @@
},
"devDependencies": {
"@nuxt/schema": "3.0.0",
"@types/aws-lambda": "^8.10.92",
"@types/fs-extra": "^9.0.13",
"@types/http-proxy": "^1.17.8",
"@types/node-fetch": "^3.0.2",

View File

@ -1,21 +1,34 @@
import type { APIGatewayProxyEvent, APIGatewayProxyEventHeaders, APIGatewayProxyEventV2, Context } from 'aws-lambda'
import '#polyfill'
import { withQuery } from 'ufo'
import type { HeadersObject } from 'unenv/runtime/_internal/types'
import { localCall } from '../server'
export async function handler (event, context) {
export const handler = async function handler (event: APIGatewayProxyEvent & APIGatewayProxyEventV2, context: Context) {
const url = withQuery(event.path || event.rawPath, event.queryStringParameters)
const method = event.httpMethod || event.requestContext?.http?.method || 'get'
const r = await localCall({
event,
url: withQuery(event.path, event.queryStringParameters),
url,
context,
headers: event.headers,
method: event.httpMethod,
headers: normalizeIncomingHeaders(event.headers),
method,
query: event.queryStringParameters,
body: event.body // TODO: handle event.isBase64Encoded
})
return {
statusCode: r.status,
headers: r.headers,
headers: normalizeOutgoingHeaders(r.headers),
body: r.body.toString()
}
}
function normalizeIncomingHeaders (headers: APIGatewayProxyEventHeaders) {
return Object.fromEntries(Object.entries(headers).map(([key, value]) => [key.toLowerCase(), value as string]))
}
function normalizeOutgoingHeaders (headers: HeadersObject) {
return Object.fromEntries(Object.entries(headers).map(([k, v]) => [k, Array.isArray(v) ? v.join(',') : v]))
}

View File

@ -1,19 +1,50 @@
import { resolve } from 'pathe'
import { describe } from 'vitest'
import type { APIGatewayProxyEvent, APIGatewayProxyEventV2 } from 'aws-lambda'
import { setupTest, testNitroBehavior, importModule } from './_tests'
describe('nitro:preset:lambda', () => {
const ctx = setupTest('lambda')
// Lambda v1 paylod
testNitroBehavior(ctx, async () => {
const { handler } = await importModule(resolve(ctx.outDir, 'server/index.mjs'))
return async ({ url: rawRelativeUrl, headers, method, body }) => {
// creating new URL object to parse query easier
const url = new URL(`https://example.com${rawRelativeUrl}`)
const queryStringParameters = Object.fromEntries(url.searchParams.entries())
const event = {
const event: Partial<APIGatewayProxyEvent> = {
resource: '/my/path',
path: url.pathname,
headers: headers || {},
method: method || 'GET',
httpMethod: method || 'GET',
queryStringParameters,
body: body || ''
}
const res = await handler(event)
return {
data: res.body
}
}
})
// Lambda v2 paylod
testNitroBehavior(ctx, async () => {
const { handler } = await importModule(resolve(ctx.outDir, 'server/index.mjs'))
return async ({ url: rawRelativeUrl, headers, method, body }) => {
// creating new URL object to parse query easier
const url = new URL(`https://example.com${rawRelativeUrl}`)
const queryStringParameters = Object.fromEntries(url.searchParams.entries())
const event: Partial<APIGatewayProxyEventV2> = {
rawPath: url.pathname,
headers: headers || {},
requestContext: {
...Object.fromEntries([['accountId'], ['apiId'], ['domainName'], ['domainPrefix']]),
http: {
path: url.pathname,
protocol: 'http',
...Object.fromEntries([['userAgent'], ['sourceIp']]),
method: method || 'GET'
}
},
queryStringParameters,
body: body || ''
}

View File

@ -2967,6 +2967,7 @@ __metadata:
"@rollup/plugin-virtual": ^2.0.3
"@rollup/plugin-wasm": ^5.1.2
"@rollup/pluginutils": ^4.1.2
"@types/aws-lambda": ^8.10.92
"@types/fs-extra": ^9.0.13
"@types/http-proxy": ^1.17.8
"@types/jsdom": ^16.2.14
@ -3902,6 +3903,13 @@ __metadata:
languageName: node
linkType: hard
"@types/aws-lambda@npm:^8.10.92":
version: 8.10.92
resolution: "@types/aws-lambda@npm:8.10.92"
checksum: 71c44d83a1c88aa6dbc920baedfb2d100b8843a3d210c695ccaafb30dfb75f04398b0e5368100022acbf75c55d456c61774242f20dd70915fc63d85430cbcf8a
languageName: node
linkType: hard
"@types/babel__core@npm:7.1.14":
version: 7.1.14
resolution: "@types/babel__core@npm:7.1.14"