Verify canary release
- I verified that the issue exists in the latest Next.js canary release
Provide environment information
Operating System:
Platform: darwin
Arch: arm64
Version: Darwin Kernel Version 21.6.0: Sat Jun 18 17:07:22 PDT 2022; root:xnu-8020.140.41~1/RELEASE_ARM64_T6000
Binaries:
Node: 18.7.0
npm: 8.15.0
Yarn: 1.22.19
pnpm: 7.8.0
Relevant packages:
next: 12.2.4-canary.9
eslint-config-next: N/A
react: 18.2.0
react-dom: 18.2.0
What browser are you using? (if relevant)
N/A
How are you deploying your application? (if relevant)
N/A
Describe the Bug
When attempting to upload a file over a few kilobytes (e.g. sending a POST request with a binary body and a Content-Type of multipart/form-data) via fetch or curl, the request stalls, then fails with the error:
error - Error: aborted
at connResetException (node:internal/errors:704:14)
at abortIncoming (node:_http_server:700:17)
at socketOnClose (node:_http_server:694:3)
at Socket.emit (node:events:525:35)
at TCP.<anonymous> (node:net:757:14) {
middleware: true
}
This occurs only for API pages with…
export const config = { api: { bodyParser: { bodyParser: false, }, }, }
…and only when middleware is present; even something as basic as:
import {NextRequest, NextResponse} from "next/server" export async function middleware(req: NextRequest) { return NextResponse.next() }
Removing the middleware fixes the issue. Of note, very small request bodies (e.g. < 1kb files) work even in the presence of middleware.
Expected Behavior
Sending a POST request to an API endpoint with a Content-Type of multipart/form-data along with a reasonably sized (~200kB) binary payload should work and not stall.
Link to reproduction
https://github.com/jhahn/nextjs-upload-issue
To Reproduce
pages/index.tsx
import type { NextPage } from 'next' import Head from 'next/head' import Image from 'next/image' import styles from '../styles/Home.module.css' const Home: NextPage = () => { const uploadFile = async (files: FileList | null) => { if (!files) return const formData = new FormData() formData.append("file", files[0]) const response = await fetch("/api/hello", { method: "POST", body: formData, }) console.log(await response.json()) } return <input type="file" onChange={(e) => uploadFile(e.target.files)} /> } export default Home
pages/api/hello.ts:
import type { Readable } from 'node:stream'; import type { NextApiRequest, NextApiResponse } from 'next' export const config = { api: { bodyParser: { bodyParser: false, }, }, } async function buffer(readable: Readable) { const chunks = []; for await (const chunk of readable) { chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk); } return Buffer.concat(chunks); } export default async function (req: NextApiRequest, res: NextApiResponse) { if (req.method === 'POST') { const buf = await buffer(req.body); const rawBody = buf.toString('utf8'); // Can do something here... res.json({ rawBody }); } else { res.setHeader('Allow', 'POST'); res.status(405).end('Method Not Allowed'); } }
The code for pages/api/hello.ts was adapted from https://vercel.com/support/articles/how-do-i-get-the-raw-body-of-a-serverless-function. However, I had to change const buf = await buffer(req); to const buf = await buffer(req.body);