Skip to content

Commit

Permalink
fix: Standalone node http server starts accepting requests before nex…
Browse files Browse the repository at this point in the history
…t handler is ready (#49548)

### Fixing a bug

Fixes #49536
Fixes #49338

### What?
Starting on next.js 13.4.0, the `server.js` file created by the
standalone output is incorrect because it does not wait until the Next
handler is ready before node http server starts accepting requests.

Initial requests will produce the following error:

```
TypeError: handler is not a function
at Server.<anonymous> (/tmp/app/server.js:29:11)
at Server.emit (node:events:513:28)
at parserOnIncoming (node:_http_server:998:12)
at HTTPParser.parserOnHeadersComplete (node:_http_common:128:17)
```

### Why?
The creation of the next server takes roughly 800ms on a basic hello
world app which causes the initial requests to fail because the
`handler` variable is not initialized.

### Solution
This changes the order of code so that Next Server is initialized before
the Node HTTP server and awaits until the promise is completed to kick
off the HTTP server.

### Alternative code

```js
let handler;

const server = http.createServer(async (req, res) => {
  try {
    await waitUntilHandlerIsReady(); // <---- wait here
    await handler(req, res);
  } catch (err) {
    console.error("Failed to call handler", err);
    res.statusCode = 500;
    res.end("Internal Server Error");
  }
});

async function waitUntilHandlerIsReady() {
  if (!handler) {
    await new Promise((resolve) => setTimeout(resolve, 200));
    await waitUntilHandlerIsReady();
  }
}
```

---------

Co-authored-by: Tim Neutkens <tim@timneutkens.nl>
Co-authored-by: JJ Kasper <jj@jjsweb.site>
  • Loading branch information
3 people committed May 11, 2023
1 parent ec6cdb3 commit 0f33205
Showing 1 changed file with 38 additions and 36 deletions.
74 changes: 38 additions & 36 deletions packages/next/src/build/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1939,8 +1939,6 @@ if (!process.env.NEXT_MANUAL_SIG_HANDLE) {
process.on('SIGINT', () => process.exit(0))
}
let handler
const currentPort = parseInt(process.env.PORT, 10) || 3000
const hostname = process.env.HOSTNAME || 'localhost'
const keepAliveTimeout = parseInt(process.env.KEEP_ALIVE_TIMEOUT, 10);
Expand All @@ -1951,42 +1949,46 @@ const nextConfig = ${JSON.stringify({
process.env.__NEXT_PRIVATE_STANDALONE_CONFIG = JSON.stringify(nextConfig)
const server = http.createServer(async (req, res) => {
try {
await handler(req, res)
} catch (err) {
console.error(err);
res.statusCode = 500
res.end('Internal Server Error')
}
})
if (
!Number.isNaN(keepAliveTimeout) &&
Number.isFinite(keepAliveTimeout) &&
keepAliveTimeout >= 0
) {
server.keepAliveTimeout = keepAliveTimeout
}
server.listen(currentPort, async (err) => {
if (err) {
console.error("Failed to start server", err)
process.exit(1)
}
handler = await createServerHandler({
port: currentPort,
hostname,
dir,
conf: nextConfig,
createServerHandler({
port: currentPort,
hostname,
dir,
conf: nextConfig,
}).then((nextHandler) => {
const server = http.createServer(async (req, res) => {
try {
await nextHandler(req, res)
} catch (err) {
console.error(err);
res.statusCode = 500
res.end('Internal Server Error')
}
})
if (
!Number.isNaN(keepAliveTimeout) &&
Number.isFinite(keepAliveTimeout) &&
keepAliveTimeout >= 0
) {
server.keepAliveTimeout = keepAliveTimeout
}
server.listen(currentPort, async (err) => {
if (err) {
console.error("Failed to start server", err)
process.exit(1)
}
console.log(
'Listening on port',
currentPort,
'url: http://' + hostname + ':' + currentPort
)
});
console.log(
'Listening on port',
currentPort,
'url: http://' + hostname + ':' + currentPort
)
})`
}).catch(err => {
console.error(err);
process.exit(1);
});`
)
}

Expand Down

0 comments on commit 0f33205

Please sign in to comment.