From 5a9b7c17d5e8df99582e8ed6589269eb7874ca1e Mon Sep 17 00:00:00 2001 From: DuCanhGH <75556609+DuCanhGH@users.noreply.github.com> Date: Tue, 25 Jul 2023 03:14:24 +0700 Subject: [PATCH 01/20] Better IPv6 support for next-server --- .../js/src/internal/next-request-helpers.ts | 3 +- packages/next/src/build/utils.ts | 8 +----- .../next/src/server/dev/next-dev-server.ts | 2 +- packages/next/src/server/lib/render-server.ts | 6 ++-- .../next/src/server/lib/server-ipc/index.ts | 2 +- .../server/lib/server-ipc/invoke-request.ts | 7 +---- .../src/server/lib/setup-server-worker.ts | 19 ++++++++----- packages/next/src/server/lib/start-server.ts | 26 ++++++----------- packages/next/src/server/next-server.ts | 28 +++++++++++++------ packages/next/src/server/next.ts | 18 ++++++++++-- packages/next/src/server/web/next-url.ts | 2 +- test/unit/web-runtime/next-url.test.ts | 4 +-- 12 files changed, 68 insertions(+), 57 deletions(-) diff --git a/packages/next-swc/crates/next-core/js/src/internal/next-request-helpers.ts b/packages/next-swc/crates/next-core/js/src/internal/next-request-helpers.ts index b05715603dad3..88da4631ffdf9 100644 --- a/packages/next-swc/crates/next-core/js/src/internal/next-request-helpers.ts +++ b/packages/next-swc/crates/next-core/js/src/internal/next-request-helpers.ts @@ -1,4 +1,5 @@ import { TLSSocket } from 'tls' +import { isIPv6 } from 'net' import { addRequestMeta, NextUrlWithParsedQuery, @@ -18,7 +19,7 @@ export function attachRequestMeta( ? 'https' : 'http' - const initUrl = `${protocol}://${host}${req.url}` + const initUrl = `${protocol}://${isIPv6(host) ? `[${host}]` : host}${req.url}` addRequestMeta(req, '__NEXT_INIT_URL', initUrl) addRequestMeta(req, '__NEXT_INIT_QUERY', { ...parsedUrl.query }) diff --git a/packages/next/src/build/utils.ts b/packages/next/src/build/utils.ts index 146c5d732a0b6..311ea162b0dc8 100644 --- a/packages/next/src/build/utils.ts +++ b/packages/next/src/build/utils.ts @@ -1970,17 +1970,11 @@ startServer({ dir, isDev: false, config: nextConfig, - hostname: hostname === 'localhost' ? '0.0.0.0' : hostname, + hostname, port: currentPort, allowRetry: false, keepAliveTimeout, useWorkers: !!nextConfig.experimental?.appDir, -}).then(() => { - console.log( - 'Listening on port', - currentPort, - 'url: http://' + hostname + ':' + currentPort - ) }).catch((err) => { console.error(err); process.exit(1); diff --git a/packages/next/src/server/dev/next-dev-server.ts b/packages/next/src/server/dev/next-dev-server.ts index 2a1d043fbc1c4..56f968a3ff3be 100644 --- a/packages/next/src/server/dev/next-dev-server.ts +++ b/packages/next/src/server/dev/next-dev-server.ts @@ -483,7 +483,7 @@ export default class DevServer extends Server { const ipcKey = process.env.__NEXT_PRIVATE_ROUTER_IPC_KEY if (ipcPort) { const res = await invokeRequest( - `http://${this.hostname}:${ipcPort}?key=${ipcKey}&method=${ + `http://${this.getHostname()}:${ipcPort}?key=${ipcKey}&method=${ method as string }&args=${encodeURIComponent(JSON.stringify(args))}`, { diff --git a/packages/next/src/server/lib/render-server.ts b/packages/next/src/server/lib/render-server.ts index 5df39f0fd5070..4e73125071766 100644 --- a/packages/next/src/server/lib/render-server.ts +++ b/packages/next/src/server/lib/render-server.ts @@ -1,3 +1,5 @@ +import net from 'net' + import type { RequestHandler } from '../next' // this must come first as it includes require hooks @@ -105,7 +107,7 @@ export async function initialize(opts: { ...opts, _routerWorker: opts.workerType === 'router', _renderWorker: opts.workerType === 'render', - hostname: hostname === '0.0.0.0' ? 'localhost' : hostname, + hostname, customServer: false, httpServer: server, port: opts.port, @@ -118,7 +120,7 @@ export async function initialize(opts: { result = { port, - hostname: hostname === '0.0.0.0' ? '127.0.0.1' : hostname, + hostname: net.isIPv6(hostname) ? `[${hostname}]` : hostname, } return result } diff --git a/packages/next/src/server/lib/server-ipc/index.ts b/packages/next/src/server/lib/server-ipc/index.ts index ff61000bb6ab0..71f47d981e2ec 100644 --- a/packages/next/src/server/lib/server-ipc/index.ts +++ b/packages/next/src/server/lib/server-ipc/index.ts @@ -64,7 +64,7 @@ export async function createIpcServer( ) const ipcPort = await new Promise((resolveIpc) => { - ipcServer.listen(0, '0.0.0.0', () => { + ipcServer.listen(0, server.hostname, () => { const addr = ipcServer.address() if (addr && typeof addr === 'object') { diff --git a/packages/next/src/server/lib/server-ipc/invoke-request.ts b/packages/next/src/server/lib/server-ipc/invoke-request.ts index c401f5ac928c2..b0683c72e4131 100644 --- a/packages/next/src/server/lib/server-ipc/invoke-request.ts +++ b/packages/next/src/server/lib/server-ipc/invoke-request.ts @@ -12,17 +12,12 @@ export const invokeRequest = async ( }, readableBody?: Readable | ReadableStream ) => { - // force to 127.0.0.1 as IPC always runs on this hostname - // to avoid localhost issues - const parsedTargetUrl = new URL(targetUrl) - parsedTargetUrl.hostname = '127.0.0.1' - const invokeHeaders = filterReqHeaders({ 'cache-control': '', ...requestInit.headers, }) as IncomingMessage['headers'] - const invokeRes = await fetch(parsedTargetUrl.toString(), { + const invokeRes = await fetch(targetUrl, { headers: invokeHeaders as any as Headers, method: requestInit.method, redirect: 'manual', diff --git a/packages/next/src/server/lib/setup-server-worker.ts b/packages/next/src/server/lib/setup-server-worker.ts index d6977d2530996..6bfcc5eac303c 100644 --- a/packages/next/src/server/lib/setup-server-worker.ts +++ b/packages/next/src/server/lib/setup-server-worker.ts @@ -76,25 +76,30 @@ export async function initializeServerWorker( upgradeHandler(req, socket, upgrade) }) } - const hostname = - !opts.hostname || opts.hostname === 'localhost' - ? '0.0.0.0' - : opts.hostname + let hostname = opts.hostname || 'localhost' server.on('listening', async () => { try { const addr = server.address() + const host = addr + ? typeof addr === 'object' + ? addr.address + : addr + : undefined const port = addr && typeof addr === 'object' ? addr.port : 0 - if (!port) { - console.error(`Invariant failed to detect render worker port`, addr) + if (!port || !host) { + console.error( + `Invariant failed to detect render worker host/port`, + addr + ) process.exit(1) } resolve({ server, port, - hostname, + hostname: host, }) } catch (err) { return reject(err) diff --git a/packages/next/src/server/lib/start-server.ts b/packages/next/src/server/lib/start-server.ts index cb79ca3b83d63..faedbd6dfef18 100644 --- a/packages/next/src/server/lib/start-server.ts +++ b/packages/next/src/server/lib/start-server.ts @@ -85,7 +85,7 @@ export const createRouterWorker = async ( }, }, exposedMethods: ['initialize'], - }) as any as InstanceType & { + }) as InstanceType & { initialize: typeof import('./render-server').initialize } } @@ -201,17 +201,13 @@ export async function startServer({ const addr = server.address() port = typeof addr === 'object' ? addr?.port || port : port - let host = !hostname || hostname === '0.0.0.0' ? 'localhost' : hostname + targetHost = hostname || 'localhost' - let normalizedHostname = hostname || '0.0.0.0' - - if (isIPv6(hostname)) { - host = host === '::' ? '[::1]' : `[${host}]` - normalizedHostname = `[${hostname}]` + if (isIPv6(targetHost)) { + targetHost = `[${targetHost}]` } - targetHost = host - const appUrl = `http://${host}:${port}` + const appUrl = `http://${targetHost}:${port}` if (isNodeDebugging) { const debugPort = getDebugPort() @@ -224,7 +220,7 @@ export async function startServer({ if (logReady) { Log.ready( - `started server on ${normalizedHostname}${ + `started server on ${targetHost}${ (port + '').startsWith(':') ? '' : ':' }${port}, url: ${appUrl}` ) @@ -233,7 +229,7 @@ export async function startServer({ } resolve() }) - server.listen(port, hostname === 'localhost' ? '0.0.0.0' : hostname) + server.listen(port, hostname) }) try { @@ -324,9 +320,7 @@ export async function startServer({ } const getProxyServer = (pathname: string) => { - const targetUrl = `http://${ - targetHost === 'localhost' ? '127.0.0.1' : targetHost - }:${routerPort}${pathname}` + const targetUrl = `http://${targetHost}:${routerPort}${pathname}` const proxyServer = httpProxy.createProxy({ target: targetUrl, changeOrigin: false, @@ -384,9 +378,7 @@ export async function startServer({ compress(req, res, () => {}) } - const targetUrl = `http://${ - targetHost === 'localhost' ? '127.0.0.1' : targetHost - }:${routerPort}${req.url || '/'}` + const targetUrl = `http://${targetHost}:${routerPort}${req.url || '/'}` const invokeRes = await invokeRequest( targetUrl, diff --git a/packages/next/src/server/next-server.ts b/packages/next/src/server/next-server.ts index 57431a8634fab..0f2a5def18efc 100644 --- a/packages/next/src/server/next-server.ts +++ b/packages/next/src/server/next-server.ts @@ -30,6 +30,7 @@ import { renderToHTML, type RenderOpts } from './render' import fs from 'fs' import { join, resolve, isAbsolute } from 'path' +import { isIPv6 } from 'net' import { IncomingMessage, ServerResponse } from 'http' import type { PagesAPIRouteModule } from './future/route-modules/pages-api/module' import { addRequestMeta, getRequestMeta } from './request-meta' @@ -514,9 +515,7 @@ export default class NextNodeServer extends BaseServer { if (this.isRenderWorker) { const invokeRes = await invokeRequest( - `http://${this.hostname || '127.0.0.1'}:${this.port}${ - newReq.url || '' - }`, + `http://${this.getHostname()}:${this.port}${newReq.url || ''}`, { method: newReq.method || 'GET', headers: newReq.headers, @@ -1043,6 +1042,16 @@ export default class NextNodeServer extends BaseServer { return res instanceof ServerResponse ? new NodeNextResponse(res) : res } + // Use this instead of this.hostname for constructing URLs. + protected getHostname(): string { + // Handles IPv6 hostnames and fallbacks to 'localhost' + return this.hostname + ? isIPv6(this.hostname) + ? `[${this.hostname}]` + : this.hostname + : 'localhost' + } + public getRequestHandler(): NodeRequestHandler { // This is just optimization to fire prepare as soon as possible // It will be properly awaited later @@ -1467,11 +1476,12 @@ export default class NextNodeServer extends BaseServer { const query = urlQueryToSearchParams(params.parsed.query).toString() const locale = params.parsed.query.__nextLocale - url = `${getRequestMeta(params.request, '_protocol')}://${ - this.hostname - }:${this.port}${locale ? `/${locale}` : ''}${params.parsed.pathname}${ - query ? `?${query}` : '' - }` + url = `${getRequestMeta( + params.request, + '_protocol' + )}://${this.getHostname()}:${this.port}${locale ? `/${locale}` : ''}${ + params.parsed.pathname + }${query ? `?${query}` : ''}` } if (!url.startsWith('http')) { @@ -1722,7 +1732,7 @@ export default class NextNodeServer extends BaseServer { // When there are hostname and port we build an absolute URL const initUrl = this.hostname && this.port - ? `${protocol}://${this.hostname}:${this.port}${req.url}` + ? `${protocol}://${this.getHostname()}:${this.port}${req.url}` : (this.nextConfig.experimental as any).trustHostHeader ? `https://${req.headers.host || 'localhost'}${req.url}` : req.url diff --git a/packages/next/src/server/next.ts b/packages/next/src/server/next.ts index bb88700717836..77dce3aa4e8ba 100644 --- a/packages/next/src/server/next.ts +++ b/packages/next/src/server/next.ts @@ -302,7 +302,12 @@ function createServer(options: NextServerOptions): NextServer { await proxyRequest( req, socket as any, - url.parse(`http://127.0.0.1:${serverPort}${req.url}`, true), + url.parse( + `http://${req.headers.host || 'localhost'}:${serverPort}${ + req.url + }`, + true + ), head ) } @@ -343,7 +348,9 @@ function createServer(options: NextServerOptions): NextServer { if (shouldUseStandaloneMode) { setupWebSocketHandler(options.httpServer, req) const parsedUrl = url.parse( - `http://127.0.0.1:${serverPort}${req.url}`, + `http://${req.headers.host || 'localhost'}:${serverPort}${ + req.url + }`, true ) if ((req?.socket as TLSSocket)?.encrypted) { @@ -394,7 +401,12 @@ function createServer(options: NextServerOptions): NextServer { await proxyRequest( req, res, - url.parse(`http://127.0.0.1:${serverPort}${req.url}`, true), + url.parse( + `http://${req.headers.host || 'localhost'}:${serverPort}${ + req.url + }`, + true + ), undefined, req ) diff --git a/packages/next/src/server/web/next-url.ts b/packages/next/src/server/web/next-url.ts index a0704a1db9801..b2cfb5e5df3f8 100644 --- a/packages/next/src/server/web/next-url.ts +++ b/packages/next/src/server/web/next-url.ts @@ -20,7 +20,7 @@ interface Options { } const REGEX_LOCALHOST_HOSTNAME = - /(?!^https?:\/\/)(127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}|::1|localhost)/ + /(?!^https?:\/\/)(127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}|\[::1\]|localhost)/ function parseURL(url: string | URL, base?: string | URL) { return new URL( diff --git a/test/unit/web-runtime/next-url.test.ts b/test/unit/web-runtime/next-url.test.ts index 03fd0beaf75e9..8ee079672a4f7 100644 --- a/test/unit/web-runtime/next-url.test.ts +++ b/test/unit/web-runtime/next-url.test.ts @@ -197,12 +197,12 @@ it('consider 127.0.0.1 and variations as localhost', () => { const httpUrl = new NextURL('http://localhost:3000/hello') expect(new NextURL('http://127.0.0.1:3000/hello')).toStrictEqual(httpUrl) expect(new NextURL('http://127.0.1.0:3000/hello')).toStrictEqual(httpUrl) - expect(new NextURL('http://::1:3000/hello')).toStrictEqual(httpUrl) + expect(new NextURL('http://[::1]:3000/hello')).toStrictEqual(httpUrl) const httpsUrl = new NextURL('https://localhost:3000/hello') expect(new NextURL('https://127.0.0.1:3000/hello')).toStrictEqual(httpsUrl) expect(new NextURL('https://127.0.1.0:3000/hello')).toStrictEqual(httpsUrl) - expect(new NextURL('https://::1:3000/hello')).toStrictEqual(httpsUrl) + expect(new NextURL('https://[::1]:3000/hello')).toStrictEqual(httpsUrl) }) it('allows to change the port', () => { From c977667d20b8bfc1e37211a57e97bab1bef89d50 Mon Sep 17 00:00:00 2001 From: DuCanhGH <75556609+DuCanhGH@users.noreply.github.com> Date: Tue, 25 Jul 2023 03:37:17 +0700 Subject: [PATCH 02/20] clean up --- packages/next/src/server/lib/render-server.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/next/src/server/lib/render-server.ts b/packages/next/src/server/lib/render-server.ts index 4e73125071766..c0f1eaa344342 100644 --- a/packages/next/src/server/lib/render-server.ts +++ b/packages/next/src/server/lib/render-server.ts @@ -1,9 +1,8 @@ -import net from 'net' - import type { RequestHandler } from '../next' // this must come first as it includes require hooks import { initializeServerWorker } from './setup-server-worker' +import { isIPv6 } from 'net' import next from '../next' export const WORKER_SELF_EXIT_CODE = 77 @@ -120,7 +119,8 @@ export async function initialize(opts: { result = { port, - hostname: net.isIPv6(hostname) ? `[${hostname}]` : hostname, + hostname: isIPv6(hostname) ? `[${hostname}]` : hostname, } + return result } From 9cf58fd402d95026815bfc156f438c2ad71d4ea2 Mon Sep 17 00:00:00 2001 From: DuCanhGH <75556609+DuCanhGH@users.noreply.github.com> Date: Thu, 27 Jul 2023 19:14:22 +0700 Subject: [PATCH 03/20] clean up branch merging --- packages/next/src/server/lib/render-server.ts | 4 +-- .../server/lib/server-ipc/invoke-request.ts | 2 +- .../server/lib/server-ipc/request-utils.ts | 5 +-- packages/next/src/server/lib/start-server.ts | 8 ++--- packages/next/src/server/lib/utils.ts | 15 +++++++++ packages/next/src/server/next-server.ts | 31 +++++++------------ 6 files changed, 35 insertions(+), 30 deletions(-) diff --git a/packages/next/src/server/lib/render-server.ts b/packages/next/src/server/lib/render-server.ts index c0f1eaa344342..acef575096387 100644 --- a/packages/next/src/server/lib/render-server.ts +++ b/packages/next/src/server/lib/render-server.ts @@ -2,7 +2,7 @@ import type { RequestHandler } from '../next' // this must come first as it includes require hooks import { initializeServerWorker } from './setup-server-worker' -import { isIPv6 } from 'net' +import { formatHostname } from './utils' import next from '../next' export const WORKER_SELF_EXIT_CODE = 77 @@ -119,7 +119,7 @@ export async function initialize(opts: { result = { port, - hostname: isIPv6(hostname) ? `[${hostname}]` : hostname, + hostname: formatHostname(hostname), } return result diff --git a/packages/next/src/server/lib/server-ipc/invoke-request.ts b/packages/next/src/server/lib/server-ipc/invoke-request.ts index bd86d6e72b2f2..997105d779828 100644 --- a/packages/next/src/server/lib/server-ipc/invoke-request.ts +++ b/packages/next/src/server/lib/server-ipc/invoke-request.ts @@ -16,7 +16,7 @@ export const invokeRequest = async ( ...requestInit.headers, }) as IncomingMessage['headers'] - return await fetch(parsedTargetUrl.toString(), { + return await fetch(targetUrl, { headers: invokeHeaders as any as Headers, method: requestInit.method, redirect: 'manual', diff --git a/packages/next/src/server/lib/server-ipc/request-utils.ts b/packages/next/src/server/lib/server-ipc/request-utils.ts index d9c453edaf7c2..7c7c11f238786 100644 --- a/packages/next/src/server/lib/server-ipc/request-utils.ts +++ b/packages/next/src/server/lib/server-ipc/request-utils.ts @@ -1,4 +1,5 @@ import { PageNotFoundError } from '../../../shared/lib/utils' +import { formatHostname } from '../utils' import { invokeRequest } from './invoke-request' export const deserializeErr = (serializedErr: any) => { @@ -29,7 +30,7 @@ export const deserializeErr = (serializedErr: any) => { } export async function invokeIpcMethod({ - hostname = '127.0.0.1', + hostname, method, args, ipcPort, @@ -43,7 +44,7 @@ export async function invokeIpcMethod({ }): Promise { if (ipcPort) { const res = await invokeRequest( - `http://${hostname}:${ipcPort}?key=${ipcKey}&method=${ + `http://${formatHostname(hostname)}:${ipcPort}?key=${ipcKey}&method=${ method as string }&args=${encodeURIComponent(JSON.stringify(args))}`, { diff --git a/packages/next/src/server/lib/start-server.ts b/packages/next/src/server/lib/start-server.ts index cf2f46dbb6da1..d80b87b42250a 100644 --- a/packages/next/src/server/lib/start-server.ts +++ b/packages/next/src/server/lib/start-server.ts @@ -6,7 +6,6 @@ import type { ChildProcess } from 'child_process' import type { NextConfigComplete } from '../config-shared' import http from 'http' -import { isIPv6 } from 'net' import { initialEnv } from '@next/env' import * as Log from '../../build/output/log' import setupDebug from 'next/dist/compiled/debug' @@ -18,6 +17,7 @@ import { normalizeRepeatedSlashes } from '../../shared/lib/utils' import { invokeRequest } from './server-ipc/invoke-request' import { isAbortError, pipeReadable } from '../pipe-readable' import { + formatHostname, genRouterWorkerExecArgv, getDebugPort, getNodeOptionsWithoutInspect, @@ -205,11 +205,7 @@ export async function startServer({ const addr = server.address() port = typeof addr === 'object' ? addr?.port || port : port - targetHost = hostname || 'localhost' - - if (isIPv6(targetHost)) { - targetHost = `[${targetHost}]` - } + targetHost = formatHostname(hostname) const appUrl = `http://${targetHost}:${port}` diff --git a/packages/next/src/server/lib/utils.ts b/packages/next/src/server/lib/utils.ts index 1ffe241e1b24d..22fa3e28e5e38 100644 --- a/packages/next/src/server/lib/utils.ts +++ b/packages/next/src/server/lib/utils.ts @@ -1,3 +1,4 @@ +import { isIPv6 } from 'net' import type arg from 'next/dist/compiled/arg/index.js' import * as Log from '../../build/output/log' @@ -69,3 +70,17 @@ export function getPort(args: arg.Result): number { return 3000 } + +/** + * Formats a hostname so that it is a valid host that can be fetched by wrapping + * IPv6 hosts with brackets. + * @param hostname + * @returns + */ +export function formatHostname(hostname: string | undefined): string { + return hostname + ? isIPv6(hostname) + ? `[${hostname}]` + : hostname + : 'localhost' +} diff --git a/packages/next/src/server/next-server.ts b/packages/next/src/server/next-server.ts index a6763c2395bec..fd885a5eb5c29 100644 --- a/packages/next/src/server/next-server.ts +++ b/packages/next/src/server/next-server.ts @@ -30,7 +30,6 @@ import { renderToHTML, type RenderOpts } from './render' import fs from 'fs' import { join, resolve, isAbsolute } from 'path' -import { isIPv6 } from 'net' import { IncomingMessage, ServerResponse } from 'http' import type { PagesAPIRouteModule } from './future/route-modules/pages-api/module' import { addRequestMeta, getRequestMeta } from './request-meta' @@ -101,6 +100,7 @@ import { NEXT_RSC_UNION_QUERY } from '../client/components/app-router-headers' import { signalFromNodeResponse } from './web/spec-extension/adapters/next-request' import { RouteModuleLoader } from './future/helpers/module-loader/route-module-loader' import { loadManifest } from './load-manifest' +import { formatHostname } from './lib/utils' export * from './base-server' @@ -516,7 +516,9 @@ export default class NextNodeServer extends BaseServer { if (this.isRenderWorker) { const invokeRes = await invokeRequest( - `http://${this.getHostname()}:${this.port}${newReq.url || ''}`, + `http://${formatHostname(this.hostname)}:${this.port}${ + newReq.url || '' + }`, { method: newReq.method || 'GET', headers: newReq.headers, @@ -1044,16 +1046,6 @@ export default class NextNodeServer extends BaseServer { return res instanceof ServerResponse ? new NodeNextResponse(res) : res } - // Use this instead of this.hostname for constructing URLs. - protected getHostname(): string { - // Handles IPv6 hostnames and fallbacks to 'localhost' - return this.hostname - ? isIPv6(this.hostname) - ? `[${this.hostname}]` - : this.hostname - : 'localhost' - } - public getRequestHandler(): NodeRequestHandler { // This is just optimization to fire prepare as soon as possible // It will be properly awaited later @@ -1482,12 +1474,11 @@ export default class NextNodeServer extends BaseServer { const query = urlQueryToSearchParams(params.parsed.query).toString() const locale = params.parsed.query.__nextLocale - url = `${getRequestMeta( - params.request, - '_protocol' - )}://${this.getHostname()}:${this.port}${locale ? `/${locale}` : ''}${ - params.parsed.pathname - }${query ? `?${query}` : ''}` + url = `${getRequestMeta(params.request, '_protocol')}://${formatHostname( + this.hostname + )}:${this.port}${locale ? `/${locale}` : ''}${params.parsed.pathname}${ + query ? `?${query}` : '' + }` } if (!url.startsWith('http')) { @@ -1736,7 +1727,9 @@ export default class NextNodeServer extends BaseServer { // When there are hostname and port we build an absolute URL const initUrl = this.hostname && this.port - ? `${protocol}://${this.getHostname()}:${this.port}${req.url}` + ? `${protocol}://${formatHostname(this.hostname)}:${this.port}${ + req.url + }` : (this.nextConfig.experimental as any).trustHostHeader ? `https://${req.headers.host || 'localhost'}${req.url}` : req.url From 455f3257e223655ea4605158523309879a1cdbf0 Mon Sep 17 00:00:00 2001 From: DuCanhGH <75556609+DuCanhGH@users.noreply.github.com> Date: Thu, 27 Jul 2023 19:19:17 +0700 Subject: [PATCH 04/20] clean up --- packages/next/src/server/next.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/next/src/server/next.ts b/packages/next/src/server/next.ts index 77dce3aa4e8ba..f39b847ec0de5 100644 --- a/packages/next/src/server/next.ts +++ b/packages/next/src/server/next.ts @@ -25,6 +25,7 @@ import { getTracer } from './lib/trace/tracer' import { NextServerSpan } from './lib/trace/constants' import { formatUrl } from '../shared/lib/router/utils/format-url' import { proxyRequest } from './lib/router-utils/proxy-request' +import { formatHostname } from './lib/utils' import { TLSSocket } from 'tls' let ServerImpl: typeof Server @@ -303,7 +304,7 @@ function createServer(options: NextServerOptions): NextServer { req, socket as any, url.parse( - `http://${req.headers.host || 'localhost'}:${serverPort}${ + `http://${formatHostname(options.hostname)}:${serverPort}${ req.url }`, true @@ -348,9 +349,9 @@ function createServer(options: NextServerOptions): NextServer { if (shouldUseStandaloneMode) { setupWebSocketHandler(options.httpServer, req) const parsedUrl = url.parse( - `http://${req.headers.host || 'localhost'}:${serverPort}${ - req.url - }`, + `http://${formatHostname( + options.hostname + )}:${serverPort}${req.url}`, true ) if ((req?.socket as TLSSocket)?.encrypted) { @@ -402,9 +403,9 @@ function createServer(options: NextServerOptions): NextServer { req, res, url.parse( - `http://${req.headers.host || 'localhost'}:${serverPort}${ - req.url - }`, + `http://${formatHostname( + options.hostname + )}:${serverPort}${req.url}`, true ), undefined, From 00a54b9c16b6af8b440878a2feb0344c5e58f0cd Mon Sep 17 00:00:00 2001 From: DuCanhGH <75556609+DuCanhGH@users.noreply.github.com> Date: Sat, 29 Jul 2023 00:39:44 +0700 Subject: [PATCH 05/20] polish --- .../src/server/app-render/action-handler.ts | 20 ++++--------------- packages/next/src/server/base-server.ts | 4 ++++ .../next/src/server/lib/route-resolver.ts | 9 +++++---- packages/next/src/server/lib/router-server.ts | 3 ++- packages/next/src/server/lib/start-server.ts | 7 ++++--- packages/next/src/server/next-server.ts | 17 ++++++---------- packages/next/src/server/next.ts | 18 ++++++++--------- 7 files changed, 33 insertions(+), 45 deletions(-) diff --git a/packages/next/src/server/app-render/action-handler.ts b/packages/next/src/server/app-render/action-handler.ts index df631e1cd6f9d..c841c27c069fb 100644 --- a/packages/next/src/server/app-render/action-handler.ts +++ b/packages/next/src/server/app-render/action-handler.ts @@ -29,6 +29,7 @@ import { getModifiedCookieValues, } from '../web/spec-extension/adapters/request-cookies' import { RequestStore } from '../../client/components/request-async-storage' +import { formatHostname } from '../lib/utils' function nodeToWebReadableStream(nodeReadable: import('stream').Readable) { if (process.env.NEXT_RUNTIME !== 'edge') { @@ -115,22 +116,9 @@ function getForwardedHeaders( return new Headers(mergedHeaders) } -function fetchIPv4v6( - url: URL, - init: RequestInit, - v6 = false -): Promise { - const hostname = url.hostname - - if (!v6 && hostname === 'localhost') { - url.hostname = '127.0.0.1' - } - return fetch(url, init).catch((err) => { - if (err.code === 'ECONNREFUSED' && !v6) { - return fetchIPv4v6(url, init, true) - } - throw err - }) +function fetchIPv4v6(url: URL, init: RequestInit): Promise { + url.hostname = formatHostname(url.hostname) + return fetch(url, init) } async function addRevalidationHeader( diff --git a/packages/next/src/server/base-server.ts b/packages/next/src/server/base-server.ts index 940c70c60bd48..51b0d011a7b12 100644 --- a/packages/next/src/server/base-server.ts +++ b/packages/next/src/server/base-server.ts @@ -34,6 +34,7 @@ import type { WebNextRequest, WebNextResponse } from './base-http/web' import type { PagesAPIRouteMatch } from './future/route-matches/pages-api-route-match' import { format as formatUrl, parse as parseUrl } from 'url' +import { formatHostname } from './lib/utils' import { getRedirectStatus } from '../lib/redirect-status' import { isEdgeRuntime } from '../lib/is-edge-runtime' import { @@ -272,6 +273,7 @@ export default abstract class Server { protected clientReferenceManifest?: ClientReferenceManifest protected nextFontManifest?: NextFontManifest public readonly hostname?: string + public readonly fetchHostname?: string public readonly port?: number protected abstract getPublicDir(): string @@ -372,6 +374,8 @@ export default abstract class Server { // values from causing issues as this can be user provided this.nextConfig = conf as NextConfigComplete this.hostname = hostname + // we format the hostname so that it can be fetched + this.fetchHostname = formatHostname(this.hostname) this.port = port this.distDir = process.env.NEXT_RUNTIME === 'edge' diff --git a/packages/next/src/server/lib/route-resolver.ts b/packages/next/src/server/lib/route-resolver.ts index e74cc02310727..2b1b9a0a0bce7 100644 --- a/packages/next/src/server/lib/route-resolver.ts +++ b/packages/next/src/server/lib/route-resolver.ts @@ -20,6 +20,7 @@ import { splitCookiesString, toNodeOutgoingHttpHeaders } from '../web/utils' import { signalFromNodeResponse } from '../web/spec-extension/adapters/next-request' import { getMiddlewareRouteMatcher } from '../../shared/lib/router/utils/middleware-route-matcher' import { pipeReadable } from '../pipe-readable' +import { formatHostname } from './utils' type RouteResult = | { @@ -68,6 +69,8 @@ export async function makeResolver( dir, !!nextConfig.experimental.appDir ) + // we format the hostname so that it can be fetched + const fetchHostname = formatHostname(serverAddr.hostname) fsChecker.ensureCallback(async (item) => { let result: string | null = null @@ -128,9 +131,7 @@ export async function makeResolver( basePath: nextConfig.basePath, trailingSlash: nextConfig.trailingSlash, }, - url: `http://${serverAddr.hostname || 'localhost'}:${ - serverAddr.port || 3000 - }${req.url}`, + url: `http://${fetchHostname}:${serverAddr.port || 3000}${req.url}`, body: cloneableBody, signal: signalFromNodeResponse(res), }, @@ -202,7 +203,7 @@ export async function makeResolver( async initialize() { return { port: middlewareServerPort, - hostname: '127.0.0.1', + hostname: fetchHostname, } }, async deleteCache() {}, diff --git a/packages/next/src/server/lib/router-server.ts b/packages/next/src/server/lib/router-server.ts index 807d864d07925..2b60f1f25a475 100644 --- a/packages/next/src/server/lib/router-server.ts +++ b/packages/next/src/server/lib/router-server.ts @@ -31,6 +31,7 @@ import { PERMANENT_REDIRECT_STATUS, } from '../../shared/lib/constants' import { signalFromNodeResponse } from '../web/spec-extension/adapters/next-request' +import { formatHostname } from './utils' let initializeResult: | undefined @@ -736,7 +737,7 @@ export async function initialize(opts: { initializeResult = { port, - hostname: hostname === '0.0.0.0' ? '127.0.0.1' : hostname, + hostname: formatHostname(hostname), } return initializeResult diff --git a/packages/next/src/server/lib/start-server.ts b/packages/next/src/server/lib/start-server.ts index 41ce7fae857aa..cb8ef2a051cca 100644 --- a/packages/next/src/server/lib/start-server.ts +++ b/packages/next/src/server/lib/start-server.ts @@ -198,15 +198,16 @@ export async function startServer({ } }) - let targetHost = hostname + let targetHost: string const isNodeDebugging = checkIsNodeDebugging() await new Promise((resolve) => { server.on('listening', () => { const addr = server.address() port = typeof addr === 'object' ? addr?.port || port : port - - targetHost = formatHostname(hostname) + targetHost = formatHostname( + typeof addr === 'object' ? addr?.address || hostname : addr + ) const appUrl = `http://${targetHost}:${port}` diff --git a/packages/next/src/server/next-server.ts b/packages/next/src/server/next-server.ts index fd885a5eb5c29..e63d3d768b7df 100644 --- a/packages/next/src/server/next-server.ts +++ b/packages/next/src/server/next-server.ts @@ -100,7 +100,6 @@ import { NEXT_RSC_UNION_QUERY } from '../client/components/app-router-headers' import { signalFromNodeResponse } from './web/spec-extension/adapters/next-request' import { RouteModuleLoader } from './future/helpers/module-loader/route-module-loader' import { loadManifest } from './load-manifest' -import { formatHostname } from './lib/utils' export * from './base-server' @@ -426,7 +425,7 @@ export default class NextNodeServer extends BaseServer { trustHostHeader: this.nextConfig.experimental.trustHostHeader, allowedRevalidateHeaderKeys: this.nextConfig.experimental.allowedRevalidateHeaderKeys, - hostname: this.hostname, + hostname: this.fetchHostname, minimalMode: this.minimalMode, dev: this.renderOpts.dev === true, query, @@ -516,9 +515,7 @@ export default class NextNodeServer extends BaseServer { if (this.isRenderWorker) { const invokeRes = await invokeRequest( - `http://${formatHostname(this.hostname)}:${this.port}${ - newReq.url || '' - }`, + `http://${this.fetchHostname}:${this.port}${newReq.url || ''}`, { method: newReq.method || 'GET', headers: newReq.headers, @@ -1474,9 +1471,9 @@ export default class NextNodeServer extends BaseServer { const query = urlQueryToSearchParams(params.parsed.query).toString() const locale = params.parsed.query.__nextLocale - url = `${getRequestMeta(params.request, '_protocol')}://${formatHostname( - this.hostname - )}:${this.port}${locale ? `/${locale}` : ''}${params.parsed.pathname}${ + url = `${getRequestMeta(params.request, '_protocol')}://${ + this.fetchHostname + }:${this.port}${locale ? `/${locale}` : ''}${params.parsed.pathname}${ query ? `?${query}` : '' }` } @@ -1727,9 +1724,7 @@ export default class NextNodeServer extends BaseServer { // When there are hostname and port we build an absolute URL const initUrl = this.hostname && this.port - ? `${protocol}://${formatHostname(this.hostname)}:${this.port}${ - req.url - }` + ? `${protocol}://${this.fetchHostname}:${this.port}${req.url}` : (this.nextConfig.experimental as any).trustHostHeader ? `https://${req.headers.host || 'localhost'}${req.url}` : req.url diff --git a/packages/next/src/server/next.ts b/packages/next/src/server/next.ts index f39b847ec0de5..3b858c7d3d0bd 100644 --- a/packages/next/src/server/next.ts +++ b/packages/next/src/server/next.ts @@ -283,6 +283,10 @@ function createServer(options: NextServerOptions): NextServer { let didWebSocketSetup = false let serverPort: number = 0 + const hostname = options.hostname || 'localhost' + // we format the hostname so that it can be fetched + const fetchHostname = formatHostname(hostname) + function setupWebSocketHandler( customServer?: import('http').Server, _req?: IncomingMessage @@ -304,9 +308,7 @@ function createServer(options: NextServerOptions): NextServer { req, socket as any, url.parse( - `http://${formatHostname(options.hostname)}:${serverPort}${ - req.url - }`, + `http://${fetchHostname}:${serverPort}${req.url}`, true ), head @@ -334,7 +336,7 @@ function createServer(options: NextServerOptions): NextServer { const initResult = await routerWorker.initialize({ dir, port: options.port || 3000, - hostname: options.hostname || 'localhost', + hostname, isNodeDebugging: !!isNodeDebugging, workerType: 'router', dev: !!options.dev, @@ -349,9 +351,7 @@ function createServer(options: NextServerOptions): NextServer { if (shouldUseStandaloneMode) { setupWebSocketHandler(options.httpServer, req) const parsedUrl = url.parse( - `http://${formatHostname( - options.hostname - )}:${serverPort}${req.url}`, + `http://${fetchHostname}:${serverPort}${req.url}`, true ) if ((req?.socket as TLSSocket)?.encrypted) { @@ -403,9 +403,7 @@ function createServer(options: NextServerOptions): NextServer { req, res, url.parse( - `http://${formatHostname( - options.hostname - )}:${serverPort}${req.url}`, + `http://${fetchHostname}:${serverPort}${req.url}`, true ), undefined, From 817ef02b3a6fefb97b7fb1e89c527631316b404c Mon Sep 17 00:00:00 2001 From: DuCanhGH <75556609+DuCanhGH@users.noreply.github.com> Date: Sat, 29 Jul 2023 01:32:55 +0700 Subject: [PATCH 06/20] removed hacky fetchIPv4v6 --- packages/next/src/server/app-render/action-handler.ts | 10 ++-------- packages/next/src/server/lib/route-resolver.ts | 10 +++++----- .../next/src/server/lib/router-utils/resolve-routes.ts | 3 ++- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/packages/next/src/server/app-render/action-handler.ts b/packages/next/src/server/app-render/action-handler.ts index c841c27c069fb..5badbbb9beb0e 100644 --- a/packages/next/src/server/app-render/action-handler.ts +++ b/packages/next/src/server/app-render/action-handler.ts @@ -29,7 +29,6 @@ import { getModifiedCookieValues, } from '../web/spec-extension/adapters/request-cookies' import { RequestStore } from '../../client/components/request-async-storage' -import { formatHostname } from '../lib/utils' function nodeToWebReadableStream(nodeReadable: import('stream').Readable) { if (process.env.NEXT_RUNTIME !== 'edge') { @@ -116,11 +115,6 @@ function getForwardedHeaders( return new Headers(mergedHeaders) } -function fetchIPv4v6(url: URL, init: RequestInit): Promise { - url.hostname = formatHostname(url.hostname) - return fetch(url, init) -} - async function addRevalidationHeader( res: ServerResponse, { @@ -189,7 +183,7 @@ async function createRedirectRenderResult( } try { - const headResponse = await fetchIPv4v6(fetchUrl, { + const headResponse = await fetch(fetchUrl, { method: 'HEAD', headers: forwardedHeaders, next: { @@ -201,7 +195,7 @@ async function createRedirectRenderResult( if ( headResponse.headers.get('content-type') === RSC_CONTENT_TYPE_HEADER ) { - const response = await fetchIPv4v6(fetchUrl, { + const response = await fetch(fetchUrl, { method: 'GET', headers: forwardedHeaders, next: { diff --git a/packages/next/src/server/lib/route-resolver.ts b/packages/next/src/server/lib/route-resolver.ts index 2b1b9a0a0bce7..af804e39a1e6f 100644 --- a/packages/next/src/server/lib/route-resolver.ts +++ b/packages/next/src/server/lib/route-resolver.ts @@ -57,7 +57,7 @@ export async function makeResolver( dir: string, nextConfig: NextConfigComplete, middleware: MiddlewareConfig, - serverAddr: Partial + { hostname = 'localhost', port = 3000 }: Partial ) { const fsChecker = await setupFsCheck({ dir, @@ -70,7 +70,7 @@ export async function makeResolver( !!nextConfig.experimental.appDir ) // we format the hostname so that it can be fetched - const fetchHostname = formatHostname(serverAddr.hostname) + const fetchHostname = formatHostname(hostname) fsChecker.ensureCallback(async (item) => { let result: string | null = null @@ -131,7 +131,7 @@ export async function makeResolver( basePath: nextConfig.basePath, trailingSlash: nextConfig.trailingSlash, }, - url: `http://${fetchHostname}:${serverAddr.port || 3000}${req.url}`, + url: `http://${fetchHostname}:${port}${req.url}`, body: cloneableBody, signal: signalFromNodeResponse(res), }, @@ -192,8 +192,8 @@ export async function makeResolver( nextConfig, { dir, - port: serverAddr.port || 3000, - hostname: serverAddr.hostname, + port, + hostname, isNodeDebugging: false, dev: true, workerType: 'render', diff --git a/packages/next/src/server/lib/router-utils/resolve-routes.ts b/packages/next/src/server/lib/router-utils/resolve-routes.ts index 07fdfc4293612..02c56f9cb8958 100644 --- a/packages/next/src/server/lib/router-utils/resolve-routes.ts +++ b/packages/next/src/server/lib/router-utils/resolve-routes.ts @@ -11,6 +11,7 @@ import { getCloneableBody } from '../../body-streams' import { filterReqHeaders } from '../server-ipc/utils' import { Header } from '../../../lib/load-custom-routes' import { stringifyQuery } from '../../server-route-utils' +import { formatHostname } from '../utils' import { toNodeOutgoingHttpHeaders } from '../../web/utils' import { invokeRequest } from '../server-ipc/invoke-request' import { isAbortError } from '../../pipe-readable' @@ -137,7 +138,7 @@ export function getResolveRoutes( const initUrl = (config.experimental as any).trustHostHeader ? `https://${req.headers.host || 'localhost'}${req.url}` : opts.port - ? `${protocol}://${opts.hostname || 'localhost'}:${opts.port}${req.url}` + ? `${protocol}://${formatHostname(opts.hostname)}:${opts.port}${req.url}` : req.url || '' addRequestMeta(req, '__NEXT_INIT_URL', initUrl) From d3a810a9fe51e9ea5a84e27b15497eb81c48fad9 Mon Sep 17 00:00:00 2001 From: DuCanhGH <75556609+DuCanhGH@users.noreply.github.com> Date: Sat, 29 Jul 2023 02:30:23 +0700 Subject: [PATCH 07/20] force hostname to not be undefined in formatHostname turns out that the old behaviour was kinda problematic. --- packages/next/src/server/base-server.ts | 6 ++++-- .../next/src/server/lib/router-utils/resolve-routes.ts | 4 +++- packages/next/src/server/lib/server-ipc/request-utils.ts | 2 +- packages/next/src/server/lib/start-server.ts | 8 ++++---- packages/next/src/server/lib/utils.ts | 8 ++------ packages/next/src/server/next-server.ts | 2 +- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/next/src/server/base-server.ts b/packages/next/src/server/base-server.ts index 51b0d011a7b12..8f62e8737299d 100644 --- a/packages/next/src/server/base-server.ts +++ b/packages/next/src/server/base-server.ts @@ -374,8 +374,10 @@ export default abstract class Server { // values from causing issues as this can be user provided this.nextConfig = conf as NextConfigComplete this.hostname = hostname - // we format the hostname so that it can be fetched - this.fetchHostname = formatHostname(this.hostname) + if (this.hostname) { + // we format the hostname so that it can be fetched + this.fetchHostname = formatHostname(this.hostname) + } this.port = port this.distDir = process.env.NEXT_RUNTIME === 'edge' diff --git a/packages/next/src/server/lib/router-utils/resolve-routes.ts b/packages/next/src/server/lib/router-utils/resolve-routes.ts index 02c56f9cb8958..951d25b951de0 100644 --- a/packages/next/src/server/lib/router-utils/resolve-routes.ts +++ b/packages/next/src/server/lib/router-utils/resolve-routes.ts @@ -138,7 +138,9 @@ export function getResolveRoutes( const initUrl = (config.experimental as any).trustHostHeader ? `https://${req.headers.host || 'localhost'}${req.url}` : opts.port - ? `${protocol}://${formatHostname(opts.hostname)}:${opts.port}${req.url}` + ? `${protocol}://${formatHostname(opts.hostname || 'localhost')}:${ + opts.port + }${req.url}` : req.url || '' addRequestMeta(req, '__NEXT_INIT_URL', initUrl) diff --git a/packages/next/src/server/lib/server-ipc/request-utils.ts b/packages/next/src/server/lib/server-ipc/request-utils.ts index 7c7c11f238786..1f1bba5173e43 100644 --- a/packages/next/src/server/lib/server-ipc/request-utils.ts +++ b/packages/next/src/server/lib/server-ipc/request-utils.ts @@ -30,7 +30,7 @@ export const deserializeErr = (serializedErr: any) => { } export async function invokeIpcMethod({ - hostname, + hostname = 'localhost', method, args, ipcPort, diff --git a/packages/next/src/server/lib/start-server.ts b/packages/next/src/server/lib/start-server.ts index cb8ef2a051cca..208a3c555e505 100644 --- a/packages/next/src/server/lib/start-server.ts +++ b/packages/next/src/server/lib/start-server.ts @@ -32,7 +32,7 @@ export interface StartServerOptions { port: number logReady?: boolean isDev: boolean - hostname: string + hostname: string | undefined useWorkers: boolean allowRetry?: boolean isTurbopack?: boolean @@ -101,7 +101,7 @@ export async function startServer({ prevDir, port, isDev, - hostname, + hostname = 'localhost', useWorkers, minimalMode, allowRetry, @@ -204,12 +204,12 @@ export async function startServer({ await new Promise((resolve) => { server.on('listening', () => { const addr = server.address() - port = typeof addr === 'object' ? addr?.port || port : port targetHost = formatHostname( typeof addr === 'object' ? addr?.address || hostname : addr ) + port = typeof addr === 'object' ? addr?.port || port : port - const appUrl = `http://${targetHost}:${port}` + const appUrl = `http://${formatHostname(hostname)}:${port}` if (isNodeDebugging) { const debugPort = getDebugPort() diff --git a/packages/next/src/server/lib/utils.ts b/packages/next/src/server/lib/utils.ts index 22fa3e28e5e38..8c46f79e382e7 100644 --- a/packages/next/src/server/lib/utils.ts +++ b/packages/next/src/server/lib/utils.ts @@ -77,10 +77,6 @@ export function getPort(args: arg.Result): number { * @param hostname * @returns */ -export function formatHostname(hostname: string | undefined): string { - return hostname - ? isIPv6(hostname) - ? `[${hostname}]` - : hostname - : 'localhost' +export function formatHostname(hostname: string): string { + return isIPv6(hostname) ? `[${hostname}]` : hostname } diff --git a/packages/next/src/server/next-server.ts b/packages/next/src/server/next-server.ts index e63d3d768b7df..ef3f7de10a81f 100644 --- a/packages/next/src/server/next-server.ts +++ b/packages/next/src/server/next-server.ts @@ -1723,7 +1723,7 @@ export default class NextNodeServer extends BaseServer { // When there are hostname and port we build an absolute URL const initUrl = - this.hostname && this.port + this.fetchHostname && this.port ? `${protocol}://${this.fetchHostname}:${this.port}${req.url}` : (this.nextConfig.experimental as any).trustHostHeader ? `https://${req.headers.host || 'localhost'}${req.url}` From 7cefc469b5721c23eb24eaa2fdf9443196f343bd Mon Sep 17 00:00:00 2001 From: DuCanhGH <75556609+DuCanhGH@users.noreply.github.com> Date: Sat, 29 Jul 2023 02:41:08 +0700 Subject: [PATCH 08/20] fallback hostnames --- packages/next/src/server/api-utils/node.ts | 2 +- packages/next/src/server/lib/start-server.ts | 4 ++-- packages/next/src/server/next-server.ts | 4 +++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/next/src/server/api-utils/node.ts b/packages/next/src/server/api-utils/node.ts index 3cac3a8f66fe1..44f36814bc74f 100644 --- a/packages/next/src/server/api-utils/node.ts +++ b/packages/next/src/server/api-utils/node.ts @@ -466,7 +466,7 @@ async function revalidate( const ipcKey = process.env.__NEXT_PRIVATE_ROUTER_IPC_KEY const res = await invokeRequest( `http://${ - context.hostname + context.hostname || 'localhost' }:${ipcPort}?key=${ipcKey}&method=revalidate&args=${encodeURIComponent( JSON.stringify([{ urlPath, revalidateHeaders, opts }]) )}`, diff --git a/packages/next/src/server/lib/start-server.ts b/packages/next/src/server/lib/start-server.ts index 208a3c555e505..dce9b8b26dfef 100644 --- a/packages/next/src/server/lib/start-server.ts +++ b/packages/next/src/server/lib/start-server.ts @@ -205,9 +205,9 @@ export async function startServer({ server.on('listening', () => { const addr = server.address() targetHost = formatHostname( - typeof addr === 'object' ? addr?.address || hostname : addr + addr ? (typeof addr === 'object' ? addr.address : addr) : hostname ) - port = typeof addr === 'object' ? addr?.port || port : port + port = addr ? (typeof addr === 'object' ? addr.port : port) : port const appUrl = `http://${formatHostname(hostname)}:${port}` diff --git a/packages/next/src/server/next-server.ts b/packages/next/src/server/next-server.ts index ef3f7de10a81f..6d3f93d9c2c98 100644 --- a/packages/next/src/server/next-server.ts +++ b/packages/next/src/server/next-server.ts @@ -515,7 +515,9 @@ export default class NextNodeServer extends BaseServer { if (this.isRenderWorker) { const invokeRes = await invokeRequest( - `http://${this.fetchHostname}:${this.port}${newReq.url || ''}`, + `http://${this.fetchHostname || 'localhost'}:${this.port}${ + newReq.url || '' + }`, { method: newReq.method || 'GET', headers: newReq.headers, From 8e383203c747f8d8434036cbfd7c2b7d7447b270 Mon Sep 17 00:00:00 2001 From: DuCanhGH <75556609+DuCanhGH@users.noreply.github.com> Date: Sat, 29 Jul 2023 19:12:55 +0700 Subject: [PATCH 09/20] polish --- .../next/src/server/dev/next-dev-server.ts | 8 +++--- .../next/src/server/lib/route-resolver.ts | 23 +++++++++++----- .../server/lib/server-ipc/request-utils.ts | 7 +++-- packages/next/src/server/lib/start-server.ts | 26 ++++++++++--------- packages/next/src/server/next-server.ts | 2 +- packages/next/src/server/next.ts | 9 +++---- 6 files changed, 42 insertions(+), 33 deletions(-) diff --git a/packages/next/src/server/dev/next-dev-server.ts b/packages/next/src/server/dev/next-dev-server.ts index 80ae75f5a9b47..92a2de43f7276 100644 --- a/packages/next/src/server/dev/next-dev-server.ts +++ b/packages/next/src/server/dev/next-dev-server.ts @@ -487,7 +487,7 @@ export default class DevServer extends Server { ) { if (this.isRenderWorker) { await invokeIpcMethod({ - hostname: this.hostname, + fetchHostname: this.fetchHostname, method: 'logErrorWithOriginalStack', args: [errorToJSON(err as Error), type], ipcPort: process.env.__NEXT_PRIVATE_ROUTER_IPC_PORT, @@ -743,7 +743,7 @@ export default class DevServer extends Server { }) { if (this.isRenderWorker) { await invokeIpcMethod({ - hostname: this.hostname, + fetchHostname: this.fetchHostname, method: 'ensurePage', args: [opts], ipcPort: process.env.__NEXT_PRIVATE_ROUTER_IPC_PORT, @@ -808,7 +808,7 @@ export default class DevServer extends Server { protected async getFallbackErrorComponents(): Promise { if (this.isRenderWorker) { await invokeIpcMethod({ - hostname: this.hostname, + fetchHostname: this.fetchHostname, method: 'getFallbackErrorComponents', args: [], ipcPort: process.env.__NEXT_PRIVATE_ROUTER_IPC_PORT, @@ -824,7 +824,7 @@ export default class DevServer extends Server { async getCompilationError(page: string): Promise { if (this.isRenderWorker) { const err = await invokeIpcMethod({ - hostname: this.hostname, + fetchHostname: this.fetchHostname, method: 'getCompilationError', args: [page], ipcPort: process.env.__NEXT_PRIVATE_ROUTER_IPC_PORT, diff --git a/packages/next/src/server/lib/route-resolver.ts b/packages/next/src/server/lib/route-resolver.ts index af804e39a1e6f..1bd344b99780e 100644 --- a/packages/next/src/server/lib/route-resolver.ts +++ b/packages/next/src/server/lib/route-resolver.ts @@ -17,10 +17,11 @@ import { proxyRequest } from './router-utils/proxy-request' import { getResolveRoutes } from './router-utils/resolve-routes' import { PERMANENT_REDIRECT_STATUS } from '../../shared/lib/constants' import { splitCookiesString, toNodeOutgoingHttpHeaders } from '../web/utils' +import { formatHostname } from './utils' import { signalFromNodeResponse } from '../web/spec-extension/adapters/next-request' import { getMiddlewareRouteMatcher } from '../../shared/lib/router/utils/middleware-route-matcher' +import type { RenderWorker } from './router-server' import { pipeReadable } from '../pipe-readable' -import { formatHostname } from './utils' type RouteResult = | { @@ -111,7 +112,10 @@ export async function makeResolver( } : {} - const middlewareServerPort = await new Promise((resolve) => { + const middlewareServerAddr = await new Promise<{ + hostname: string + port: number + }>((resolve) => { const srv = http.createServer(async (req, res) => { const cloneableBody = getCloneableBody(req) try { @@ -173,7 +177,14 @@ export async function makeResolver( } }) srv.on('listening', () => { - resolve((srv.address() as any).port) + const srvAddr = srv.address() + if (!srvAddr || typeof srvAddr === 'string') { + throw new Error("Failed to determine middleware's host/port.") + } + resolve({ + hostname: srvAddr.address, + port: srvAddr.port, + }) }) srv.listen(0) }) @@ -202,15 +213,15 @@ export async function makeResolver( pages: { async initialize() { return { - port: middlewareServerPort, - hostname: fetchHostname, + port: middlewareServerAddr.port, + hostname: formatHostname(middlewareServerAddr.hostname), } }, async deleteCache() {}, async clearModuleContext() {}, async deleteAppClientCache() {}, async propagateServerField() {}, - } as any, + } as Partial as any, }, {} as any ) diff --git a/packages/next/src/server/lib/server-ipc/request-utils.ts b/packages/next/src/server/lib/server-ipc/request-utils.ts index 1f1bba5173e43..14387229353b8 100644 --- a/packages/next/src/server/lib/server-ipc/request-utils.ts +++ b/packages/next/src/server/lib/server-ipc/request-utils.ts @@ -1,5 +1,4 @@ import { PageNotFoundError } from '../../../shared/lib/utils' -import { formatHostname } from '../utils' import { invokeRequest } from './invoke-request' export const deserializeErr = (serializedErr: any) => { @@ -30,13 +29,13 @@ export const deserializeErr = (serializedErr: any) => { } export async function invokeIpcMethod({ - hostname = 'localhost', + fetchHostname = 'localhost', method, args, ipcPort, ipcKey, }: { - hostname?: string + fetchHostname?: string method: string args: any[] ipcPort?: string @@ -44,7 +43,7 @@ export async function invokeIpcMethod({ }): Promise { if (ipcPort) { const res = await invokeRequest( - `http://${formatHostname(hostname)}:${ipcPort}?key=${ipcKey}&method=${ + `http://${fetchHostname}:${ipcPort}?key=${ipcKey}&method=${ method as string }&args=${encodeURIComponent(JSON.stringify(args))}`, { diff --git a/packages/next/src/server/lib/start-server.ts b/packages/next/src/server/lib/start-server.ts index dce9b8b26dfef..820b9afa3cd7b 100644 --- a/packages/next/src/server/lib/start-server.ts +++ b/packages/next/src/server/lib/start-server.ts @@ -101,7 +101,7 @@ export async function startServer({ prevDir, port, isDev, - hostname = 'localhost', + hostname, useWorkers, minimalMode, allowRetry, @@ -112,6 +112,7 @@ export async function startServer({ }: StartServerOptions): Promise { const sockets = new Set() let worker: import('next/dist/compiled/jest-worker').Worker | undefined + let routerHostname: string | undefined let routerPort: number | undefined let handlersReady = () => {} let handlersError = () => {} @@ -198,18 +199,18 @@ export async function startServer({ } }) - let targetHost: string const isNodeDebugging = checkIsNodeDebugging() await new Promise((resolve) => { server.on('listening', () => { const addr = server.address() - targetHost = formatHostname( - addr ? (typeof addr === 'object' ? addr.address : addr) : hostname + const actualHostname = formatHostname( + typeof addr === 'object' + ? addr?.address || hostname || 'localhost' + : addr ) - port = addr ? (typeof addr === 'object' ? addr.port : port) : port - - const appUrl = `http://${formatHostname(hostname)}:${port}` + const formattedHostname = formatHostname(hostname || 'localhost') + port = typeof addr === 'object' ? addr?.port || port : port if (isNodeDebugging) { const debugPort = getDebugPort() @@ -222,9 +223,7 @@ export async function startServer({ if (logReady) { Log.ready( - `started server on ${targetHost}${ - (port + '').startsWith(':') ? '' : ':' - }${port}, url: ${appUrl}` + `started server on ${actualHostname}:${port}, url: http://${formattedHostname}:${port}` ) // expose the main port to render workers process.env.PORT = port + '' @@ -312,6 +311,7 @@ export async function startServer({ isNodeDebugging: !!isNodeDebugging, keepAliveTimeout, }) + routerHostname = initializeResult.hostname routerPort = initializeResult.port didInitialize = true @@ -322,7 +322,7 @@ export async function startServer({ } const getProxyServer = (pathname: string) => { - const targetUrl = `http://${targetHost}:${routerPort}${pathname}` + const targetUrl = `http://${routerHostname}:${routerPort}${pathname}` const proxyServer = httpProxy.createProxy({ target: targetUrl, changeOrigin: false, @@ -380,7 +380,9 @@ export async function startServer({ compress(req, res, () => {}) } - const targetUrl = `http://${targetHost}:${routerPort}${req.url || '/'}` + const targetUrl = `http://${routerHostname}:${routerPort}${ + req.url || '/' + }` let invokeRes try { diff --git a/packages/next/src/server/next-server.ts b/packages/next/src/server/next-server.ts index 6d3f93d9c2c98..0da9bfc81dd5a 100644 --- a/packages/next/src/server/next-server.ts +++ b/packages/next/src/server/next-server.ts @@ -1727,7 +1727,7 @@ export default class NextNodeServer extends BaseServer { const initUrl = this.fetchHostname && this.port ? `${protocol}://${this.fetchHostname}:${this.port}${req.url}` - : (this.nextConfig.experimental as any).trustHostHeader + : this.nextConfig.experimental.trustHostHeader ? `https://${req.headers.host || 'localhost'}${req.url}` : req.url diff --git a/packages/next/src/server/next.ts b/packages/next/src/server/next.ts index 3b858c7d3d0bd..f8e332f8b7c76 100644 --- a/packages/next/src/server/next.ts +++ b/packages/next/src/server/next.ts @@ -25,7 +25,6 @@ import { getTracer } from './lib/trace/tracer' import { NextServerSpan } from './lib/trace/constants' import { formatUrl } from '../shared/lib/router/utils/format-url' import { proxyRequest } from './lib/router-utils/proxy-request' -import { formatHostname } from './lib/utils' import { TLSSocket } from 'tls' let ServerImpl: typeof Server @@ -282,10 +281,7 @@ function createServer(options: NextServerOptions): NextServer { let didWebSocketSetup = false let serverPort: number = 0 - - const hostname = options.hostname || 'localhost' - // we format the hostname so that it can be fetched - const fetchHostname = formatHostname(hostname) + let fetchHostname: string | undefined function setupWebSocketHandler( customServer?: import('http').Server, @@ -336,12 +332,13 @@ function createServer(options: NextServerOptions): NextServer { const initResult = await routerWorker.initialize({ dir, port: options.port || 3000, - hostname, + hostname: options.hostname || 'localhost', isNodeDebugging: !!isNodeDebugging, workerType: 'router', dev: !!options.dev, minimalMode: options.minimalMode, }) + fetchHostname = initResult.hostname serverPort = initResult.port } case 'getRequestHandler': { From 816d774bb53b96a23bb8689763eab2c2652306cd Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Sun, 13 Aug 2023 11:51:07 +0200 Subject: [PATCH 10/20] Move format-hostname to separate file --- packages/next/src/server/base-server.ts | 2 +- packages/next/src/server/lib/format-hostname.ts | 12 ++++++++++++ packages/next/src/server/lib/render-server.ts | 2 +- packages/next/src/server/lib/route-resolver.ts | 2 +- packages/next/src/server/lib/router-server.ts | 2 +- .../src/server/lib/router-utils/resolve-routes.ts | 2 +- packages/next/src/server/lib/start-server.ts | 3 ++- packages/next/src/server/lib/utils.ts | 11 ----------- 8 files changed, 19 insertions(+), 17 deletions(-) create mode 100644 packages/next/src/server/lib/format-hostname.ts diff --git a/packages/next/src/server/base-server.ts b/packages/next/src/server/base-server.ts index 5cc8709644c65..f1361ed58e259 100644 --- a/packages/next/src/server/base-server.ts +++ b/packages/next/src/server/base-server.ts @@ -36,7 +36,7 @@ import type { } from './future/route-modules/app-route/module' import { format as formatUrl, parse as parseUrl } from 'url' -import { formatHostname } from './lib/utils' +import { formatHostname } from './lib/format-hostname' import { getRedirectStatus } from '../lib/redirect-status' import { isEdgeRuntime } from '../lib/is-edge-runtime' import { diff --git a/packages/next/src/server/lib/format-hostname.ts b/packages/next/src/server/lib/format-hostname.ts new file mode 100644 index 0000000000000..cfbb4c5cd203e --- /dev/null +++ b/packages/next/src/server/lib/format-hostname.ts @@ -0,0 +1,12 @@ +import { isIPv6 } from 'net' + +/** + * Formats a hostname so that it is a valid host that can be fetched by wrapping + * IPv6 hosts with brackets. + * @param hostname + * @returns + */ + +export function formatHostname(hostname: string): string { + return isIPv6(hostname) ? `[${hostname}]` : hostname +} diff --git a/packages/next/src/server/lib/render-server.ts b/packages/next/src/server/lib/render-server.ts index bc599c12f0c7f..398ad9b581d33 100644 --- a/packages/next/src/server/lib/render-server.ts +++ b/packages/next/src/server/lib/render-server.ts @@ -2,7 +2,7 @@ import type { RequestHandler } from '../next' // this must come first as it includes require hooks import { initializeServerWorker } from './setup-server-worker' -import { formatHostname } from './utils' +import { formatHostname } from './format-hostname' import next from '../next' import { PropagateToWorkersField } from './router-utils/types' diff --git a/packages/next/src/server/lib/route-resolver.ts b/packages/next/src/server/lib/route-resolver.ts index 1bd344b99780e..0976284115cd8 100644 --- a/packages/next/src/server/lib/route-resolver.ts +++ b/packages/next/src/server/lib/route-resolver.ts @@ -17,7 +17,7 @@ import { proxyRequest } from './router-utils/proxy-request' import { getResolveRoutes } from './router-utils/resolve-routes' import { PERMANENT_REDIRECT_STATUS } from '../../shared/lib/constants' import { splitCookiesString, toNodeOutgoingHttpHeaders } from '../web/utils' -import { formatHostname } from './utils' +import { formatHostname } from './format-hostname' import { signalFromNodeResponse } from '../web/spec-extension/adapters/next-request' import { getMiddlewareRouteMatcher } from '../../shared/lib/router/utils/middleware-route-matcher' import type { RenderWorker } from './router-server' diff --git a/packages/next/src/server/lib/router-server.ts b/packages/next/src/server/lib/router-server.ts index ca5153429621d..6e357a059245d 100644 --- a/packages/next/src/server/lib/router-server.ts +++ b/packages/next/src/server/lib/router-server.ts @@ -35,7 +35,7 @@ import { PERMANENT_REDIRECT_STATUS, } from '../../shared/lib/constants' import { signalFromNodeResponse } from '../web/spec-extension/adapters/next-request' -import { formatHostname } from './utils' +import { formatHostname } from './format-hostname' const debug = setupDebug('next:router-server:main') diff --git a/packages/next/src/server/lib/router-utils/resolve-routes.ts b/packages/next/src/server/lib/router-utils/resolve-routes.ts index 0e08ad7262845..d24cfd8dbee2d 100644 --- a/packages/next/src/server/lib/router-utils/resolve-routes.ts +++ b/packages/next/src/server/lib/router-utils/resolve-routes.ts @@ -11,7 +11,7 @@ import { getCloneableBody } from '../../body-streams' import { filterReqHeaders, ipcForbiddenHeaders } from '../server-ipc/utils' import { Header } from '../../../lib/load-custom-routes' import { stringifyQuery } from '../../server-route-utils' -import { formatHostname } from '../utils' +import { formatHostname } from '../format-hostname' import { toNodeOutgoingHttpHeaders } from '../../web/utils' import { invokeRequest } from '../server-ipc/invoke-request' import { isAbortError } from '../../pipe-readable' diff --git a/packages/next/src/server/lib/start-server.ts b/packages/next/src/server/lib/start-server.ts index 8d6370afa9f49..cc19329777513 100644 --- a/packages/next/src/server/lib/start-server.ts +++ b/packages/next/src/server/lib/start-server.ts @@ -5,7 +5,8 @@ import type { IncomingMessage, ServerResponse } from 'http' import http from 'http' import * as Log from '../../build/output/log' import setupDebug from 'next/dist/compiled/debug' -import { formatHostname, getDebugPort } from './utils' +import { getDebugPort } from './utils' +import { formatHostname } from './format-hostname' import { initialize } from './router-server' import { WorkerRequestHandler, diff --git a/packages/next/src/server/lib/utils.ts b/packages/next/src/server/lib/utils.ts index b5d2e5f251f07..5c04aa698816b 100644 --- a/packages/next/src/server/lib/utils.ts +++ b/packages/next/src/server/lib/utils.ts @@ -1,4 +1,3 @@ -import { isIPv6 } from 'net' import type arg from 'next/dist/compiled/arg/index.js' export function printAndExit(message: string, code = 1) { @@ -63,13 +62,3 @@ export function getPort(args: arg.Result): number { return 3000 } - -/** - * Formats a hostname so that it is a valid host that can be fetched by wrapping - * IPv6 hosts with brackets. - * @param hostname - * @returns - */ -export function formatHostname(hostname: string): string { - return isIPv6(hostname) ? `[${hostname}]` : hostname -} From 384b89f835540ab0458cc9e96eb9bea6976dba00 Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Sun, 13 Aug 2023 12:04:36 +0200 Subject: [PATCH 11/20] Format hostname using the same function in turbopack --- .../crates/next-core/js/src/internal/next-request-helpers.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/next-swc/crates/next-core/js/src/internal/next-request-helpers.ts b/packages/next-swc/crates/next-core/js/src/internal/next-request-helpers.ts index 88da4631ffdf9..fdbe8a7c41de0 100644 --- a/packages/next-swc/crates/next-core/js/src/internal/next-request-helpers.ts +++ b/packages/next-swc/crates/next-core/js/src/internal/next-request-helpers.ts @@ -7,11 +7,12 @@ import { import { NodeNextRequest } from 'next/dist/server/base-http/node' import { BaseNextRequest } from 'next/dist/server/base-http' import { getCloneableBody } from 'next/dist/server/body-streams' +import { formatHostname } from 'next/dist/server/lib/format-hostname' export function attachRequestMeta( req: BaseNextRequest, parsedUrl: NextUrlWithParsedQuery, - host: string + hostname: string ) { const protocol = ( (req as NodeNextRequest).originalRequest?.socket as TLSSocket @@ -19,7 +20,7 @@ export function attachRequestMeta( ? 'https' : 'http' - const initUrl = `${protocol}://${isIPv6(host) ? `[${host}]` : host}${req.url}` + const initUrl = `${protocol}://${formatHostname(hostname)}${req.url}` addRequestMeta(req, '__NEXT_INIT_URL', initUrl) addRequestMeta(req, '__NEXT_INIT_QUERY', { ...parsedUrl.query }) From da289b2bc7a28836e1893552a6954e8feb56fe06 Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Sun, 13 Aug 2023 12:05:03 +0200 Subject: [PATCH 12/20] Remove unused import --- .../crates/next-core/js/src/internal/next-request-helpers.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/next-swc/crates/next-core/js/src/internal/next-request-helpers.ts b/packages/next-swc/crates/next-core/js/src/internal/next-request-helpers.ts index fdbe8a7c41de0..baf1188b33987 100644 --- a/packages/next-swc/crates/next-core/js/src/internal/next-request-helpers.ts +++ b/packages/next-swc/crates/next-core/js/src/internal/next-request-helpers.ts @@ -1,5 +1,4 @@ import { TLSSocket } from 'tls' -import { isIPv6 } from 'net' import { addRequestMeta, NextUrlWithParsedQuery, From f936a667dd0d7f880833ae9da6ee3a973a4b37f1 Mon Sep 17 00:00:00 2001 From: Zack Tanner Date: Sun, 13 Aug 2023 07:33:55 -0700 Subject: [PATCH 13/20] add isIPv6 implementation from node --- .../next/src/server/lib/format-hostname.ts | 2 +- packages/next/src/server/lib/is-ipv6.ts | 42 +++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 packages/next/src/server/lib/is-ipv6.ts diff --git a/packages/next/src/server/lib/format-hostname.ts b/packages/next/src/server/lib/format-hostname.ts index cfbb4c5cd203e..222146a5a9dd4 100644 --- a/packages/next/src/server/lib/format-hostname.ts +++ b/packages/next/src/server/lib/format-hostname.ts @@ -1,4 +1,4 @@ -import { isIPv6 } from 'net' +import { isIPv6 } from './is-ipv6' /** * Formats a hostname so that it is a valid host that can be fetched by wrapping diff --git a/packages/next/src/server/lib/is-ipv6.ts b/packages/next/src/server/lib/is-ipv6.ts new file mode 100644 index 0000000000000..9cd9213392cd5 --- /dev/null +++ b/packages/next/src/server/lib/is-ipv6.ts @@ -0,0 +1,42 @@ +// Regex from `node/lib/internal/net.js`: https://github.com/nodejs/node/blob/9fc57006c27564ed7f75eee090eca86786508f51/lib/internal/net.js#L19-L29 +// License included below: +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +const v4Seg = '(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])' +const v4Str = `(${v4Seg}[.]){3}${v4Seg}` +const v6Seg = '(?:[0-9a-fA-F]{1,4})' +const IPv6Reg = new RegExp( + '^(' + + `(?:${v6Seg}:){7}(?:${v6Seg}|:)|` + + `(?:${v6Seg}:){6}(?:${v4Str}|:${v6Seg}|:)|` + + `(?:${v6Seg}:){5}(?::${v4Str}|(:${v6Seg}){1,2}|:)|` + + `(?:${v6Seg}:){4}(?:(:${v6Seg}){0,1}:${v4Str}|(:${v6Seg}){1,3}|:)|` + + `(?:${v6Seg}:){3}(?:(:${v6Seg}){0,2}:${v4Str}|(:${v6Seg}){1,4}|:)|` + + `(?:${v6Seg}:){2}(?:(:${v6Seg}){0,3}:${v4Str}|(:${v6Seg}){1,5}|:)|` + + `(?:${v6Seg}:){1}(?:(:${v6Seg}){0,4}:${v4Str}|(:${v6Seg}){1,6}|:)|` + + `(?::((?::${v6Seg}){0,5}:${v4Str}|(?::${v6Seg}){1,7}|:))` + + ')(%[0-9a-zA-Z-.:]{1,})?$' +) + +export function isIPv6(s: string) { + return IPv6Reg.test(s) +} From 0fa8f8dbb97afbe3187128c947f599691e10e559 Mon Sep 17 00:00:00 2001 From: Zack Tanner Date: Sun, 13 Aug 2023 07:40:37 -0700 Subject: [PATCH 14/20] lint --- packages/next/src/server/lib/router-server.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/next/src/server/lib/router-server.ts b/packages/next/src/server/lib/router-server.ts index 6e357a059245d..118712c7adb93 100644 --- a/packages/next/src/server/lib/router-server.ts +++ b/packages/next/src/server/lib/router-server.ts @@ -35,7 +35,6 @@ import { PERMANENT_REDIRECT_STATUS, } from '../../shared/lib/constants' import { signalFromNodeResponse } from '../web/spec-extension/adapters/next-request' -import { formatHostname } from './format-hostname' const debug = setupDebug('next:router-server:main') From befd23208c5873fcd5a738132c5e7ce1f7d647dd Mon Sep 17 00:00:00 2001 From: Zack Tanner Date: Sun, 13 Aug 2023 09:34:36 -0700 Subject: [PATCH 15/20] update formattedHostName & fix a few tests --- packages/next/src/server/lib/start-server.ts | 15 +++++++++++---- test/e2e/app-dir/app/standalone.test.ts | 7 +++++-- test/integration/cli/test/index.test.js | 14 +++++++------- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/packages/next/src/server/lib/start-server.ts b/packages/next/src/server/lib/start-server.ts index cc19329777513..3ed911e454d00 100644 --- a/packages/next/src/server/lib/start-server.ts +++ b/packages/next/src/server/lib/start-server.ts @@ -157,7 +157,16 @@ export async function startServer({ ? addr?.address || hostname || 'localhost' : addr ) - const formattedHostname = formatHostname(hostname || 'localhost') + + const formattedHostname = + !hostname || hostname === '0.0.0.0' + ? 'localhost' + : actualHostname === '[::]' + ? '[::1]' + : actualHostname + + const appUrl = `http://${formattedHostname}:${port}` + port = typeof addr === 'object' ? addr?.port || port : port if (isNodeDebugging) { @@ -170,9 +179,7 @@ export async function startServer({ } if (logReady) { - Log.ready( - `started server on ${actualHostname}:${port}, url: http://${formattedHostname}:${port}` - ) + Log.ready(`started server on ${actualHostname}:${port}, url: ${appUrl}`) // expose the main port to render workers process.env.PORT = port + '' } diff --git a/test/e2e/app-dir/app/standalone.test.ts b/test/e2e/app-dir/app/standalone.test.ts index 8629ff4440ef4..95585ac24f9b0 100644 --- a/test/e2e/app-dir/app/standalone.test.ts +++ b/test/e2e/app-dir/app/standalone.test.ts @@ -67,7 +67,7 @@ if (!(globalThis as any).isNextStart) { const appPort = await findPort() server = await initNextServerScript( testServer, - /Listening on/, + /ready started server on/, { ...process.env, PORT: appPort.toString(), @@ -91,7 +91,10 @@ if (!(globalThis as any).isNextStart) { '/dashboard/project/123', '/catch-all/first', ]) { - const res = await fetchViaHTTP(appPort, testPath) + const res = await fetchViaHTTP(appPort, testPath, undefined, { + // fetchViaHTTP forces IPv4 + agent: null, + }) expect(res.status).toBe(200) } } finally { diff --git a/test/integration/cli/test/index.test.js b/test/integration/cli/test/index.test.js index 66aba3464cb90..9d19da817314a 100644 --- a/test/integration/cli/test/index.test.js +++ b/test/integration/cli/test/index.test.js @@ -363,7 +363,7 @@ describe('CLI Usage', () => { } ) try { - await check(() => output, new RegExp(`on 0.0.0.0:${port}`)) + await check(() => output, new RegExp(`on \\[::\\]:${port}`)) await check(() => output, new RegExp(`http://localhost:${port}`)) } finally { await killApp(app) @@ -383,12 +383,12 @@ describe('CLI Usage', () => { } ) try { - await check(() => output, new RegExp(`on 0.0.0.0:${port}`)) + await check(() => output, new RegExp(`on \\[::\\]:${port}`)) await check(() => output, new RegExp(`http://localhost:${port}`)) } finally { await killApp(app) } - const matches = /on 0.0.0.0:(\d+)/.exec(output) + const matches = /on \[::\]:(\d+)/.exec(output) expect(matches).not.toBe(null) const _port = parseInt(matches[1]) @@ -408,8 +408,8 @@ describe('CLI Usage', () => { }, }) try { - await check(() => output, /on 0.0.0.0:(\d+)/) - const matches = /on 0.0.0.0:(\d+)/.exec(output) + await check(() => output, /on \[::\]:(\d+)/) + const matches = /on \[::\]:(\d+)/.exec(output) const _port = parseInt(matches[1]) expect(matches).not.toBe(null) // Regression test: port 0 was interpreted as if no port had been @@ -434,7 +434,7 @@ describe('CLI Usage', () => { } ) try { - await check(() => output, new RegExp(`on 0.0.0.0:${port}`)) + await check(() => output, new RegExp(`on \\[::\\]:${port}`)) await check(() => output, new RegExp(`http://localhost:${port}`)) } finally { await killApp(app) @@ -451,7 +451,7 @@ describe('CLI Usage', () => { env: { NODE_OPTIONS: '--inspect' }, }) try { - await check(() => output, new RegExp(`on 0.0.0.0:${port}`)) + await check(() => output, new RegExp(`on \\[::\\]:${port}`)) await check(() => output, new RegExp(`http://localhost:${port}`)) } finally { await killApp(app) From fb9e2147e22ec77ccdae8ae58b68677888694e69 Mon Sep 17 00:00:00 2001 From: Zack Tanner Date: Sun, 13 Aug 2023 09:53:26 -0700 Subject: [PATCH 16/20] woops --- packages/next/src/server/lib/start-server.ts | 3 +-- test/e2e/app-dir/app/standalone.test.ts | 7 ++----- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/packages/next/src/server/lib/start-server.ts b/packages/next/src/server/lib/start-server.ts index 3ed911e454d00..193e250854916 100644 --- a/packages/next/src/server/lib/start-server.ts +++ b/packages/next/src/server/lib/start-server.ts @@ -165,9 +165,8 @@ export async function startServer({ ? '[::1]' : actualHostname - const appUrl = `http://${formattedHostname}:${port}` - port = typeof addr === 'object' ? addr?.port || port : port + const appUrl = `http://${formattedHostname}:${port}` if (isNodeDebugging) { const debugPort = getDebugPort() diff --git a/test/e2e/app-dir/app/standalone.test.ts b/test/e2e/app-dir/app/standalone.test.ts index 95585ac24f9b0..f1ef8f10e12b4 100644 --- a/test/e2e/app-dir/app/standalone.test.ts +++ b/test/e2e/app-dir/app/standalone.test.ts @@ -67,7 +67,7 @@ if (!(globalThis as any).isNextStart) { const appPort = await findPort() server = await initNextServerScript( testServer, - /ready started server on/, + /Listening on/, { ...process.env, PORT: appPort.toString(), @@ -91,10 +91,7 @@ if (!(globalThis as any).isNextStart) { '/dashboard/project/123', '/catch-all/first', ]) { - const res = await fetchViaHTTP(appPort, testPath, undefined, { - // fetchViaHTTP forces IPv4 - agent: null, - }) + const res = await fetchViaHTTP(appPort, testPath, {}, {}) expect(res.status).toBe(200) } } finally { From d32784f78839f9a33753155cf301d826b5cc1910 Mon Sep 17 00:00:00 2001 From: Zack Tanner Date: Sun, 13 Aug 2023 09:54:27 -0700 Subject: [PATCH 17/20] undo standalone change --- test/e2e/app-dir/app/standalone.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/app-dir/app/standalone.test.ts b/test/e2e/app-dir/app/standalone.test.ts index f1ef8f10e12b4..f4dd9f4d2f288 100644 --- a/test/e2e/app-dir/app/standalone.test.ts +++ b/test/e2e/app-dir/app/standalone.test.ts @@ -67,7 +67,7 @@ if (!(globalThis as any).isNextStart) { const appPort = await findPort() server = await initNextServerScript( testServer, - /Listening on/, + /ready started server on/, { ...process.env, PORT: appPort.toString(), From 9e51dd907a1aa7026551accb5ea6e16028c930aa Mon Sep 17 00:00:00 2001 From: Zack Tanner Date: Sun, 13 Aug 2023 10:55:04 -0700 Subject: [PATCH 18/20] remove forced ipv4 in fetchViaHTTP & test fixes --- test/e2e/app-dir/app/standalone.test.ts | 2 +- test/lib/next-test-utils.ts | 14 +------------- .../required-server-files-app.test.ts | 2 +- .../required-server-files-i18n.test.ts | 2 +- .../required-server-files.test.ts | 4 ++-- .../standalone-mode/response-cache/index.test.ts | 11 +++++++---- 6 files changed, 13 insertions(+), 22 deletions(-) diff --git a/test/e2e/app-dir/app/standalone.test.ts b/test/e2e/app-dir/app/standalone.test.ts index f4dd9f4d2f288..710249164c385 100644 --- a/test/e2e/app-dir/app/standalone.test.ts +++ b/test/e2e/app-dir/app/standalone.test.ts @@ -91,7 +91,7 @@ if (!(globalThis as any).isNextStart) { '/dashboard/project/123', '/catch-all/first', ]) { - const res = await fetchViaHTTP(appPort, testPath, {}, {}) + const res = await fetchViaHTTP(appPort, testPath) expect(res.status).toBe(200) } } finally { diff --git a/test/lib/next-test-utils.ts b/test/lib/next-test-utils.ts index 9b0a1692c1035..6a077402f0703 100644 --- a/test/lib/next-test-utils.ts +++ b/test/lib/next-test-utils.ts @@ -162,19 +162,7 @@ export function fetchViaHTTP( opts?: RequestInit ): Promise { const url = query ? withQuery(pathname, query) : pathname - return fetch(getFullUrl(appPort, url), { - // in node.js v17 fetch favors IPv6 but Next.js is - // listening on IPv4 by default so force IPv4 DNS resolving - agent: (parsedUrl) => { - if (parsedUrl.protocol === 'https:') { - return new https.Agent({ family: 4 }) - } - if (parsedUrl.protocol === 'http:') { - return new http.Agent({ family: 4 }) - } - }, - ...opts, - }) + return fetch(getFullUrl(appPort, url), opts) } export function renderViaHTTP( diff --git a/test/production/standalone-mode/required-server-files/required-server-files-app.test.ts b/test/production/standalone-mode/required-server-files/required-server-files-app.test.ts index aa0246624f875..a941dfea3c884 100644 --- a/test/production/standalone-mode/required-server-files/required-server-files-app.test.ts +++ b/test/production/standalone-mode/required-server-files/required-server-files-app.test.ts @@ -76,7 +76,7 @@ describe('should set-up next', () => { appPort = await findPort() server = await initNextServerScript( testServer, - /Listening on/, + /ready started server on/, { ...process.env, PORT: appPort, diff --git a/test/production/standalone-mode/required-server-files/required-server-files-i18n.test.ts b/test/production/standalone-mode/required-server-files/required-server-files-i18n.test.ts index 76a77da675e22..4c681f515e7d1 100644 --- a/test/production/standalone-mode/required-server-files/required-server-files-i18n.test.ts +++ b/test/production/standalone-mode/required-server-files/required-server-files-i18n.test.ts @@ -112,7 +112,7 @@ describe('should set-up next', () => { appPort = await findPort() server = await initNextServerScript( testServer, - /Listening on/, + /ready started server on/, { ...process.env, PORT: appPort, diff --git a/test/production/standalone-mode/required-server-files/required-server-files.test.ts b/test/production/standalone-mode/required-server-files/required-server-files.test.ts index 1d66a77308a59..69b62e57bd6ed 100644 --- a/test/production/standalone-mode/required-server-files/required-server-files.test.ts +++ b/test/production/standalone-mode/required-server-files/required-server-files.test.ts @@ -119,7 +119,7 @@ describe('should set-up next', () => { appPort = await findPort() server = await initNextServerScript( testServer, - /Listening on/, + /ready started server on/, { ...process.env, PORT: appPort, @@ -1289,7 +1289,7 @@ describe('should set-up next', () => { appPort = await findPort() server = await initNextServerScript( testServer, - /Listening on/, + /ready started server on/, { ...process.env, PORT: appPort, diff --git a/test/production/standalone-mode/response-cache/index.test.ts b/test/production/standalone-mode/response-cache/index.test.ts index 6c3717da9329f..e7427567c1542 100644 --- a/test/production/standalone-mode/response-cache/index.test.ts +++ b/test/production/standalone-mode/response-cache/index.test.ts @@ -63,7 +63,7 @@ describe('minimal-mode-response-cache', () => { appPort = await findPort() server = await initNextServerScript( testServer, - /Listening on/, + /ready started server on/, { ...process.env, HOSTNAME: '', @@ -141,9 +141,12 @@ describe('minimal-mode-response-cache', () => { expect(res2.headers.get('content-type')).toContain('text/html') }) - it('should have correct "Listening on" log', async () => { - expect(output).toContain(`Listening on port`) - expect(output).toContain(`url: http://localhost:${appPort}`) + it('should have correct "Started server on" log', async () => { + expect(output).toContain(`started server on`) + let pattern = new RegExp( + `url: http://localhost:${appPort}|url: http://\\[::1\\]:${appPort}` + ) + expect(output).toMatch(pattern) }) it('should have correct responses', async () => { From 73e3271aa695bf95525501e45869d69eb33907cc Mon Sep 17 00:00:00 2001 From: Zack Tanner Date: Sun, 13 Aug 2023 11:21:26 -0700 Subject: [PATCH 19/20] more test updates --- test/lib/next-test-utils.ts | 1 - test/production/pnpm-support/index.test.ts | 2 +- test/production/standalone-mode/response-cache/index.test.ts | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/test/lib/next-test-utils.ts b/test/lib/next-test-utils.ts index 6a077402f0703..7da56c88138d6 100644 --- a/test/lib/next-test-utils.ts +++ b/test/lib/next-test-utils.ts @@ -8,7 +8,6 @@ import { } from 'fs' import { promisify } from 'util' import http from 'http' -import https from 'https' import path from 'path' import spawn from 'cross-spawn' diff --git a/test/production/pnpm-support/index.test.ts b/test/production/pnpm-support/index.test.ts index fc0468d4f7234..ca40c4fcd439e 100644 --- a/test/production/pnpm-support/index.test.ts +++ b/test/production/pnpm-support/index.test.ts @@ -103,7 +103,7 @@ describe('pnpm support', () => { ) server = await initNextServerScript( path.join(standaloneDir, 'server.js'), - /Listening/, + /ready started server on/, { ...process.env, PORT: appPort, diff --git a/test/production/standalone-mode/response-cache/index.test.ts b/test/production/standalone-mode/response-cache/index.test.ts index e7427567c1542..d6da0ad2209f8 100644 --- a/test/production/standalone-mode/response-cache/index.test.ts +++ b/test/production/standalone-mode/response-cache/index.test.ts @@ -144,7 +144,7 @@ describe('minimal-mode-response-cache', () => { it('should have correct "Started server on" log', async () => { expect(output).toContain(`started server on`) let pattern = new RegExp( - `url: http://localhost:${appPort}|url: http://\\[::1\\]:${appPort}` + `url: http://localhost:${appPort}|url: http://127.0.0.1:${appPort}|url: http://\\[::1\\]:${appPort}` ) expect(output).toMatch(pattern) }) From 4b379c822c318533f019617a7a12405c4b1fd71b Mon Sep 17 00:00:00 2001 From: Zack Tanner Date: Sun, 13 Aug 2023 11:52:39 -0700 Subject: [PATCH 20/20] missed another spot --- test/production/standalone-mode/type-module/index.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/production/standalone-mode/type-module/index.test.ts b/test/production/standalone-mode/type-module/index.test.ts index 24f79917889ee..8483b082b1d5e 100644 --- a/test/production/standalone-mode/type-module/index.test.ts +++ b/test/production/standalone-mode/type-module/index.test.ts @@ -43,7 +43,7 @@ describe('type-module', () => { const appPort = await findPort() const server = await initNextServerScript( serverFile, - /Listening on/, + /ready started server on/, { ...process.env, PORT: appPort.toString() }, undefined, { cwd: next.testDir }