UNPKG

16.3 kB JavaScript View Raw
1/**
2 * react-router v8.5.1
3 *
4 * Copyright (c) Remix Software Inc.
5 *
6 * This source code is licensed under the MIT license found in the
7 * LICENSE.md file in the root directory of this source tree.
8 *
9 * @license MIT
10 */
11import { ErrorResponseImpl, RouterContextProvider, createDataFunctionUrl, defaultMapRouteProperties, getRoutePattern, isRouteErrorResponse, removeTrailingSlash, stripBasename } from "../router/utils.js";
12import { instrumentHandler, instrumentationResultMetaContext } from "../router/instrumentation.js";
13import { createStaticHandler, getStaticContextFromError, isMutationMethod, isRedirectResponse, isResponse } from "../router/router.js";
14import { getManifestPath, isFogOfWarEnabled, shouldUseInitialRouteDiscovery } from "../dom/ssr/fog-of-war.js";
15import { createEntryRouteModules } from "./entry.js";
16import { isServerMode } from "./mode.js";
17import { sanitizeErrors, serializeError } from "./errors.js";
18import { matchServerRoutes } from "./routeMatching.js";
19import { getBuildTimeHeader, getDevServerHooks } from "./dev.js";
20import { createStaticHandlerDataRoutes } from "./routes.js";
21import { createServerHandoffString } from "./serverHandoff.js";
22import { getDocumentHeaders } from "./headers.js";
23import { throwIfPotentialCSRFAttack } from "../actions.js";
24import { getNormalizedPath } from "./urls.js";
25import { SERVER_NO_BODY_STATUS_CODES, encodeViaTurboStream, generateSingleFetchRedirectResponse, singleFetchAction, singleFetchLoaders } from "./single-fetch.js";
26//#region lib/server-runtime/server.ts
27function derive(build, mode) {
28 let dataRoutes = createStaticHandlerDataRoutes(build.routes);
29 let serverMode = isServerMode(mode) ? mode : "production";
30 let staticHandler = createStaticHandler(dataRoutes, {
31 basename: build.basename,
32 mapRouteProperties: defaultMapRouteProperties,
33 instrumentations: build.entry.module.instrumentations,
34 future: build.future
35 });
36 let errorHandler = build.entry.module.handleError || ((error, { request }) => {
37 if (serverMode !== "test" && !request.signal.aborted) console.error(isRouteErrorResponse(error) && error.error ? error.error : error);
38 });
39 let requestHandlerInstrumentations = build.entry.module.instrumentations?.map((i) => i.handler).filter(Boolean);
40 let requestHandler = async (request, initialContext) => {
41 let params = {};
42 let loadContext;
43 let handleError = (error) => {
44 if (mode === "development") getDevServerHooks()?.processRequestError?.(error);
45 errorHandler(error, {
46 context: loadContext,
47 params,
48 request
49 });
50 };
51 if (initialContext && !(initialContext instanceof RouterContextProvider)) {
52 let error = /* @__PURE__ */ new Error("Invalid `context` value provided to `handleRequest`. You must return an instance of `RouterContextProvider` from your `getLoadContext` function.");
53 handleError(error);
54 return returnLastResortErrorResponse(error, serverMode);
55 }
56 loadContext = initialContext || new RouterContextProvider();
57 let requestUrl = new URL(request.url);
58 let normalizedPath = getNormalizedPath(request);
59 let normalizedPathname = normalizedPath.pathname;
60 let isSpaMode = getBuildTimeHeader(request, "X-React-Router-SPA-Mode") === "yes";
61 if (!build.ssr || build.unstable_apiOnly) {
62 let decodedPath = normalizedPathname;
63 try {
64 decodedPath = decodeURI(normalizedPathname);
65 } catch {}
66 if (build.basename && build.basename !== "/") {
67 let strippedPath = stripBasename(decodedPath, build.basename);
68 if (strippedPath == null) {
69 errorHandler(new ErrorResponseImpl(404, "Not Found", `Refusing to prerender the \`${decodedPath}\` path because it does not start with the basename \`${build.basename}\``), {
70 context: loadContext,
71 params,
72 request
73 });
74 return new Response("Not Found", {
75 status: 404,
76 statusText: "Not Found"
77 });
78 }
79 decodedPath = strippedPath;
80 }
81 if (build.prerender.length === 0) isSpaMode = true;
82 else if (!build.prerender.some((p) => removeTrailingSlash(p) === removeTrailingSlash(decodedPath))) if (requestUrl.pathname.endsWith(".data") && !build.unstable_apiOnly) {
83 errorHandler(new ErrorResponseImpl(404, "Not Found", `Refusing to SSR the path \`${decodedPath}\` because \`ssr:false\` is set and the path is not included in the \`prerender\` config, so in production the path will be a 404.`), {
84 context: loadContext,
85 params,
86 request
87 });
88 return new Response("Not Found", {
89 status: 404,
90 statusText: "Not Found"
91 });
92 } else isSpaMode = true;
93 }
94 let manifestUrl = getManifestPath(build.routeDiscovery.manifestPath, build.basename);
95 if (build.routeDiscovery.mode === "lazy" && requestUrl.pathname === manifestUrl) try {
96 return await handleManifestRequest(build, staticHandler, requestUrl);
97 } catch (e) {
98 handleError(e);
99 return new Response("Unknown Server Error", { status: 500 });
100 }
101 if (build.unstable_apiOnly && serverMode !== "development" && !requestUrl.pathname.endsWith(".data") && getBuildTimeHeader(request, "X-React-Router-SPA-Mode") !== "yes" && getBuildTimeHeader(request, "X-React-Router-Prerender") !== "yes") return new Response(null, {
102 status: 404,
103 statusText: "Not Found"
104 });
105 let matches = matchServerRoutes(build.routes, staticHandler, normalizedPathname);
106 if (matches && matches.length > 0) Object.assign(params, matches[0].params);
107 if (requestHandlerInstrumentations?.length) loadContext.set(instrumentationResultMetaContext, {
108 url: createDataFunctionUrl(request, normalizedPath),
109 pattern: matches ? getRoutePattern(matches) : "",
110 params: matches?.[0]?.params ? { ...matches[0].params } : {}
111 });
112 let response;
113 if (requestUrl.pathname.endsWith(".data")) {
114 response = await handleSingleFetchRequest(serverMode, build, staticHandler, request, loadContext, handleError);
115 if (isRedirectResponse(response)) response = generateSingleFetchRedirectResponse(response, request, build, serverMode);
116 if (build.entry.module.handleDataRequest) {
117 response = await build.entry.module.handleDataRequest(response, {
118 context: loadContext,
119 params: matches ? matches[0].params : {},
120 request
121 });
122 if (isRedirectResponse(response)) response = generateSingleFetchRedirectResponse(response, request, build, serverMode);
123 }
124 } else if (!isSpaMode && matches && matches[matches.length - 1].route.module.default == null && matches[matches.length - 1].route.module.ErrorBoundary == null) response = await handleResourceRequest(serverMode, build, staticHandler, matches.slice(-1)[0].route.id, request, loadContext, handleError);
125 else {
126 let { pathname } = requestUrl;
127 let criticalCss = void 0;
128 if (build.unstable_getCriticalCss) criticalCss = await build.unstable_getCriticalCss({ pathname });
129 else if (mode === "development" && getDevServerHooks()?.getCriticalCss) criticalCss = await getDevServerHooks()?.getCriticalCss?.(pathname);
130 response = await handleDocumentRequest(serverMode, build, staticHandler, request, loadContext, handleError, isSpaMode, criticalCss);
131 }
132 if (request.method === "HEAD") return new Response(null, {
133 headers: response.headers,
134 status: response.status,
135 statusText: response.statusText
136 });
137 return response;
138 };
139 if (requestHandlerInstrumentations?.length) requestHandler = instrumentHandler(requestHandler, requestHandlerInstrumentations);
140 return {
141 serverMode,
142 staticHandler,
143 errorHandler,
144 requestHandler
145 };
146}
147/**
148* Creates a request handler for a React Router server build.
149*
150* This is a low-level API used by server adapters to translate incoming
151* requests into React Router responses.
152*
153* @category Utils
154* @param build The server build, or a function that resolves to the server
155* build, used to handle requests.
156* @param mode The mode in which the server build is running.
157* @returns A request handler that returns a response for each incoming request.
158*/
159const createRequestHandler = (build, mode) => {
160 let _build;
161 let serverMode;
162 let staticHandler;
163 let errorHandler;
164 let _requestHandler;
165 return async function requestHandler(request, initialContext) {
166 _build = typeof build === "function" ? await build() : build;
167 if (typeof build === "function") {
168 let derived = derive(_build, mode);
169 serverMode = derived.serverMode;
170 staticHandler = derived.staticHandler;
171 errorHandler = derived.errorHandler;
172 _requestHandler = derived.requestHandler;
173 } else if (!serverMode || !staticHandler || !errorHandler || !_requestHandler) {
174 let derived = derive(_build, mode);
175 serverMode = derived.serverMode;
176 staticHandler = derived.staticHandler;
177 errorHandler = derived.errorHandler;
178 _requestHandler = derived.requestHandler;
179 }
180 return _requestHandler(request, initialContext);
181 };
182};
183async function handleManifestRequest(build, staticHandler, url) {
184 if (url.toString().length > 7680) return new Response(null, {
185 statusText: "Bad Request",
186 status: 400
187 });
188 if (build.assets.version !== url.searchParams.get("version")) return new Response(null, {
189 status: 204,
190 headers: { "X-Remix-Reload-Document": "true" }
191 });
192 let patches = {};
193 if (url.searchParams.has("paths")) {
194 let pathParam = url.searchParams.get("paths") || "";
195 let paths = new Set(pathParam.split(",").filter(Boolean));
196 for (let path of paths) {
197 if (!path.startsWith("/")) path = `/${path}`;
198 let matches = matchServerRoutes(build.routes, staticHandler, path);
199 if (matches) for (let match of matches) {
200 let routeId = match.route.id;
201 let route = build.assets.routes[routeId];
202 if (route) patches[routeId] = route;
203 }
204 }
205 return Response.json(patches, { headers: { "Cache-Control": "public, max-age=31536000, immutable" } });
206 }
207 return new Response("Invalid Request", { status: 400 });
208}
209async function handleSingleFetchRequest(serverMode, build, staticHandler, request, loadContext, handleError) {
210 return isMutationMethod(request.method) ? await singleFetchAction(build, serverMode, staticHandler, request, loadContext, handleError) : await singleFetchLoaders(build, serverMode, staticHandler, request, loadContext, handleError);
211}
212async function handleDocumentRequest(serverMode, build, staticHandler, request, loadContext, handleError, isSpaMode, criticalCss) {
213 try {
214 if (isMutationMethod(request.method)) try {
215 throwIfPotentialCSRFAttack(request, Array.isArray(build.allowedActionOrigins) ? build.allowedActionOrigins : []);
216 } catch (e) {
217 handleError(e);
218 return new Response("Bad Request", { status: 400 });
219 }
220 let result = await staticHandler.query(request, {
221 requestContext: loadContext,
222 generateMiddlewareResponse: async (query) => {
223 try {
224 let innerResult = await query(request);
225 if (!isResponse(innerResult)) innerResult = await renderHtml(innerResult, isSpaMode);
226 return innerResult;
227 } catch (error) {
228 handleError(error);
229 return new Response(null, { status: 500 });
230 }
231 },
232 normalizePath: (r) => getNormalizedPath(r)
233 });
234 if (!isResponse(result)) result = await renderHtml(result, isSpaMode);
235 return result;
236 } catch (error) {
237 handleError(error);
238 return new Response(null, { status: 500 });
239 }
240 async function renderHtml(context, isSpaMode) {
241 let headers = getDocumentHeaders(context, build);
242 if (SERVER_NO_BODY_STATUS_CODES.has(context.statusCode)) return new Response(null, {
243 status: context.statusCode,
244 headers
245 });
246 if (context.errors) {
247 Object.values(context.errors).forEach((err) => {
248 if (!isRouteErrorResponse(err) || err.error) handleError(err);
249 });
250 context.errors = sanitizeErrors(context.errors, serverMode);
251 }
252 let state = {
253 loaderData: context.loaderData,
254 actionData: context.actionData,
255 errors: context.errors
256 };
257 let routeDiscovery = build.routeDiscovery;
258 if (isFogOfWarEnabled(routeDiscovery, build.ssr) && shouldUseInitialRouteDiscovery(context.location.pathname, staticHandler._matchCost)) routeDiscovery = {
259 ...routeDiscovery,
260 mode: "initial"
261 };
262 let baseServerHandoff = {
263 basename: build.basename,
264 future: build.future,
265 routeDiscovery,
266 ssr: build.ssr,
267 isSpaMode,
268 unstable_apiOnly: build.unstable_apiOnly,
269 unstable_apiServerOrigin: build.unstable_apiServerOrigin
270 };
271 let entryContext = {
272 manifest: build.assets,
273 branches: [],
274 routeModules: createEntryRouteModules(build.routes),
275 staticHandlerContext: context,
276 criticalCss,
277 serverHandoffString: createServerHandoffString({
278 ...baseServerHandoff,
279 criticalCss
280 }),
281 serverHandoffStream: encodeViaTurboStream(state, request.signal, build.entry.module.streamTimeout, serverMode),
282 renderMeta: {},
283 future: build.future,
284 ssr: build.ssr,
285 routeDiscovery,
286 isSpaMode,
287 unstable_apiOnly: build.unstable_apiOnly,
288 unstable_apiServerOrigin: build.unstable_apiServerOrigin,
289 serializeError: (err) => serializeError(err, serverMode)
290 };
291 let handleDocumentRequestFunction = build.entry.module.default;
292 try {
293 return await handleDocumentRequestFunction(request, context.statusCode, headers, entryContext, loadContext);
294 } catch (error) {
295 handleError(error);
296 let errorForSecondRender = error;
297 if (isResponse(error)) try {
298 let data = await unwrapResponse(error);
299 errorForSecondRender = new ErrorResponseImpl(error.status, error.statusText, data);
300 } catch {}
301 context = getStaticContextFromError(staticHandler.dataRoutes, context, errorForSecondRender);
302 if (context.errors) context.errors = sanitizeErrors(context.errors, serverMode);
303 let state = {
304 loaderData: context.loaderData,
305 actionData: context.actionData,
306 errors: context.errors
307 };
308 entryContext = {
309 ...entryContext,
310 branches: [],
311 staticHandlerContext: context,
312 serverHandoffString: createServerHandoffString(baseServerHandoff),
313 serverHandoffStream: encodeViaTurboStream(state, request.signal, build.entry.module.streamTimeout, serverMode),
314 renderMeta: {}
315 };
316 try {
317 return await handleDocumentRequestFunction(request, context.statusCode, headers, entryContext, loadContext);
318 } catch (error) {
319 handleError(error);
320 return returnLastResortErrorResponse(error, serverMode);
321 }
322 }
323 }
324}
325async function handleResourceRequest(serverMode, build, staticHandler, routeId, request, loadContext, handleError) {
326 try {
327 return handleQueryRouteResult(await staticHandler.queryRoute(request, {
328 routeId,
329 requestContext: loadContext,
330 generateMiddlewareResponse: async (queryRoute) => {
331 try {
332 return handleQueryRouteResult(await queryRoute(request));
333 } catch (error) {
334 return handleQueryRouteError(error);
335 }
336 },
337 normalizePath: (r) => getNormalizedPath(r)
338 }));
339 } catch (error) {
340 return handleQueryRouteError(error);
341 }
342 function handleQueryRouteResult(result) {
343 if (isResponse(result)) return result;
344 if (typeof result === "string") return new Response(result);
345 return Response.json(result);
346 }
347 function handleQueryRouteError(error) {
348 if (isResponse(error)) return error;
349 if (isRouteErrorResponse(error)) {
350 handleError(error);
351 return errorResponseToJson(error, serverMode);
352 }
353 if (error instanceof Error && error.message === "Expected a response from queryRoute") {
354 let newError = /* @__PURE__ */ new Error("Expected a Response to be returned from resource route handler");
355 handleError(newError);
356 return returnLastResortErrorResponse(newError, serverMode);
357 }
358 handleError(error);
359 return returnLastResortErrorResponse(error, serverMode);
360 }
361}
362function errorResponseToJson(errorResponse, serverMode) {
363 return Response.json(serializeError(errorResponse.error || /* @__PURE__ */ new Error("Unexpected Server Error"), serverMode), {
364 status: errorResponse.status,
365 statusText: errorResponse.statusText
366 });
367}
368function returnLastResortErrorResponse(error, serverMode) {
369 let message = "Unexpected Server Error";
370 if (serverMode !== "production") message += `\n\n${String(error)}`;
371 return new Response(message, {
372 status: 500,
373 headers: { "Content-Type": "text/plain" }
374 });
375}
376function unwrapResponse(response) {
377 let contentType = response.headers.get("Content-Type");
378 return contentType && /\bapplication\/json\b/.test(contentType) ? response.body == null ? null : response.json() : response.text();
379}
380//#endregion
381export { createRequestHandler };