Skip to content

Commit c8338a6

Browse files
authored
fix(aws-lambda): always send the metadata prelude (#101)
On AWS Lambda, `sendStandardResponse` could send a response without its metadata prelude, so the status, headers, and cookies were lost. `HttpResponseStream.from()` only sets the prelude up, and the runtime sends it just before the first `write()` call. `end(chunk)` skips that, and empty bodies never call `write()`. The fix makes one empty write right after `from()` so every response sends its prelude. ## Fixes - JSON, URLSearchParams, `undefined`, empty-Blob, and empty-stream responses now send their status, headers, and cookies. Before, they reached the wire as just the body, or as zero bytes. - Non-empty streamed responses are unaffected, apart from sending their status and headers right away instead of waiting for the first chunk. ## Testing - Checked against the stream code in `aws-lambda-ric` 4.0.2 (3.3.0 uses the same prelude logic). The bytes were captured by a local HTTP server standing in for the Lambda API. Every body type now carries the prelude and delimiter exactly once. Not tested on a real Lambda deployment. - The unit-test mock used to write the prelude itself, which hid the bug. It now waits for the first `write()` like the runtime. Without the fix, 3 tests fail: `buffered (empty)`, `buffered (json)`, and the new `chunked (empty)`.
1 parent a6c90ca commit c8338a6

3 files changed

Lines changed: 32 additions & 5 deletions

File tree

‎packages/aws-lambda/src/response.test.ts‎

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,15 @@ const DELIMITER = new Uint8Array(8)
1111

1212
const fromSpy = vi.fn((responseStream: HttpResponseStream, metadata: Record<string, unknown>) => {
1313
// mimics what the lambda runtime does: send the metadata prelude
14-
// ahead of the body on the very same stream
14+
// ahead of the first `write` call only, `end(chunk)` bypasses it
1515
responseStream.setContentType('application/vnd.awslambda.http-integration-response')
16-
responseStream.write(JSON.stringify(metadata))
17-
responseStream.write(DELIMITER)
16+
const write = responseStream.write.bind(responseStream)
17+
responseStream.write = (chunk: unknown) => {
18+
responseStream.write = write
19+
write(JSON.stringify(metadata))
20+
write(DELIMITER)
21+
return write(chunk)
22+
}
1823
return responseStream
1924
})
2025

@@ -159,6 +164,23 @@ describe('sendStandardResponse', () => {
159164
expect(responseStream.writableEnded).toBe(true)
160165
})
161166

167+
it('chunked (empty)', async () => {
168+
const responseStream = createResponseStream()
169+
170+
await sendStandardResponse(responseStream, {
171+
status: 200,
172+
headers: {},
173+
body: new Blob([]),
174+
})
175+
176+
expect(metadataOf(responseStream)).toMatchObject({
177+
statusCode: 200,
178+
})
179+
180+
expect(bodyOf(responseStream)).toBe('')
181+
expect(responseStream.writableEnded).toBe(true)
182+
})
183+
162184
it('destroys the response stream when the body stream errors during streaming', async () => {
163185
const responseStream = createResponseStream()
164186

‎packages/aws-lambda/src/response.ts‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export async function sendStandardResponse(
4646

4747
const [headers, setCookies] = toLambdaHeaders(resHeaders)
4848

49-
// sends the metadata prelude (status, headers, cookies) and
49+
// arms the metadata prelude (status, headers, cookies) and
5050
// returns the stream the body should be written to
5151
const res = awslambda.HttpResponseStream.from(responseStream, {
5252
statusCode: standardResponse.status,
@@ -57,6 +57,10 @@ export async function sendStandardResponse(
5757
res.once('error', reject)
5858
res.once('close', resolve)
5959

60+
// The runtime only sends the armed prelude ahead of the first `write` call:
61+
// `end(chunk)` bypasses it and an empty body never writes, so trigger it now
62+
res.write('')
63+
6064
if (resBody === undefined) {
6165
// NOTE: Lambda functions don't allow passing undefined to `res.end`
6266
res.end()

‎packages/aws-lambda/src/types.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,8 @@ export interface AwsLambdaGlobal {
105105

106106
HttpResponseStream: {
107107
/**
108-
* Sends the metadata prelude and returns the stream to write the body to.
108+
* Arms the metadata prelude and returns the stream to write the body to.
109+
* The prelude is only sent ahead of the first `write` call, never by `end(chunk)`.
109110
*/
110111
from(
111112
responseStream: HttpResponseStream,

0 commit comments

Comments
 (0)