Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

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

[Hapi + Nextjs] custom-server-hapi got a [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client #19004

Closed
shockw4ver opened this issue Nov 10, 2020 · 4 comments
Labels
examples Issue/PR related to examples good first issue Easy to fix issues, good for newcomers
Milestone

Comments

@shockw4ver
Copy link

Bug report

Describe the bug

I started the custom-server-hapi project and try to visit /a, the page could response correctly but the server output an error UnhandledPromiseRejectionWarning [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client.

To Reproduce

Steps to reproduce the behavior, please provide code snippets or a repository:

  1. Start server (https://github.com/vercel/next.js/tree/canary/examples/custom-server-hapi)
  2. Try localhost:3000/a
  3. The page response correctly
  4. The terminal showed [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client, and repeat a lot

Expected behavior

Page can be responsed correctly and no error in server output.

Screenshots

Page in browser:
image

Server output:
image

System information

  • OS: macOS 10.15.7
  • Browser: Chrome
  • Version of Next.js: tried 9.5.5 or canary
  • Version of Node.js: 14.14.1

Additional context

Love Nextjs!

@shockw4ver shockw4ver added the bug Issue was opened via the bug report template. label Nov 10, 2020
@shockw4ver
Copy link
Author

shockw4ver commented Nov 10, 2020

Now I found if I write a delay between h and next handler those warning will disappear.

Just like:
next-wrapper.js

function delay(ms) {
  return new Promise((ok, bad) => {
    setTimeout(() => ok, ms)
  })
}

const nextHandlerWrapper = (app) => {
  const handler = app.getRequestHandler()
  return async ({ raw, url }, h) => {
    await handler(raw.req, raw.res, url)
    await delay(0)

    return h.close
  }
}

const pathWrapper = (app, pathName, opts) => async (
  { raw, query, params },
  h
) => {
  const html = await app.render(
    raw.req,
    raw.res,
    pathName,
    { ...query, ...params },
    opts
  )

  await delay(0)
  return h.response(html).code(raw.res.statusCode)
}

module.exports = {
  pathWrapper,
  nextHandlerWrapper,
}

Is there any conflicts related to event loop between next handler and hapi?

Additionally, it was happened to stuck while request page file without the "0ms delay",
image
this cause route cannot change as expected, but also worked fine according to the "0ms delay"...
image

@gorbatiukcom
Copy link

I have the same problem

@timneutkens timneutkens added good first issue Easy to fix issues, good for newcomers examples Issue/PR related to examples and removed bug Issue was opened via the bug report template. labels Nov 24, 2020
@timneutkens timneutkens added this to the backlog milestone Nov 24, 2020
@mashaalmemon
Copy link

Same problem here.

@shockw4ver
Copy link
Author

Now I found the probable reason:

  1. The workflow of app.render function would end with sendPayload method which called res.end ,this tells node the response has been fininshed.
export function sendPayload(
  req: IncomingMessage,
  res: ServerResponse,
  payload: any,
  type: 'html' | 'json',
  {
    generateEtags,
    poweredByHeader,
  }: { generateEtags: boolean; poweredByHeader: boolean },
  options?: PayloadOptions
): void {
  if (isResSent(res)) {
    return
  }

  if (poweredByHeader && type === 'html') {
    res.setHeader('X-Powered-By', 'Next.js')
  }

  const etag = generateEtags ? generateETag(payload) : undefined
  if (sendEtagResponse(req, res, etag)) {
    return
  }

  if (!res.getHeader('Content-Type')) {
    res.setHeader(
      'Content-Type',
      type === 'json' ? 'application/json' : 'text/html; charset=utf-8'
    )
  }
  res.setHeader('Content-Length', Buffer.byteLength(payload))
  if (options != null) {
    setRevalidateHeaders(res, options)
  }
  res.end(req.method === 'HEAD' ? null : payload)
}
  1. But the lifecycle of hapi still got something to do, so hapi continue to use the response instance for finally work.
  2. Obviously using an expired response is invalid, error occured.

We can return h.abandon to avoid this error:

const pathWrapper = (app, pathName, opts) => async (
  { raw, query, params },
  h
) => {
  await app.render(
    raw.req,
    raw.res,
    pathName,
    { ...query, ...params },
    opts
  )

  return h.abandon
}

No need for delay(0) call.

But this is still not felt like a smooth way...
Any more graceful idea?

@vercel vercel locked and limited conversation to collaborators Apr 17, 2024
@balazsorban44 balazsorban44 converted this issue into discussion #64654 Apr 17, 2024

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
examples Issue/PR related to examples good first issue Easy to fix issues, good for newcomers
Projects
None yet
Development

No branches or pull requests

4 participants