Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: async behaviour #1451

Closed
wants to merge 12 commits into from
6 changes: 1 addition & 5 deletions src/events/http/Endpoint.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export default class Endpoint {
// loosely based on:
// https://github.com/serverless/serverless/blob/v1.59.2/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.js#L380
#getIntegration(http) {
const { integration, async: isAsync } = http
const { integration } = http
if (integration) {
const normalizedIntegration = integration.toUpperCase().replace('-', '_')
if (normalizedIntegration === 'LAMBDA') {
Expand All @@ -104,10 +104,6 @@ export default class Endpoint {
return normalizedIntegration
}

if (isAsync) {
return 'AWS'
}

return 'AWS_PROXY'
}

Expand Down
15 changes: 13 additions & 2 deletions src/events/http/HttpServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,7 @@ export default class HttpServer {
const response = h.response()
const contentType = request.mime || 'application/json' // default content type

const { integration, requestTemplates } = endpoint
const { integration, requestTemplates, async: isAsync } = endpoint

// default request template to '' if we don't have a definition pushed in from serverless or endpoint
const requestTemplate =
Expand Down Expand Up @@ -576,13 +576,15 @@ export default class HttpServer {
request,
stage,
endpoint.routeKey,
isAsync,
additionalRequestContext,
)
: new LambdaProxyIntegrationEvent(
request,
stage,
requestPath,
endpoint.isHttpApi ? endpoint.routeKey : null,
isAsync,
additionalRequestContext,
)

Expand All @@ -599,7 +601,16 @@ export default class HttpServer {
let err

try {
result = await lambdaFunction.runHandler()
if (isAsync) {
// API Gateway returns 200 automatically
lambdaFunction.runHandler().catch(() => {})
result = {
body: '',
statusCode: 200,
}
} else {
result = await lambdaFunction.runHandler()
}
} catch (_err) {
err = _err
}
Expand Down
22 changes: 21 additions & 1 deletion src/events/http/lambda-events/LambdaProxyIntegrationEvent.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,22 @@ export default class LambdaProxyIntegrationEvent {

#stage = null

constructor(request, stage, path, routeKey, additionalRequestContext) {
#isAsync = false

constructor(
request,
stage,
path,
routeKey,
isAsync,
additionalRequestContext,
) {
this.#additionalRequestContext = additionalRequestContext || {}
this.#path = path
this.#routeKey = routeKey
this.#request = request
this.#stage = stage
this.#isAsync = isAsync || false
}

create() {
Expand Down Expand Up @@ -96,6 +106,16 @@ export default class LambdaProxyIntegrationEvent {
headers['Content-Length'] = String(byteLength(body))
}

if (this.#isAsync) {
if (headers['Content-Type'] === 'application/x-www-form-urlencoded') {
body = Object.fromEntries(new URLSearchParams(body).entries())
}

if (headers['Content-Type'] === 'application/json') {
body = JSON.parse(body)
}
}

// Set a default Content-Type if not provided.
if (
!headers['Content-Type'] &&
Expand Down
52 changes: 52 additions & 0 deletions tests/old-unit/LambdaProxyIntegrationEvent.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -747,6 +747,58 @@ describe('LambdaProxyIntegrationEvent', () => {
})
})

describe('with isAsync', () => {
it('should parse the body of a call with a application/json header', () => {
const requestBuilder = new RequestBuilder('POST', '/async')
requestBuilder.addBody(JSON.stringify({ key: 'value' }))
requestBuilder.addHeader('Content-Type', 'application/json')
const request = requestBuilder.toObject()
const path = 'path'
const stageVariables = 'stageVariables'
const routeKey = undefined
const isAsync = true

const lambdaProxyIntegrationEvent = new LambdaProxyIntegrationEvent(
request,
stage,
path,
stageVariables,
routeKey,
isAsync,
).create()

assert.equal(
lambdaProxyIntegrationEvent.body,
JSON.stringify({ key: 'value' }),
)
})

it('should parse the body of a call with a application/x-www-form-urlencoded header', () => {
const requestBuilder = new RequestBuilder('POST', '/async')
requestBuilder.addBody(new URLSearchParams({ key: 'value' }).toString())
requestBuilder.addHeader(
'Content-Type',
'application/x-www-form-urlencoded',
)
const request = requestBuilder.toObject()
const path = 'path'
const stageVariables = 'stageVariables'
const routeKey = undefined
const isAsync = true

const lambdaProxyIntegrationEvent = new LambdaProxyIntegrationEvent(
request,
stage,
path,
stageVariables,
routeKey,
isAsync,
).create()

assert.equal(lambdaProxyIntegrationEvent.body, 'key=value')
})
})

describe('with operation name', () => {
const requestBuilder = new RequestBuilder('GET', '/fn1')
const request = requestBuilder.toObject()
Expand Down
28 changes: 28 additions & 0 deletions tests/old-unit/offline.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,34 @@ describe('Offline', () => {
})
})

describe('lambda-proxy integration with async', () => {
it('should return 200 even if lambda fails', async () => {
const offline = await new OfflineBuilder()
.addFunctionConfig('async', {
events: [
{
http: {
async: true,
method: 'GET',
path: 'async',
},
},
],
handler: 'tests/old-unit/fixtures/handler.throwDone',
})
.toObject()

const res = await offline.inject({
method: 'GET',
payload: { data: 'input' },
url: '/dev/async',
})

assert.equal(res.headers['content-type'], 'application/json')
assert.equal(res.statusCode, 200)
})
})

describe('with catch-all route', () => {
it('should match arbitary route', async () => {
const offline = new OfflineBuilder().addFunctionConfig('test', {
Expand Down
Loading