Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[question] res.setHeader after status(code).json(body) fails with ERR_HTTP_HEADERS_SENT #62

Open
FranciscoKloganB opened this issue Aug 1, 2022 · 2 comments

Comments

@FranciscoKloganB
Copy link

FranciscoKloganB commented Aug 1, 2022

Describe the bug

I have recently started using your middleware package on a personal project and I quite enjoyed following through with the docs and implementing what I needed. At some point, I saw the following example.

// request-tracing.pipe.ts`
export async function requestTracingPipe(
  _req: NextApiRequest,
  res: NextApiResponse,
  next: () => Promise<void>
): Promise<void> {
  res.setHeader("X-Timing-Start", new Date().toISOString())

  await next()

  res.setHeader("X-Response-ID", nanoid())
  res.setHeader("X-Timing-End", new Date().toISOString())
}

However, when trying to setHeader after responding to the client on the API Route, I get the following error:

Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client

I have run the debugger multiple times and res is only called once. Also, if I disable request-tracing.pipe.ts the error stops occurring.

Here is a snippet of the code that runs before the middleware.

async function getHandler(req: NextApiRequest, res: NextApiResponse) {
  const { search } = req.query

  // res.status(200).json(data) executes and the next middleware to execute is `request-tracing.pipe.ts`
  if (search) {
    const data = await forwardGeocode(search as string)
    res.status(200).json(data) 👈
  } else {
    throw new AppError(400, `Query string must define "search" key value pair.`)
  }
}

async function handler(req: NextApiRequest, res: NextApiResponse) {
  if (req.method === "GET") {
    await getHandler(req, res)
  }
}

export default withMiddleware()(METHODS_GUARD)(handler)

Question

Has this happened before? Are there any Next API changes we are not aware of?

Expected behavior

After performing res.status(code).json(body) I expected the res to still pass through request-tracing.pipe.ts before being returned to the client, so that, timing headers can be set. However, this might not be a bug related to next-api-middleware.

Desktop (please complete the following information):

  • OS: Ubuntu
  • Version: 20.04
  • Next 12.2.3
@htunnicliff
Copy link
Owner

Hi @FranciscoKloganB!

After looking into this, I think that my example here is actually a little faulty 🤦

Calling res.json() will call res.send() internally, which means that any headers added after that point are unable to be included in the response.

It sounds like I need to modify or remove this example to reflect header handling properly.

@FranciscoKloganB
Copy link
Author

FranciscoKloganB commented Aug 1, 2022

Thanks for answering so promptly @htunnicliff. Do you have any idea how this example can remain valid or changed to retain functionality even if done in a slightly different manner?

What I have done so far was override the ServerResponse prototype to include a jsonWrite function that does write(json.Stringify(data)) without invoking end like Next's utility function. Then finally, adding the end() on my error.filter.ts after the await next() statement. Noticing that error.filter.ts is which is the first and last middleware to work in the execution chain.

Edit: I can only assume that I either do not understand Express API or Next.js is doing more magic behind the scenes. Following the approach above, I still get the same error, if the request-tracing.pipe.ts is enabled with post-next() call to setHeader.

Scenario 1: do not set the response header on tracing middleware and invoke end on error.filter.ts

Works as expected. Sends the response properly, as if using native implementation. ✔️

Scenario 2: do not set the response header on tracing middleware and not invoke end on error.filter.ts

Works as expected. Does not send the response because end was not invoked. ✔️ :

Scenario 3: set the response header on tracing middleware after doing the write and invoke end on error.filter.ts

Fails with error ERR_HTTP_HEADERS_SENT. Never reaches end statement but somehow get the error saying response was already sent ✖️

😮‍💨

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants