next.js icon indicating copy to clipboard operation
next.js copied to clipboard

Fastify: next.js attempts (and fails) to consume a request body that's already been consumed by Fastify

Open chrskrchr opened this issue 4 years ago • 5 comments

What version of Next.js are you using?

10.2.0

What version of Node.js are you using?

14.15.4

What browser are you using?

Chrome

What operating system are you using?

macOS

How are you deploying your application?

custom server

Describe the Bug

When using Fastify as a custom web server, performing a POST to a Next.js /api route results in a 400 Invalid body response.

Expected Behavior

The API handler is allowed to process the request.

To Reproduce

  • Create a Next.js + Fastify app:
    • npx create-next-app --example custom-server-fastify custom-server-fastify-app
  • Create an API route handler at /pages/api/test.js with the following code:
module.exports = (req,res) => {
  const { body } = req;
  res.json({ping: body})
}
  • Run yarn dev to start the server
  • Execute the following curl request
    • curl -v -POST -H "Content-Type: application/json" -d '{"ping":true}' http://localhost:3000/api/test
curl -v -POST -H "Content-Type: application/json" -d '{"ping":true}' http://localhost:3000/api/test
*   Trying ::1...
* TCP_NODELAY set
* Connection failed
* connect to ::1 port 3000 failed: Connection refused
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 3000 (#0)
> POST /api/test HTTP/1.1
> Host: localhost:3000
> User-Agent: curl/7.64.1
> Accept: */*
> Content-Type: application/json
> Content-Length: 13
> 
* upload completely sent off: 13 out of 13 bytes
< HTTP/1.1 400 Invalid body
< Date: Fri, 07 May 2021 15:32:45 GMT
< Connection: keep-alive
< Keep-Alive: timeout=5
< Transfer-Encoding: chunked
< 
* Connection #0 to host localhost left intact
Invalid body* Closing connection 0

chrskrchr avatar May 07 '21 15:05 chrskrchr

This is the same issue described in #9978 that I think was mistakenly marked as fixed. The issue still exists, and we have to work around it by wiring up a no-op content parser in Fastify that disables parsing of the request body on Next.js routes.

It would be great if Next.js had a way to handle this defensively and not attempt to consume a request body that's already been consumed.

chrskrchr avatar May 07 '21 15:05 chrskrchr

This is the same issue described in #9978 that I think was mistakenly marked as fixed. The issue still exists, and we have to work around it by wiring up a no-op content parser in Fastify that disables parsing of the request body on Next.js routes.

It would be great if Next.js had a way to handle this defensively and not attempt to consume a request body that's already been consumed.

~~can you give an example of the no-op you mention?~~

~~I've been trying~~

  app.addContentTypeParser('application/json', { parseAs: 'buffer' }, (_req, body, done) => {
    done(null, body);
  });

~~and~~

  app.addContentTypeParser('application/json', { parseAs: 'string' }, (_req, body, done) => {
    done(null, body);
  });

~~and it doesn't work~~

EDIT:

This works:

app.addContentTypeParser('application/json', (_req, body, done) => {
    done(null, body);
});

PabloSzx avatar May 20 '21 06:05 PabloSzx

Right - in your first example, the { parseAs: '...' } triggered Fastify to consume the body prior to passing it to your handler, which mean that Next.js would continue to fail since the body had already been consumed.

For posterity - our async no-op handler looks like this:

  async function noOpParser(req: FastifyRequest, payload: IncomingMessage) {
    return payload;
  }

  fastifyInstance.addContentTypeParser('text/plain', noOpParser);
  fastifyInstance.addContentTypeParser('application/json', noOpParser);

chrskrchr avatar May 20 '21 12:05 chrskrchr

Do maintainers understand the problem or are they already working on it? cc/ @Timer

felippem avatar Oct 25 '21 02:10 felippem

Hey. I'm interested to take on this task.

There's this statement that is raised by @chrskrchr that is:

"... It would be great if Next.js had a way to handle this defensively and not attempt to consume a request body that's already been consumed."

I wonder whether I can have a bit of a clue over where does Next.js store their content parsing and I want to see what I can do there.

Or, probably whether it's the decision of the maintainer over which this isn't handled defensively for reasons, I can then help with modifying the fastify example!

Thanks so much!

cc @timneutkens

MarioSerano avatar Sep 19 '22 15:09 MarioSerano

I will take a look the example given

rubiagatra avatar Dec 12 '22 07:12 rubiagatra

I think we should close this issue. @Timer @chrskrchr

➜ npx create-next-app --example custom-server-fastify custom-server-fastify-app
Could not locate an example named "custom-server-fastify". It could be due to the following:
 1. Your spelling of example "custom-server-fastify" might be incorrect.
 2. You might not be connected to the internet or you are behind a proxy.

rubiagatra avatar Dec 12 '22 07:12 rubiagatra

@rubiagatra The custom server examples were removed in https://github.com/vercel/next.js/commit/984627a65ed3e8fcb001d886bd25e96fcff47466 but you can still do

npx create-next-app@latest --example https://github.com/vercel/next.js/tree/v12.3.4/examples/custom-server-fastify custom-server-fastify-app

seanparmelee avatar Dec 12 '22 17:12 seanparmelee

I've updated the issue's description with modern repro steps using node@16, next@13, and fastify@4.

chrskrchr avatar Dec 14 '22 13:12 chrskrchr

looking for a solution as well

nclsjstnn avatar Oct 17 '23 01:10 nclsjstnn

Ran into this problem today, unable to use a POST request if it contains any sort of body. 400 Invalid Body Adding a noop content parser for application/json fixed it for me. Looking for a permanent solution though.

archcorsair avatar Oct 20 '23 01:10 archcorsair