Presence of middleware prevents access to raw request bodies greater than or equal to 16,384 bytes (16 KiB)
See original GitHub issueVerify 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);
Issue Analytics
- State:
- Created a year ago
- Reactions:21
- Comments:44 (4 by maintainers)
Top GitHub Comments
Building on the workaround found by @Gawdfrey (🙏), if you simply wanted to exclude all api routes from middleware, seems you could do something like this:
I proposed a solution (#41270). Any collaboration or suggestion is welcome.