nitro icon indicating copy to clipboard operation
nitro copied to clipboard

Nuxt 3.0.0-rc.9 with Nitro 0.5.0 configuration error: Malformed Lambda proxy response

Open rhumie opened this issue 3 years ago • 4 comments
trafficstars

Environment

Nuxt 3.0.0-rc.9 with Nitro 0.5.0

Reproduction

I build the Nuxt app with aws-lambda deployment setting and configured Amazon API Gateway proxy integration to work with this lambda function. When I call my endpoint, I receive a configuration error and an HTTP 502 status code.

Execution failed due to configuration error: Malformed Lambda proxy response

Describe the bug

As I commented in the PR (https://github.com/unjs/nitro/pull/357#issuecomment-1247562089), this is due to the fact that cookies are returned in the response object.

For API Gateway to handle a Lambda function's response, the function must return output according to the following JSON format. Additional fields are not allowed.

{
    "isBase64Encoded": true|false,
    "statusCode": httpStatusCode,
    "headers": { "headerName": "headerValue", ... },
    "body": "..."
}

cf. https://aws.amazon.com/premiumsupport/knowledge-center/malformed-502-api-gateway/?nc1=h_ls

Additional context

Related Issues and PRs are as follows.

  • https://github.com/unjs/nitro/pull/357
  • https://github.com/unjs/nitro/issues/245

Logs

No response

rhumie avatar Sep 15 '22 04:09 rhumie

@danielroe At this point, is there any other way than to create a wrapper for the handler (index.mjs) ourselves?

rhumie avatar Sep 15 '22 08:09 rhumie

Yes, Nitro presets are fully extensible. Copy this file into your project, make the necessary changes, and set nitro.entry to point to your changed file...

(Of course, we do plan to fix the issue in Nitro itself!)

danielroe avatar Sep 15 '22 09:09 danielroe

Thank you for your prompt response. I will try the approach you suggested.

rhumie avatar Sep 15 '22 10:09 rhumie

@datake914 (and others affected): sorry about the push for the change in #357 -- the separate cookies property is required if you are using Cloudfront vs. API Gateway.

brtinney avatar Sep 15 '22 12:09 brtinney

Yes, Nitro presets are fully extensible. Copy this file into your project, make the necessary changes, and set nitro.entry to point to your changed file...

(Of course, we do plan to fix the issue in Nitro itself!)

This solution doesn't seem to work with current version of nuxt, any suggestion on how to do this?

timmak avatar Oct 16 '22 12:10 timmak

@timmak I am using Nuxt 3.0.0-rc.11 with Nitro 0.5.4 and it works fine.

I specify in nuxt.config.ts to use custom nitro.entry.ts only when process.env.NODE_ENV is production.

  nitro: {
    preset: 'aws-lambda',
    serveStatic: false,
    entry: process.env.NODE_ENV === 'production' ? 'nitro.entry.ts' : undefined,
  },

rhumie avatar Oct 21 '22 01:10 rhumie

@datake914 Maybe what I am missing is where to import nitroApp from in nitro.entry.ts, as currently it not outputting similar output as without the entry file

timmak avatar Oct 22 '22 12:10 timmak

make the necessary changes

@danielroe What are those changes? I am trying to do this with the cloudflare-pages.ts one and I can't get it to work.

PizzaConsole avatar Nov 13 '22 02:11 PizzaConsole

@timmak I am using Nuxt 3.0.0-rc.11 with Nitro 0.5.4 and it works fine.

I specify in nuxt.config.ts to use custom nitro.entry.ts only when process.env.NODE_ENV is production.

  nitro: {
    preset: 'aws-lambda',
    serveStatic: false,
    entry: process.env.NODE_ENV === 'production' ? 'nitro.entry.ts' : undefined,
  },

The path to nitro.entry.ts should be absolute, so for me this worked (considering nitro.entry.ts is placed in the root folder of the project):

    nitro: {
        preset: 'aws-lambda',
        serveStatic: false,
        entry: process.env.NODE_ENV === 'production' ? path.join(__dirname, 'nitro.entry.ts') : undefined,
    },

boogiefromzk avatar Nov 20 '22 19:11 boogiefromzk

make the necessary changes

@danielroe What are those changes? I am trying to do this with the cloudflare-pages.ts one and I can't get it to work.

I had to fix path to the nitro app and comment-out cookies in the response:

import type { APIGatewayProxyEvent, APIGatewayProxyEventHeaders, APIGatewayProxyEventV2, APIGatewayProxyResult, APIGatewayProxyResultV2, Context } from 'aws-lambda'
import '#internal/nitro/virtual/polyfill'
import { withQuery } from 'ufo'
import { nitroApp } from './node_modules/nitropack/dist/runtime/app'

// Compatibility types that work with AWS v1, AWS v2 & Netlify
type Event = Omit<APIGatewayProxyEvent, 'pathParameters' | 'stageVariables' | 'requestContext' | 'resource'> | Omit<APIGatewayProxyEventV2, 'pathParameters' | 'stageVariables' | 'requestContext' | 'resource'>
type Result = Exclude<APIGatewayProxyResult | APIGatewayProxyResultV2, string> & { statusCode: number }

export const handler = async function handler (event: Event, context: Context): Promise<Result> {
  const query = { ...event.queryStringParameters, ...(event as APIGatewayProxyEvent).multiValueQueryStringParameters }
  const url = withQuery((event as APIGatewayProxyEvent).path || (event as APIGatewayProxyEventV2).rawPath, query)
  const method = (event as APIGatewayProxyEvent).httpMethod || (event as APIGatewayProxyEventV2).requestContext?.http?.method || 'get'

  if ('cookies' in event && event.cookies) {
    event.headers.cookie = event.cookies.join(';')
  }

  const r = await nitroApp.localCall({
    event,
    url,
    context,
    headers: normalizeIncomingHeaders(event.headers),
    method,
    query,
    body: event.body // TODO: handle event.isBase64Encoded
  })

  const outgoingCookies = r.headers['set-cookie']
  const cookies = Array.isArray(outgoingCookies) ? outgoingCookies : outgoingCookies?.split(',') || []

  return {
    // cookies,
    statusCode: r.status,
    headers: normalizeOutgoingHeaders(r.headers),
    body: r.body.toString()
  }
}

function normalizeIncomingHeaders (headers?: APIGatewayProxyEventHeaders) {
  return Object.fromEntries(Object.entries(headers || {}).map(([key, value]) => [key.toLowerCase(), value!]))
}

function normalizeOutgoingHeaders (headers: Record<string, string | string[] | undefined>) {
  return Object.fromEntries(Object.entries(headers)
    .filter(([key]) => !['set-cookie'].includes(key))
    .map(([k, v]) => [k, Array.isArray(v) ? v.join(',') : v!]))
}

boogiefromzk avatar Nov 20 '22 21:11 boogiefromzk

@danielroe I've managed to make AWS Lambda work, the instructions are above, the simplest solution for this issue is adding another aws-lambda-no-cookies preset to nitro.

boogiefromzk avatar Nov 21 '22 14:11 boogiefromzk