From 0f332055d43fa97acbb52d7a9c187850bc08073f Mon Sep 17 00:00:00 2001 From: Fernando Dingler Date: Thu, 11 May 2023 10:36:40 -0700 Subject: [PATCH] fix: Standalone node http server starts accepting requests before next 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. (/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 Co-authored-by: JJ Kasper --- packages/next/src/build/utils.ts | 74 ++++++++++++++++---------------- 1 file changed, 38 insertions(+), 36 deletions(-) diff --git a/packages/next/src/build/utils.ts b/packages/next/src/build/utils.ts index 0c144c643a4bf..ee8438ca6179f 100644 --- a/packages/next/src/build/utils.ts +++ b/packages/next/src/build/utils.ts @@ -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); @@ -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); +});` ) }