fix(relay): deliver an explicit empty body frame for zero-chunk body sources

A request body stream that yields no chunks still declares hasBody in
the request head; send one empty HttpBody frame so the host can tell an
intentionally empty body apart from body frames lost in transit.
This commit is contained in:
Bohdan Triapitsyn
2026-08-12 16:57:46 +03:00
parent 95c5a0a69c
commit 4074986112
2 changed files with 30 additions and 0 deletions
@@ -312,6 +312,27 @@ describe('createRelayTunnelClient', () => {
expect(await c.text()).toBe('payload-xyz');
});
test('a body source with zero chunks still delivers an explicit empty body frame', async () => {
const frames: TunnelFrame[] = [];
const { client } = await setupClient({ recordFrame: (frame) => frames.push(frame) });
track(client);
const emptyStream = new ReadableStream<Uint8Array>({
start(controller) {
controller.close();
},
});
const response = await client.fetch('/echo-body', { method: 'POST', body: emptyStream });
expect(response.status).toBe(200);
expect(await response.text()).toBe('');
// The head declares hasBody, so the host must see at least one HttpBody
// frame — otherwise it treats the body as lost in transit and aborts.
const request = frames.find((frame) => frame.frameType === TunnelFrameType.HttpRequest);
expect(request).toBeDefined();
const bodyFrames = frames.filter((frame) => frame.frameType === TunnelFrameType.HttpBody && frame.streamId === request!.streamId);
expect(bodyFrames.length).toBe(1);
expect(bodyFrames[0]!.payload.length).toBe(0);
});
test('streams a response body incrementally', async () => {
const { client } = await setupClient();
track(client);