From e812cb5c501adeaff3785f86b1ef459b3d9fdb3d Mon Sep 17 00:00:00 2001 From: Arda TANRIKULU Date: Sat, 22 Feb 2025 15:05:56 +0300 Subject: [PATCH 1/4] fix(node-fetch/http2): handle Http2 streams correctly --- .changeset/mighty-worlds-smoke.md | 9 ++++++ packages/node-fetch/src/Body.ts | 2 +- packages/server/test/http2.spec.ts | 46 ++++++++++++++++++++++-------- 3 files changed, 44 insertions(+), 13 deletions(-) create mode 100644 .changeset/mighty-worlds-smoke.md diff --git a/.changeset/mighty-worlds-smoke.md b/.changeset/mighty-worlds-smoke.md new file mode 100644 index 00000000000..5f3569c88e3 --- /dev/null +++ b/.changeset/mighty-worlds-smoke.md @@ -0,0 +1,9 @@ +--- +'@whatwg-node/node-fetch': patch +'@whatwg-node/server': patch +'@whatwg-node/fetch': patch +--- + +Fix HTTP/2 body stream handling + +This fixes the `TypeError: bodyInit.stream is not a function` error thrown when the incoming request is attempted to parse. diff --git a/packages/node-fetch/src/Body.ts b/packages/node-fetch/src/Body.ts index 53d1a581bbf..cef8a55dc03 100644 --- a/packages/node-fetch/src/Body.ts +++ b/packages/node-fetch/src/Body.ts @@ -508,7 +508,7 @@ function isFormData(value: any): value is FormData { } function isBlob(value: any): value is Blob { - return value?.stream != null; + return value?.stream != null && typeof value.stream === 'function'; } function isURLSearchParams(value: any): value is URLSearchParams { diff --git a/packages/server/test/http2.spec.ts b/packages/server/test/http2.spec.ts index ad472f655f5..e59bc0bf497 100644 --- a/packages/server/test/http2.spec.ts +++ b/packages/server/test/http2.spec.ts @@ -27,13 +27,22 @@ describeIf(!globalThis.Bun && !globalThis.Deno)('http2', () => { runTestsForEachFetchImpl((_, { createServerAdapter }) => { it('should support http2 and respond as expected', async () => { - let calledRequest: Request | undefined; - const handleRequest = jest.fn((_request: Request) => { - calledRequest = _request; - return new Response('Hey there!', { - status: 418, - headers: { 'x-is-this-http2': 'yes', 'content-type': 'text/plain;charset=UTF-8' }, - }); + const handleRequest = jest.fn(async (request: Request) => { + return Response.json( + { + body: await request.json(), + headers: Object.fromEntries(request.headers), + method: request.method, + url: request.url, + }, + { + headers: { + 'x-is-this-http2': 'yes', + 'content-type': 'text/plain;charset=UTF-8', + }, + status: 418, + }, + ); }); const adapter = createServerAdapter(handleRequest); @@ -49,11 +58,16 @@ describeIf(!globalThis.Bun && !globalThis.Deno)('http2', () => { const req = client.request({ [constantsHttp2.HTTP2_HEADER_METHOD]: 'POST', [constantsHttp2.HTTP2_HEADER_PATH]: '/hi', + [constantsHttp2.HTTP2_HEADER_CONTENT_TYPE]: 'application/json', }); + req.write(JSON.stringify({ hello: 'world' })); + req.end(); + const receivedNodeRequest = await new Promise<{ headers: Record; data: string; + status?: number | undefined; }>((resolve, reject) => { req.once( 'response', @@ -68,7 +82,8 @@ describeIf(!globalThis.Bun && !globalThis.Deno)('http2', () => { req.on('end', () => { resolve({ headers, - data, + data: JSON.parse(data), + status: headers[':status'], }); }); }, @@ -77,18 +92,25 @@ describeIf(!globalThis.Bun && !globalThis.Deno)('http2', () => { }); expect(receivedNodeRequest).toMatchObject({ - data: 'Hey there!', + data: { + body: { + hello: 'world', + }, + method: 'POST', + url: expect.stringMatching(/^http:\/\/localhost:\d+\/hi$/), + headers: { + 'content-type': 'application/json', + }, + }, headers: { ':status': 418, 'content-type': 'text/plain;charset=UTF-8', 'x-is-this-http2': 'yes', }, + status: 418, }); await new Promise(resolve => req.end(resolve)); - - expect(calledRequest?.method).toBe('POST'); - expect(calledRequest?.url).toMatch(/^http:\/\/localhost:\d+\/hi$/); }); }); }); From 6ce0559cd5257588396d5ca72a49da76fb972e4a Mon Sep 17 00:00:00 2001 From: Arda TANRIKULU Date: Sat, 22 Feb 2025 15:06:24 +0300 Subject: [PATCH 2/4] .. --- .changeset/mighty-worlds-smoke.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.changeset/mighty-worlds-smoke.md b/.changeset/mighty-worlds-smoke.md index 5f3569c88e3..1fffc723e8c 100644 --- a/.changeset/mighty-worlds-smoke.md +++ b/.changeset/mighty-worlds-smoke.md @@ -4,6 +4,4 @@ '@whatwg-node/fetch': patch --- -Fix HTTP/2 body stream handling - -This fixes the `TypeError: bodyInit.stream is not a function` error thrown when the incoming request is attempted to parse. +Fixes the `TypeError: bodyInit.stream is not a function` error thrown when the incoming HTTP/2 request is attempted to parse. From 72155f04ceffc4ba2046239575d6970c44e6707b Mon Sep 17 00:00:00 2001 From: Arda TANRIKULU Date: Sat, 22 Feb 2025 15:09:03 +0300 Subject: [PATCH 3/4] Fix the changeset --- .changeset/mighty-worlds-smoke.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/mighty-worlds-smoke.md b/.changeset/mighty-worlds-smoke.md index 1fffc723e8c..9ff4042d3f1 100644 --- a/.changeset/mighty-worlds-smoke.md +++ b/.changeset/mighty-worlds-smoke.md @@ -4,4 +4,4 @@ '@whatwg-node/fetch': patch --- -Fixes the `TypeError: bodyInit.stream is not a function` error thrown when the incoming HTTP/2 request is attempted to parse. +Fixes the `TypeError: bodyInit.stream is not a function` error thrown when `@whatwg-node/server` attempts the incoming HTTP/2 request to parse with `Request.json`, `Request.text`, `Request.formData`, or `Request.blob` methods. From 869be2474b8eedcb461dd935cb99971cc0bdab21 Mon Sep 17 00:00:00 2001 From: Arda TANRIKULU Date: Sat, 22 Feb 2025 15:09:19 +0300 Subject: [PATCH 4/4] changeset --- .changeset/mighty-worlds-smoke.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/mighty-worlds-smoke.md b/.changeset/mighty-worlds-smoke.md index 9ff4042d3f1..3ab3aa5e7c4 100644 --- a/.changeset/mighty-worlds-smoke.md +++ b/.changeset/mighty-worlds-smoke.md @@ -4,4 +4,4 @@ '@whatwg-node/fetch': patch --- -Fixes the `TypeError: bodyInit.stream is not a function` error thrown when `@whatwg-node/server` attempts the incoming HTTP/2 request to parse with `Request.json`, `Request.text`, `Request.formData`, or `Request.blob` methods. +Fixes the `TypeError: bodyInit.stream is not a function` error thrown when `@whatwg-node/server` is used with `node:http2` and attempts the incoming HTTP/2 request to parse with `Request.json`, `Request.text`, `Request.formData`, or `Request.blob` methods.