next
Version:
The React Framework
597 lines (596 loc) • 29.6 kB
JavaScript
// this must come first as it includes require hooks
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "initialize", {
enumerable: true,
get: function() {
return initialize;
}
});
require("../node-environment");
require("../require-hook");
const _url = /*#__PURE__*/ _interop_require_default(require("url"));
const _path = /*#__PURE__*/ _interop_require_default(require("path"));
const _config = /*#__PURE__*/ _interop_require_default(require("../config"));
const _servestatic = require("../serve-static");
const _debug = /*#__PURE__*/ _interop_require_default(require("next/dist/compiled/debug"));
const _log = /*#__PURE__*/ _interop_require_wildcard(require("../../build/output/log"));
const _utils = require("../../shared/lib/utils");
const _findpagesdir = require("../../lib/find-pages-dir");
const _filesystem = require("./router-utils/filesystem");
const _proxyrequest = require("./router-utils/proxy-request");
const _pipereadable = require("../pipe-readable");
const _resolveroutes = require("./router-utils/resolve-routes");
const _requestmeta = require("../request-meta");
const _pathhasprefix = require("../../shared/lib/router/utils/path-has-prefix");
const _removepathprefix = require("../../shared/lib/router/utils/remove-path-prefix");
const _compression = /*#__PURE__*/ _interop_require_default(require("next/dist/compiled/compression"));
const _baseserver = require("../base-server");
const _nextrequest = require("../web/spec-extension/adapters/next-request");
const _ispostpone = require("./router-utils/is-postpone");
const _parseurl = require("../../shared/lib/router/utils/parse-url");
const _constants = require("../../shared/lib/constants");
const _redirectstatuscode = require("../../client/components/redirect-status-code");
const _devbundlerservice = require("./dev-bundler-service");
const _trace = require("../../trace");
const _ensureleadingslash = require("../../shared/lib/page-path/ensure-leading-slash");
const _getnextpathnameinfo = require("../../shared/lib/router/utils/get-next-pathname-info");
const _gethostname = require("../../shared/lib/get-hostname");
const _detectdomainlocale = require("../../shared/lib/i18n/detect-domain-locale");
const _mockrequest = require("./mock-request");
const _hotreloadertypes = require("../dev/hot-reloader-types");
const _normalizedassetprefix = require("../../shared/lib/normalized-asset-prefix");
const _patchfetch = require("./patch-fetch");
const _utils1 = require("./server-ipc/utils");
const _blockcrosssite = require("./router-utils/block-cross-site");
function _interop_require_default(obj) {
return obj && obj.__esModule ? obj : {
default: obj
};
}
function _getRequireWildcardCache(nodeInterop) {
if (typeof WeakMap !== "function") return null;
var cacheBabelInterop = new WeakMap();
var cacheNodeInterop = new WeakMap();
return (_getRequireWildcardCache = function(nodeInterop) {
return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
})(nodeInterop);
}
function _interop_require_wildcard(obj, nodeInterop) {
if (!nodeInterop && obj && obj.__esModule) {
return obj;
}
if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
return {
default: obj
};
}
var cache = _getRequireWildcardCache(nodeInterop);
if (cache && cache.has(obj)) {
return cache.get(obj);
}
var newObj = {
__proto__: null
};
var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
for(var key in obj){
if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
if (desc && (desc.get || desc.set)) {
Object.defineProperty(newObj, key, desc);
} else {
newObj[key] = obj[key];
}
}
}
newObj.default = obj;
if (cache) {
cache.set(obj, newObj);
}
return newObj;
}
const debug = (0, _debug.default)('next:router-server:main');
const isNextFont = (pathname)=>pathname && /\/media\/[^/]+\.(woff|woff2|eot|ttf|otf)$/.test(pathname);
const requestHandlers = {};
async function initialize(opts) {
if (!process.env.NODE_ENV) {
// @ts-ignore not readonly
process.env.NODE_ENV = opts.dev ? 'development' : 'production';
}
const config = await (0, _config.default)(opts.dev ? _constants.PHASE_DEVELOPMENT_SERVER : _constants.PHASE_PRODUCTION_SERVER, opts.dir, {
silent: false
});
let compress;
if ((config == null ? void 0 : config.compress) !== false) {
compress = (0, _compression.default)();
}
const fsChecker = await (0, _filesystem.setupFsCheck)({
dev: opts.dev,
dir: opts.dir,
config,
minimalMode: opts.minimalMode
});
const renderServer = {};
let developmentBundler;
let devBundlerService;
let originalFetch = globalThis.fetch;
if (opts.dev) {
const { Telemetry } = require('../../telemetry/storage');
const telemetry = new Telemetry({
distDir: _path.default.join(opts.dir, config.distDir)
});
const { pagesDir, appDir } = (0, _findpagesdir.findPagesDir)(opts.dir);
const { setupDevBundler } = require('./router-utils/setup-dev-bundler');
const resetFetch = ()=>{
globalThis.fetch = originalFetch;
globalThis[_patchfetch.NEXT_PATCH_SYMBOL] = false;
};
const setupDevBundlerSpan = opts.startServerSpan ? opts.startServerSpan.traceChild('setup-dev-bundler') : (0, _trace.trace)('setup-dev-bundler');
developmentBundler = await setupDevBundlerSpan.traceAsyncFn(()=>setupDevBundler({
// Passed here but the initialization of this object happens below, doing the initialization before the setupDev call breaks.
renderServer,
appDir,
pagesDir,
telemetry,
fsChecker,
dir: opts.dir,
nextConfig: config,
isCustomServer: opts.customServer,
turbo: !!process.env.TURBOPACK,
port: opts.port,
onDevServerCleanup: opts.onDevServerCleanup,
resetFetch
}));
devBundlerService = new _devbundlerservice.DevBundlerService(developmentBundler, // The request handler is assigned below, this allows us to create a lazy
// reference to it.
(req, res)=>{
return requestHandlers[opts.dir](req, res);
});
}
renderServer.instance = require('./render-server');
const requestHandlerImpl = async (req, res)=>{
// internal headers should not be honored by the request handler
if (!process.env.NEXT_PRIVATE_TEST_HEADERS) {
(0, _utils1.filterInternalHeaders)(req.headers);
}
if (!opts.minimalMode && config.i18n && config.i18n.localeDetection !== false) {
var _this;
const urlParts = (req.url || '').split('?', 1);
let urlNoQuery = urlParts[0] || '';
if (config.basePath) {
urlNoQuery = (0, _removepathprefix.removePathPrefix)(urlNoQuery, config.basePath);
}
const pathnameInfo = (0, _getnextpathnameinfo.getNextPathnameInfo)(urlNoQuery, {
nextConfig: config
});
const domainLocale = (0, _detectdomainlocale.detectDomainLocale)(config.i18n.domains, (0, _gethostname.getHostname)({
hostname: urlNoQuery
}, req.headers));
const defaultLocale = (domainLocale == null ? void 0 : domainLocale.defaultLocale) || config.i18n.defaultLocale;
const { getLocaleRedirect } = require('../../shared/lib/i18n/get-locale-redirect');
const parsedUrl = (0, _parseurl.parseUrl)((_this = req.url || '') == null ? void 0 : _this.replace(/^\/+/, '/'));
const redirect = getLocaleRedirect({
defaultLocale,
domainLocale,
headers: req.headers,
nextConfig: config,
pathLocale: pathnameInfo.locale,
urlParsed: {
...parsedUrl,
pathname: pathnameInfo.locale ? `/${pathnameInfo.locale}${urlNoQuery}` : urlNoQuery
}
});
if (redirect) {
res.setHeader('Location', redirect);
res.statusCode = _redirectstatuscode.RedirectStatusCode.TemporaryRedirect;
res.end(redirect);
return;
}
}
if (compress) {
// @ts-expect-error not express req/res
compress(req, res, ()=>{});
}
req.on('error', (_err)=>{
// TODO: log socket errors?
});
res.on('error', (_err)=>{
// TODO: log socket errors?
});
const invokedOutputs = new Set();
async function invokeRender(parsedUrl, invokePath, handleIndex, additionalRequestMeta) {
var _fsChecker_getMiddlewareMatchers;
// invokeRender expects /api routes to not be locale prefixed
// so normalize here before continuing
if (config.i18n && (0, _removepathprefix.removePathPrefix)(invokePath, config.basePath).startsWith(`/${(0, _requestmeta.getRequestMeta)(req, 'locale')}/api`)) {
invokePath = fsChecker.handleLocale((0, _removepathprefix.removePathPrefix)(invokePath, config.basePath)).pathname;
}
if (req.headers['x-nextjs-data'] && ((_fsChecker_getMiddlewareMatchers = fsChecker.getMiddlewareMatchers()) == null ? void 0 : _fsChecker_getMiddlewareMatchers.length) && (0, _removepathprefix.removePathPrefix)(invokePath, config.basePath) === '/404') {
res.setHeader('x-nextjs-matched-path', parsedUrl.pathname || '');
res.statusCode = 404;
res.setHeader('content-type', 'application/json');
res.end('{}');
return null;
}
if (!handlers) {
throw Object.defineProperty(new Error('Failed to initialize render server'), "__NEXT_ERROR_CODE", {
value: "E90",
enumerable: false,
configurable: true
});
}
(0, _requestmeta.addRequestMeta)(req, 'invokePath', invokePath);
(0, _requestmeta.addRequestMeta)(req, 'invokeQuery', parsedUrl.query);
(0, _requestmeta.addRequestMeta)(req, 'middlewareInvoke', false);
for(const key in additionalRequestMeta || {}){
(0, _requestmeta.addRequestMeta)(req, key, additionalRequestMeta[key]);
}
debug('invokeRender', req.url, req.headers);
try {
var _renderServer_instance;
const initResult = await (renderServer == null ? void 0 : (_renderServer_instance = renderServer.instance) == null ? void 0 : _renderServer_instance.initialize(renderServerOpts));
try {
await (initResult == null ? void 0 : initResult.requestHandler(req, res));
} catch (err) {
if (err instanceof _baseserver.NoFallbackError) {
// eslint-disable-next-line
await handleRequest(handleIndex + 1);
return;
}
throw err;
}
return;
} catch (e) {
// If the client aborts before we can receive a response object (when
// the headers are flushed), then we can early exit without further
// processing.
if ((0, _pipereadable.isAbortError)(e)) {
return;
}
throw e;
}
}
const handleRequest = async (handleIndex)=>{
if (handleIndex > 5) {
throw Object.defineProperty(new Error(`Attempted to handle request too many times ${req.url}`), "__NEXT_ERROR_CODE", {
value: "E283",
enumerable: false,
configurable: true
});
}
// handle hot-reloader first
if (developmentBundler) {
if ((0, _blockcrosssite.blockCrossSite)(req, res, config.allowedDevOrigins, opts.hostname)) {
return;
}
const origUrl = req.url || '/';
if (config.basePath && (0, _pathhasprefix.pathHasPrefix)(origUrl, config.basePath)) {
req.url = (0, _removepathprefix.removePathPrefix)(origUrl, config.basePath);
}
const parsedUrl = _url.default.parse(req.url || '/');
const hotReloaderResult = await developmentBundler.hotReloader.run(req, res, parsedUrl);
if (hotReloaderResult.finished) {
return hotReloaderResult;
}
req.url = origUrl;
}
const { finished, parsedUrl, statusCode, resHeaders, bodyStream, matchedOutput } = await resolveRoutes({
req,
res,
isUpgradeReq: false,
signal: (0, _nextrequest.signalFromNodeResponse)(res),
invokedOutputs
});
if (res.closed || res.finished) {
return;
}
if (developmentBundler && (matchedOutput == null ? void 0 : matchedOutput.type) === 'devVirtualFsItem') {
const origUrl = req.url || '/';
if (config.basePath && (0, _pathhasprefix.pathHasPrefix)(origUrl, config.basePath)) {
req.url = (0, _removepathprefix.removePathPrefix)(origUrl, config.basePath);
}
if (resHeaders) {
for (const key of Object.keys(resHeaders)){
res.setHeader(key, resHeaders[key]);
}
}
const result = await developmentBundler.requestHandler(req, res);
if (result.finished) {
return;
}
// TODO: throw invariant if we resolved to this but it wasn't handled?
req.url = origUrl;
}
debug('requestHandler!', req.url, {
matchedOutput,
statusCode,
resHeaders,
bodyStream: !!bodyStream,
parsedUrl: {
pathname: parsedUrl.pathname,
query: parsedUrl.query
},
finished
});
// apply any response headers from routing
for (const key of Object.keys(resHeaders || {})){
res.setHeader(key, resHeaders[key]);
}
// handle redirect
if (!bodyStream && statusCode && statusCode > 300 && statusCode < 400) {
const destination = _url.default.format(parsedUrl);
res.statusCode = statusCode;
res.setHeader('location', destination);
if (statusCode === _redirectstatuscode.RedirectStatusCode.PermanentRedirect) {
res.setHeader('Refresh', `0;url=${destination}`);
}
return res.end(destination);
}
// handle middleware body response
if (bodyStream) {
res.statusCode = statusCode || 200;
return await (0, _pipereadable.pipeToNodeResponse)(bodyStream, res);
}
if (finished && parsedUrl.protocol) {
var _getRequestMeta;
return await (0, _proxyrequest.proxyRequest)(req, res, parsedUrl, undefined, (_getRequestMeta = (0, _requestmeta.getRequestMeta)(req, 'clonableBody')) == null ? void 0 : _getRequestMeta.cloneBodyStream(), config.experimental.proxyTimeout);
}
if ((matchedOutput == null ? void 0 : matchedOutput.fsPath) && matchedOutput.itemPath) {
if (opts.dev && (fsChecker.appFiles.has(matchedOutput.itemPath) || fsChecker.pageFiles.has(matchedOutput.itemPath))) {
res.statusCode = 500;
const message = `A conflicting public file and page file was found for path ${matchedOutput.itemPath} https://nextjs.org/docs/messages/conflicting-public-file-page`;
await invokeRender(parsedUrl, '/_error', handleIndex, {
invokeStatus: 500,
invokeError: Object.defineProperty(new Error(message), "__NEXT_ERROR_CODE", {
value: "E394",
enumerable: false,
configurable: true
})
});
_log.error(message);
return;
}
if (!res.getHeader('cache-control') && matchedOutput.type === 'nextStaticFolder') {
if (opts.dev && !isNextFont(parsedUrl.pathname)) {
res.setHeader('Cache-Control', 'no-store, must-revalidate');
} else {
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
}
}
if (!(req.method === 'GET' || req.method === 'HEAD')) {
res.setHeader('Allow', [
'GET',
'HEAD'
]);
res.statusCode = 405;
return await invokeRender(_url.default.parse('/405', true), '/405', handleIndex, {
invokeStatus: 405
});
}
try {
return await (0, _servestatic.serveStatic)(req, res, matchedOutput.itemPath, {
root: matchedOutput.itemsRoot,
// Ensures that etags are not generated for static files when disabled.
etag: config.generateEtags
});
} catch (err) {
/**
* Hardcoded every possible error status code that could be thrown by "serveStatic" method
* This is done by searching "this.error" inside "send" module's source code:
* https://github.com/pillarjs/send/blob/master/index.js
* https://github.com/pillarjs/send/blob/develop/index.js
*/ const POSSIBLE_ERROR_CODE_FROM_SERVE_STATIC = new Set([
// send module will throw 500 when header is already sent or fs.stat error happens
// https://github.com/pillarjs/send/blob/53f0ab476145670a9bdd3dc722ab2fdc8d358fc6/index.js#L392
// Note: we will use Next.js built-in 500 page to handle 500 errors
// 500,
// send module will throw 404 when file is missing
// https://github.com/pillarjs/send/blob/53f0ab476145670a9bdd3dc722ab2fdc8d358fc6/index.js#L421
// Note: we will use Next.js built-in 404 page to handle 404 errors
// 404,
// send module will throw 403 when redirecting to a directory without enabling directory listing
// https://github.com/pillarjs/send/blob/53f0ab476145670a9bdd3dc722ab2fdc8d358fc6/index.js#L484
// Note: Next.js throws a different error (without status code) for directory listing
// 403,
// send module will throw 400 when fails to normalize the path
// https://github.com/pillarjs/send/blob/53f0ab476145670a9bdd3dc722ab2fdc8d358fc6/index.js#L520
400,
// send module will throw 412 with conditional GET request
// https://github.com/pillarjs/send/blob/53f0ab476145670a9bdd3dc722ab2fdc8d358fc6/index.js#L632
412,
// send module will throw 416 when range is not satisfiable
// https://github.com/pillarjs/send/blob/53f0ab476145670a9bdd3dc722ab2fdc8d358fc6/index.js#L669
416
]);
let validErrorStatus = POSSIBLE_ERROR_CODE_FROM_SERVE_STATIC.has(err.statusCode);
// normalize non-allowed status codes
if (!validErrorStatus) {
;
err.statusCode = 400;
}
if (typeof err.statusCode === 'number') {
const invokePath = `/${err.statusCode}`;
const invokeStatus = err.statusCode;
res.statusCode = err.statusCode;
return await invokeRender(_url.default.parse(invokePath, true), invokePath, handleIndex, {
invokeStatus
});
}
throw err;
}
}
if (matchedOutput) {
invokedOutputs.add(matchedOutput.itemPath);
return await invokeRender(parsedUrl, parsedUrl.pathname || '/', handleIndex, {
invokeOutput: matchedOutput.itemPath
});
}
// 404 case
res.setHeader('Cache-Control', 'private, no-cache, no-store, max-age=0, must-revalidate');
// Short-circuit favicon.ico serving so that the 404 page doesn't get built as favicon is requested by the browser when loading any route.
if (opts.dev && !matchedOutput && parsedUrl.pathname === '/favicon.ico') {
res.statusCode = 404;
res.end('');
return null;
}
const appNotFound = opts.dev ? developmentBundler == null ? void 0 : developmentBundler.serverFields.hasAppNotFound : await fsChecker.getItem(_constants.UNDERSCORE_NOT_FOUND_ROUTE);
res.statusCode = 404;
if (appNotFound) {
return await invokeRender(parsedUrl, _constants.UNDERSCORE_NOT_FOUND_ROUTE, handleIndex, {
invokeStatus: 404
});
}
await invokeRender(parsedUrl, '/404', handleIndex, {
invokeStatus: 404
});
};
try {
await handleRequest(0);
} catch (err) {
try {
let invokePath = '/500';
let invokeStatus = '500';
if (err instanceof _utils.DecodeError) {
invokePath = '/400';
invokeStatus = '400';
} else {
console.error(err);
}
res.statusCode = Number(invokeStatus);
return await invokeRender(_url.default.parse(invokePath, true), invokePath, 0, {
invokeStatus: res.statusCode
});
} catch (err2) {
console.error(err2);
}
res.statusCode = 500;
res.end('Internal Server Error');
}
};
let requestHandler = requestHandlerImpl;
if (config.experimental.testProxy) {
// Intercept fetch and other testmode apis.
const { wrapRequestHandlerWorker, interceptTestApis } = require('next/dist/experimental/testmode/server');
requestHandler = wrapRequestHandlerWorker(requestHandler);
interceptTestApis();
// We treat the intercepted fetch as "original" fetch that should be reset to during HMR.
originalFetch = globalThis.fetch;
}
requestHandlers[opts.dir] = requestHandler;
const renderServerOpts = {
port: opts.port,
dir: opts.dir,
hostname: opts.hostname,
minimalMode: opts.minimalMode,
dev: !!opts.dev,
server: opts.server,
serverFields: {
...(developmentBundler == null ? void 0 : developmentBundler.serverFields) || {},
setIsrStatus: devBundlerService == null ? void 0 : devBundlerService.setIsrStatus.bind(devBundlerService)
},
experimentalTestProxy: !!config.experimental.testProxy,
experimentalHttpsServer: !!opts.experimentalHttpsServer,
bundlerService: devBundlerService,
startServerSpan: opts.startServerSpan,
quiet: opts.quiet,
onDevServerCleanup: opts.onDevServerCleanup
};
renderServerOpts.serverFields.routerServerHandler = requestHandlerImpl;
// pre-initialize workers
const handlers = await renderServer.instance.initialize(renderServerOpts);
const logError = async (type, err)=>{
if ((0, _ispostpone.isPostpone)(err)) {
// React postpones that are unhandled might end up logged here but they're
// not really errors. They're just part of rendering.
return;
}
if (type === 'unhandledRejection') {
_log.error('unhandledRejection: ', err);
} else if (type === 'uncaughtException') {
_log.error('uncaughtException: ', err);
}
};
process.on('uncaughtException', logError.bind(null, 'uncaughtException'));
process.on('unhandledRejection', logError.bind(null, 'unhandledRejection'));
const resolveRoutes = (0, _resolveroutes.getResolveRoutes)(fsChecker, config, opts, renderServer.instance, renderServerOpts, developmentBundler == null ? void 0 : developmentBundler.ensureMiddleware);
const upgradeHandler = async (req, socket, head)=>{
try {
req.on('error', (_err)=>{
// TODO: log socket errors?
// console.error(_err);
});
socket.on('error', (_err)=>{
// TODO: log socket errors?
// console.error(_err);
});
if (opts.dev && developmentBundler && req.url) {
if ((0, _blockcrosssite.blockCrossSite)(req, socket, config.allowedDevOrigins, opts.hostname)) {
return;
}
const { basePath, assetPrefix } = config;
let hmrPrefix = basePath;
// assetPrefix overrides basePath for HMR path
if (assetPrefix) {
hmrPrefix = (0, _normalizedassetprefix.normalizedAssetPrefix)(assetPrefix);
if (URL.canParse(hmrPrefix)) {
// remove trailing slash from pathname
// return empty string if pathname is '/'
// to avoid conflicts with '/_next' below
hmrPrefix = new URL(hmrPrefix).pathname.replace(/\/$/, '');
}
}
const isHMRRequest = req.url.startsWith((0, _ensureleadingslash.ensureLeadingSlash)(`${hmrPrefix}/_next/webpack-hmr`));
// only handle HMR requests if the basePath in the request
// matches the basePath for the handler responding to the request
if (isHMRRequest) {
return developmentBundler.hotReloader.onHMR(req, socket, head, (client)=>{
client.send(JSON.stringify({
action: _hotreloadertypes.HMR_ACTIONS_SENT_TO_BROWSER.ISR_MANIFEST,
data: (devBundlerService == null ? void 0 : devBundlerService.appIsrManifest) || {}
}));
});
}
}
const res = new _mockrequest.MockedResponse({
resWriter: ()=>{
throw Object.defineProperty(new Error('Invariant: did not expect response writer to be written to for upgrade request'), "__NEXT_ERROR_CODE", {
value: "E522",
enumerable: false,
configurable: true
});
}
});
const { matchedOutput, parsedUrl } = await resolveRoutes({
req,
res,
isUpgradeReq: true,
signal: (0, _nextrequest.signalFromNodeResponse)(socket)
});
// TODO: allow upgrade requests to pages/app paths?
// this was not previously supported
if (matchedOutput) {
return socket.end();
}
if (parsedUrl.protocol) {
return await (0, _proxyrequest.proxyRequest)(req, socket, parsedUrl, head);
}
// If there's no matched output, we don't handle the request as user's
// custom WS server may be listening on the same path.
} catch (err) {
console.error('Error handling upgrade request', err);
socket.end();
}
};
return {
requestHandler,
upgradeHandler,
server: handlers.server,
closeUpgraded () {
var _developmentBundler_hotReloader;
developmentBundler == null ? void 0 : (_developmentBundler_hotReloader = developmentBundler.hotReloader) == null ? void 0 : _developmentBundler_hotReloader.close();
}
};
}
//# sourceMappingURL=router-server.js.map