diff --git a/src/lib/functions/server.mjs b/src/lib/functions/server.mjs index 552bd54c4c3..cbfbcf3864c 100644 --- a/src/lib/functions/server.mjs +++ b/src/lib/functions/server.mjs @@ -41,6 +41,14 @@ const buildClientContext = function (headers) { } } +const hasBody = (req) => + // copied from is-type package + // eslint-disable-next-line unicorn/prefer-number-properties + (req.header('transfer-encoding') !== undefined || !isNaN(req.header('content-length'))) && + // we expect a string or a buffer, because we use the two bodyParsers(text, raw) from express + // eslint-disable-next-line n/prefer-global/buffer + (typeof req.body === 'string' || Buffer.isBuffer(req.body)) + export const createHandler = function (options) { const { config, functionsRegistry } = options @@ -62,18 +70,21 @@ export const createHandler = function (options) { return } - const isBase64Encoded = shouldBase64Encode(request.headers['content-type']) - const body = request.get('content-length') ? request.body.toString(isBase64Encoded ? 'base64' : 'utf8') : undefined + const isBase64Encoded = shouldBase64Encode(request.header('content-type')) + let body + if (hasBody(request)) { + body = request.body.toString(isBase64Encoded ? 'base64' : 'utf8') + } - let remoteAddress = request.get('x-forwarded-for') || request.connection.remoteAddress || '' + let remoteAddress = request.header('x-forwarded-for') || request.connection.remoteAddress || '' remoteAddress = remoteAddress .split(remoteAddress.includes('.') ? ':' : ',') .pop() .trim() let requestPath = request.path - if (request.get('x-netlify-original-pathname')) { - requestPath = request.get('x-netlify-original-pathname') + if (request.header('x-netlify-original-pathname')) { + requestPath = request.header('x-netlify-original-pathname') delete request.headers['x-netlify-original-pathname'] } const queryParams = Object.entries(request.query).reduce( diff --git a/tests/integration/400.command.dev.test.cjs b/tests/integration/400.command.dev.test.cjs index 695045d2832..1ba6b103a58 100644 --- a/tests/integration/400.command.dev.test.cjs +++ b/tests/integration/400.command.dev.test.cjs @@ -287,6 +287,57 @@ test('should pass body to functions event for POST requests when redirecting', a }) }) +test('should pass body to functions event for POST requests with passthrough edge function', async (t) => { + await withSiteBuilder('site-with-post-echo-function', async (builder) => { + builder + .withNetlifyToml({ + config: { + functions: { directory: 'functions' }, + redirects: [{ from: '/api/*', to: '/.netlify/functions/:splat', status: 200 }], + edge_functions: [ + { + function: 'passthrough', + path: '/*', + }, + ], + }, + }) + .withEdgeFunction({ + name: 'passthrough', + handler: async (_, context) => context.next(), + }) + .withFunction({ + path: 'echo.js', + handler: async (event) => ({ + statusCode: 200, + body: JSON.stringify(event), + }), + }) + + await builder.buildAsync() + + await withDevServer({ cwd: builder.directory }, async (server) => { + const response = await got + .post(`${server.url}/api/echo?ding=dong`, { + headers: { + 'content-type': 'application/x-www-form-urlencoded', + }, + body: 'some=thing', + }) + .json() + + t.is(response.body, 'some=thing') + t.is(response.headers.host, `${server.host}:${server.port}`) + t.is(response.headers['content-type'], 'application/x-www-form-urlencoded') + t.is(response.headers['transfer-encoding'], 'chunked') + t.is(response.httpMethod, 'POST') + t.is(response.isBase64Encoded, false) + t.is(response.path, '/api/echo') + t.deepEqual(response.queryStringParameters, { ding: 'dong' }) + }) + }) +}) + test('should return an empty body for a function with no body when redirecting', async (t) => { await withSiteBuilder('site-with-no-body-function', async (builder) => { builder