diff --git a/packages/aws-lambda/src/response.test.ts b/packages/aws-lambda/src/response.test.ts index 1d0c859..b374b2c 100644 --- a/packages/aws-lambda/src/response.test.ts +++ b/packages/aws-lambda/src/response.test.ts @@ -11,10 +11,15 @@ const DELIMITER = new Uint8Array(8) const fromSpy = vi.fn((responseStream: HttpResponseStream, metadata: Record) => { // mimics what the lambda runtime does: send the metadata prelude - // ahead of the body on the very same stream + // ahead of the first `write` call only, `end(chunk)` bypasses it responseStream.setContentType('application/vnd.awslambda.http-integration-response') - responseStream.write(JSON.stringify(metadata)) - responseStream.write(DELIMITER) + const write = responseStream.write.bind(responseStream) + responseStream.write = (chunk: unknown) => { + responseStream.write = write + write(JSON.stringify(metadata)) + write(DELIMITER) + return write(chunk) + } return responseStream }) @@ -159,6 +164,23 @@ describe('sendStandardResponse', () => { expect(responseStream.writableEnded).toBe(true) }) + it('chunked (empty)', async () => { + const responseStream = createResponseStream() + + await sendStandardResponse(responseStream, { + status: 200, + headers: {}, + body: new Blob([]), + }) + + expect(metadataOf(responseStream)).toMatchObject({ + statusCode: 200, + }) + + expect(bodyOf(responseStream)).toBe('') + expect(responseStream.writableEnded).toBe(true) + }) + it('destroys the response stream when the body stream errors during streaming', async () => { const responseStream = createResponseStream() diff --git a/packages/aws-lambda/src/response.ts b/packages/aws-lambda/src/response.ts index f31912f..828ca4a 100644 --- a/packages/aws-lambda/src/response.ts +++ b/packages/aws-lambda/src/response.ts @@ -46,7 +46,7 @@ export async function sendStandardResponse( const [headers, setCookies] = toLambdaHeaders(resHeaders) - // sends the metadata prelude (status, headers, cookies) and + // arms the metadata prelude (status, headers, cookies) and // returns the stream the body should be written to const res = awslambda.HttpResponseStream.from(responseStream, { statusCode: standardResponse.status, @@ -57,6 +57,10 @@ export async function sendStandardResponse( res.once('error', reject) res.once('close', resolve) + // The runtime only sends the armed prelude ahead of the first `write` call: + // `end(chunk)` bypasses it and an empty body never writes, so trigger it now + res.write('') + if (resBody === undefined) { // NOTE: Lambda functions don't allow passing undefined to `res.end` res.end() diff --git a/packages/aws-lambda/src/types.ts b/packages/aws-lambda/src/types.ts index 9761309..e6b9ab0 100644 --- a/packages/aws-lambda/src/types.ts +++ b/packages/aws-lambda/src/types.ts @@ -105,7 +105,8 @@ export interface AwsLambdaGlobal { HttpResponseStream: { /** - * Sends the metadata prelude and returns the stream to write the body to. + * Arms the metadata prelude and returns the stream to write the body to. + * The prelude is only sent ahead of the first `write` call, never by `end(chunk)`. */ from( responseStream: HttpResponseStream,