From 4ed0b78ab662399200db46f777773f8e33bbba65 Mon Sep 17 00:00:00 2001 From: Gal Schlezinger Date: Mon, 20 Jun 2022 12:46:52 +0300 Subject: [PATCH] Allow Edge Functions to receive body (#37822) This PR enables Edge API endpoints to receive a body. This wasn't in the original PR, as thankfully pointed out by @zaiste in [this comment](https://github.com/vercel/next.js/pull/37481#discussion_r899440567) :pray: ## Related - Fixes #37821 ## Bug - [x] Related issues linked using `fixes #number` - [x] Integration tests added - [ ] Errors have helpful link attached, see `contributing.md` --- packages/next/server/body-streams.ts | 2 +- packages/next/server/next-server.ts | 11 ++++-- .../app/pages/api/edge.js | 11 ++++++ .../index.test.ts | 35 +++++++++++++++++++ 4 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 test/e2e/edge-api-endpoints-can-receive-body/app/pages/api/edge.js create mode 100644 test/e2e/edge-api-endpoints-can-receive-body/index.test.ts diff --git a/packages/next/server/body-streams.ts b/packages/next/server/body-streams.ts index 19a3c20c004b9..9f0abca87f594 100644 --- a/packages/next/server/body-streams.ts +++ b/packages/next/server/body-streams.ts @@ -7,7 +7,7 @@ type BodyStream = ReadableStream /** * Creates a ReadableStream from a Node.js HTTP request */ -function requestToBodyStream(request: IncomingMessage): BodyStream { +export function requestToBodyStream(request: IncomingMessage): BodyStream { const transform = new Primitives.TransformStream({ start(controller) { request.on('data', (chunk) => controller.enqueue(chunk)) diff --git a/packages/next/server/next-server.ts b/packages/next/server/next-server.ts index 351d559aa434a..d6f57225c4f19 100644 --- a/packages/next/server/next-server.ts +++ b/packages/next/server/next-server.ts @@ -81,7 +81,11 @@ import { urlQueryToSearchParams } from '../shared/lib/router/utils/querystring' import ResponseCache from '../server/response-cache' import { removeTrailingSlash } from '../shared/lib/router/utils/remove-trailing-slash' import { getNextPathnameInfo } from '../shared/lib/router/utils/get-next-pathname-info' -import { bodyStreamToNodeStream, clonableBodyForRequest } from './body-streams' +import { + bodyStreamToNodeStream, + clonableBodyForRequest, + requestToBodyStream, +} from './body-streams' import { checkIsManualRevalidate } from './api-utils' const shouldUseReactRoot = parseInt(React.version) >= 18 @@ -1495,8 +1499,9 @@ export default class NextNodeServer extends BaseServer { name: params.page, ...(params.params && { params: params.params }), }, - // TODO(gal): complete body - // body: originalBody?.cloneBodyStream(), + body: ['GET', 'HEAD'].includes(params.req.method) + ? undefined + : requestToBodyStream(params.req.originalRequest), }, useCache: !this.nextConfig.experimental.runtime, onWarning: (_warning: Error) => { diff --git a/test/e2e/edge-api-endpoints-can-receive-body/app/pages/api/edge.js b/test/e2e/edge-api-endpoints-can-receive-body/app/pages/api/edge.js new file mode 100644 index 0000000000000..26bcb1402fe01 --- /dev/null +++ b/test/e2e/edge-api-endpoints-can-receive-body/app/pages/api/edge.js @@ -0,0 +1,11 @@ +export default async (req) => { + if (!req.body) { + return new Response('Body is required', { status: 400 }) + } + + return new Response(`got: ${await req.text()}`) +} + +export const config = { + runtime: 'experimental-edge', +} diff --git a/test/e2e/edge-api-endpoints-can-receive-body/index.test.ts b/test/e2e/edge-api-endpoints-can-receive-body/index.test.ts new file mode 100644 index 0000000000000..09d6aee901fb7 --- /dev/null +++ b/test/e2e/edge-api-endpoints-can-receive-body/index.test.ts @@ -0,0 +1,35 @@ +import { createNext, FileRef } from 'e2e-utils' +import { NextInstance } from 'test/lib/next-modes/base' +import { fetchViaHTTP } from 'next-test-utils' +import path from 'path' + +describe('Edge API endpoints can receive body', () => { + let next: NextInstance + + beforeAll(async () => { + next = await createNext({ + files: { + 'pages/api/edge.js': new FileRef( + path.resolve(__dirname, './app/pages/api/edge.js') + ), + }, + dependencies: {}, + }) + }) + afterAll(() => next.destroy()) + + it('reads the body as text', async () => { + const res = await fetchViaHTTP( + next.url, + '/api/edge', + {}, + { + body: 'hello, world.', + method: 'POST', + } + ) + + expect(res.status).toBe(200) + expect(await res.text()).toBe('got: hello, world.') + }) +})