jhahn · GitHub

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);

Read the original on github.com ↗