diff --git a/packages/next/server/base-http/node.ts b/packages/next/server/base-http/node.ts index d1d720d809898..5d5c54ce00643 100644 --- a/packages/next/server/base-http/node.ts +++ b/packages/next/server/base-http/node.ts @@ -7,11 +7,6 @@ import { NEXT_REQUEST_META, RequestMeta } from '../request-meta' import { BaseNextRequest, BaseNextResponse } from './index' -type Req = IncomingMessage & { - [NEXT_REQUEST_META]?: RequestMeta - cookies?: NextApiRequestCookies -} - export class NodeNextRequest extends BaseNextRequest { public headers = this._req.headers; @@ -26,11 +21,12 @@ export class NodeNextRequest extends BaseNextRequest { return this._req } - set originalRequest(value: Req) { - this._req = value - } - - constructor(private _req: Req) { + constructor( + private _req: IncomingMessage & { + [NEXT_REQUEST_META]?: RequestMeta + cookies?: NextApiRequestCookies + } + ) { super(_req.method!.toUpperCase(), _req.url!, _req) } diff --git a/packages/next/server/body-streams.ts b/packages/next/server/body-streams.ts deleted file mode 100644 index 5ce9a0b3abde2..0000000000000 --- a/packages/next/server/body-streams.ts +++ /dev/null @@ -1,87 +0,0 @@ -import type { IncomingMessage } from 'http' -import { Readable } from 'stream' -import { TransformStream } from 'next/dist/compiled/web-streams-polyfill' - -type BodyStream = ReadableStream - -/** - * Creates a ReadableStream from a Node.js HTTP request - */ -function requestToBodyStream(request: IncomingMessage): BodyStream { - const transform = new TransformStream({ - start(controller) { - request.on('data', (chunk) => controller.enqueue(chunk)) - request.on('end', () => controller.terminate()) - request.on('error', (err) => controller.error(err)) - }, - }) - - return transform.readable as unknown as ReadableStream -} - -function bodyStreamToNodeStream(bodyStream: BodyStream): Readable { - const reader = bodyStream.getReader() - return Readable.from( - (async function* () { - while (true) { - const { done, value } = await reader.read() - if (done) { - return - } - yield value - } - })() - ) -} - -function replaceRequestBody( - base: T, - stream: Readable -): T { - for (const key in stream) { - let v = stream[key as keyof Readable] as any - if (typeof v === 'function') { - v = v.bind(stream) - } - base[key as keyof T] = v - } - - return base -} - -/** - * An interface that encapsulates body stream cloning - * of an incoming request. - */ -export function clonableBodyForRequest( - incomingMessage: T -) { - let bufferedBodyStream: BodyStream | null = null - - return { - /** - * Replaces the original request body if necessary. - * This is done because once we read the body from the original request, - * we can't read it again. - */ - finalize(): void { - if (bufferedBodyStream) { - replaceRequestBody( - incomingMessage, - bodyStreamToNodeStream(bufferedBodyStream) - ) - } - }, - /** - * Clones the body stream - * to pass into a middleware - */ - cloneBodyStream(): BodyStream { - const originalStream = - bufferedBodyStream ?? requestToBodyStream(incomingMessage) - const [stream1, stream2] = originalStream.tee() - bufferedBodyStream = stream1 - return stream2 - }, - } -} diff --git a/packages/next/server/next-server.ts b/packages/next/server/next-server.ts index b3c1dcb5cb9e9..4c77794818224 100644 --- a/packages/next/server/next-server.ts +++ b/packages/next/server/next-server.ts @@ -38,7 +38,7 @@ import { PagesManifest } from '../build/webpack/plugins/pages-manifest-plugin' import { recursiveReadDirSync } from './lib/recursive-readdir-sync' import { format as formatUrl, UrlWithParsedQuery } from 'url' import compression from 'next/dist/compiled/compression' -import HttpProxy from 'next/dist/compiled/http-proxy' +import Proxy from 'next/dist/compiled/http-proxy' import { route } from './router' import { run } from './web/sandbox' @@ -73,7 +73,6 @@ import { loadEnvConfig } from '@next/env' import { getCustomRoute } from './server-route-utils' import { urlQueryToSearchParams } from '../shared/lib/router/utils/querystring' import ResponseCache from '../server/response-cache' -import { clonableBodyForRequest } from './body-streams' export * from './base-server' @@ -486,7 +485,7 @@ export default class NextNodeServer extends BaseServer { parsedUrl.search = stringifyQuery(req, query) const target = formatUrl(parsedUrl) - const proxy = new HttpProxy({ + const proxy = new Proxy({ target, changeOrigin: true, ignorePath: true, @@ -1237,11 +1236,6 @@ export default class NextNodeServer extends BaseServer { const allHeaders = new Headers() let result: FetchEventResult | null = null - const method = (params.request.method || 'GET').toUpperCase() - let originalBody = - method !== 'GET' && method !== 'HEAD' - ? clonableBodyForRequest(params.request.body) - : undefined for (const middleware of this.middleware || []) { if (middleware.match(params.parsedUrl.pathname)) { @@ -1251,6 +1245,7 @@ export default class NextNodeServer extends BaseServer { } await this.ensureMiddleware(middleware.page, middleware.ssr) + const middlewareInfo = this.getMiddlewareInfo(middleware.page) result = await run({ @@ -1259,7 +1254,7 @@ export default class NextNodeServer extends BaseServer { env: middlewareInfo.env, request: { headers: params.request.headers, - method, + method: params.request.method || 'GET', nextConfig: { basePath: this.nextConfig.basePath, i18n: this.nextConfig.i18n, @@ -1267,7 +1262,6 @@ export default class NextNodeServer extends BaseServer { }, url: url, page: page, - body: originalBody?.cloneBodyStream(), }, useCache: !this.nextConfig.experimental.runtime, onWarning: (warning: Error) => { @@ -1304,8 +1298,6 @@ export default class NextNodeServer extends BaseServer { } } - originalBody?.finalize() - return result } diff --git a/packages/next/server/web/adapter.ts b/packages/next/server/web/adapter.ts index 6252ea738f5cc..ff7f3559453c3 100644 --- a/packages/next/server/web/adapter.ts +++ b/packages/next/server/web/adapter.ts @@ -16,7 +16,6 @@ export async function adapter(params: { page: params.page, input: params.request.url, init: { - body: params.request.body, geo: params.request.geo, headers: fromNodeHeaders(params.request.headers), ip: params.request.ip, diff --git a/packages/next/server/web/types.ts b/packages/next/server/web/types.ts index 029a1024d4620..5a6d48fde63b0 100644 --- a/packages/next/server/web/types.ts +++ b/packages/next/server/web/types.ts @@ -39,7 +39,6 @@ export interface RequestData { params?: { [key: string]: string } } url: string - body?: ReadableStream } export interface FetchEventResult { diff --git a/test/production/reading-request-body-in-middleware/index.test.ts b/test/production/reading-request-body-in-middleware/index.test.ts deleted file mode 100644 index 0f1d61ccfa92e..0000000000000 --- a/test/production/reading-request-body-in-middleware/index.test.ts +++ /dev/null @@ -1,144 +0,0 @@ -import { createNext } from 'e2e-utils' -import { NextInstance } from 'test/lib/next-modes/base' -import { fetchViaHTTP } from 'next-test-utils' - -describe('reading request body in middleware', () => { - let next: NextInstance - - beforeAll(async () => { - next = await createNext({ - files: { - 'pages/_middleware.js': ` - const { NextResponse } = require('next/server'); - - export default async function middleware(request) { - if (!request.body) { - return new Response('No body', { status: 400 }); - } - - const json = await request.json(); - - if (request.nextUrl.searchParams.has("next")) { - const res = NextResponse.next(); - res.headers.set('x-from-root-middleware', '1'); - return res; - } - - return new Response(JSON.stringify({ - root: true, - ...json, - }), { - status: 200, - headers: { - 'content-type': 'application/json', - }, - }) - } - `, - - 'pages/nested/_middleware.js': ` - const { NextResponse } = require('next/server'); - - export default async function middleware(request) { - if (!request.body) { - return new Response('No body', { status: 400 }); - } - - const json = await request.json(); - - return new Response(JSON.stringify({ - root: false, - ...json, - }), { - status: 200, - headers: { - 'content-type': 'application/json', - }, - }) - } - `, - - 'pages/api/hi.js': ` - export default function hi(req, res) { - res.json({ - ...req.body, - api: true, - }) - } - `, - }, - dependencies: {}, - }) - }) - afterAll(() => next.destroy()) - - it('rejects with 400 for get requests', async () => { - const response = await fetchViaHTTP(next.url, '/') - expect(response.status).toEqual(400) - }) - - it('returns root: true for root calls', async () => { - const response = await fetchViaHTTP( - next.url, - '/', - {}, - { - method: 'POST', - body: JSON.stringify({ - foo: 'bar', - }), - } - ) - expect(response.status).toEqual(200) - expect(await response.json()).toEqual({ - foo: 'bar', - root: true, - }) - }) - - it('reads the same body on both middlewares', async () => { - const response = await fetchViaHTTP( - next.url, - '/nested/hello', - { - next: '1', - }, - { - method: 'POST', - body: JSON.stringify({ - foo: 'bar', - }), - } - ) - expect(response.status).toEqual(200) - expect(await response.json()).toEqual({ - foo: 'bar', - root: false, - }) - }) - - it('passes the body to the api endpoint', async () => { - const response = await fetchViaHTTP( - next.url, - '/api/hi', - { - next: '1', - }, - { - method: 'POST', - headers: { - 'content-type': 'application/json', - }, - body: JSON.stringify({ - foo: 'bar', - }), - } - ) - expect(response.status).toEqual(200) - expect(await response.json()).toEqual({ - foo: 'bar', - api: true, - }) - expect(response.headers.get('x-from-root-middleware')).toEqual('1') - }) -}) diff --git a/yarn.lock b/yarn.lock index a5d0874165bc2..af18b6562100c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -20812,7 +20812,8 @@ webpack-bundle-analyzer@4.3.0: source-list-map "^2.0.0" source-map "~0.6.1" -"webpack-sources3@npm:webpack-sources@3.2.3", webpack-sources@^3.2.2, webpack-sources@^3.2.3: +"webpack-sources3@npm:webpack-sources@3.2.3", webpack-sources@^3.2.3: + name webpack-sources3 version "3.2.3" resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==