From 9c8aa37f3a6bbdd182995879e1840c79ff422705 Mon Sep 17 00:00:00 2001 From: swapnil-nagar Date: Sun, 26 Jul 2026 21:13:34 -0700 Subject: [PATCH] E2E to validate streaming path by omitting the request body for GET/HEAD methods --- app/v3/helloWorld/function.json | 2 +- app/v4/src/functions/helloWorld.ts | 2 +- src/http.test.ts | 58 ++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 2 deletions(-) diff --git a/app/v3/helloWorld/function.json b/app/v3/helloWorld/function.json index c67f3b5..ebbdf28 100644 --- a/app/v3/helloWorld/function.json +++ b/app/v3/helloWorld/function.json @@ -5,7 +5,7 @@ "type": "httpTrigger", "direction": "in", "name": "req", - "methods": ["get", "post"] + "methods": ["get", "post", "head"] }, { "type": "http", diff --git a/app/v4/src/functions/helloWorld.ts b/app/v4/src/functions/helloWorld.ts index ff807e5..b4513a7 100644 --- a/app/v4/src/functions/helloWorld.ts +++ b/app/v4/src/functions/helloWorld.ts @@ -12,7 +12,7 @@ export async function helloWorld(request: HttpRequest, context: InvocationContex } app.http('helloWorld', { - methods: ['GET', 'POST'], + methods: ['GET', 'POST', 'HEAD'], authLevel: 'anonymous', handler: helloWorld, }); diff --git a/src/http.test.ts b/src/http.test.ts index 2ae01ad..9a1cd72 100644 --- a/src/http.test.ts +++ b/src/http.test.ts @@ -3,6 +3,7 @@ import Agent from 'agentkeepalive'; import { expect } from 'chai'; +import * as http from 'http'; import { encode } from 'iconv-lite'; // Node.js core added support for fetch in v18, but while we're testing versions <18 we'll use "node-fetch" import { default as fetch, HeadersInit } from 'node-fetch'; @@ -26,6 +27,42 @@ const octetStreamHeaders = getContentTypeHeaders('application/octet-stream'); const multipartFormHeaders = getContentTypeHeaders('multipart/form'); const textPlainHeaders = getContentTypeHeaders('text/plain'); +// node-fetch (like the WHATWG fetch spec) refuses to send a body with GET/HEAD, so we use the raw +// http module to reproduce a GET/HEAD request that arrives with a body (as an upstream/proxy might send). +// See https://github.com/Azure/azure-functions-nodejs-library/issues/458 +function sendRequestWithBody( + url: string, + method: 'GET' | 'HEAD', + body: string +): Promise<{ status: number; body: string }> { + return new Promise((resolve, reject) => { + const parsed = new URL(url); + const req = http.request( + { + hostname: parsed.hostname, + port: parsed.port, + path: `${parsed.pathname}${parsed.search}`, + method, + headers: { + 'content-type': 'text/plain', + 'content-length': Buffer.byteLength(body), + }, + }, + (res) => { + let data = ''; + res.setEncoding('utf8'); + res.on('data', (chunk) => { + data += chunk; + }); + res.on('end', () => resolve({ status: res.statusCode ?? 0, body: data })); + } + ); + req.on('error', reject); + req.write(body); + req.end(); + }); +} + describe('http', () => { afterEach(() => { funcCliSettings.hideOutput = false; @@ -124,6 +161,27 @@ describe('http', () => { } }); + // Regression test for https://github.com/Azure/azure-functions-nodejs-library/issues/458 + // A GET/HEAD request that arrives with a body must reach the handler instead of crashing the worker + // while constructing the WHATWG Request (which rejects bodies for GET/HEAD). The name is passed via + // query so the response is deterministic across models and streaming modes; a status of 200 (rather + // than a 500) confirms the request reached the handler. + describe('GET/HEAD request with a body (issue #458)', () => { + it('GET with a body reaches the handler', async () => { + const url = getFuncUrl('helloWorld', { name: 'getWithBody' }); + const { status, body } = await sendRequestWithBody(url, 'GET', 'this GET body must not crash the worker'); + expect(status).to.equal(200); + expect(body).to.equal('Hello, getWithBody!'); + }); + + it('HEAD with a body reaches the handler', async () => { + const url = getFuncUrl('helloWorld', { name: 'headWithBody' }); + // HEAD responses carry no body, so we only assert the request reached the handler (status 200). + const { status } = await sendRequestWithBody(url, 'HEAD', 'this HEAD body must not crash the worker'); + expect(status).to.equal(200); + }); + }); + // Use a connection pool to avoid flaky test failures due to various connection limits (Mac in particular seems to have a low limit) // NOTE: The node-fetch package has a bug starting in 2.6.8 related to keep alive agents, so we have to use 2.6.7 // https://github.com/node-fetch/node-fetch/issues/1767