UNPKG

15.5 kBJavaScriptView Raw
1/**
2 * react-router v8.3.0
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 } 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) {
62 let decodedPath = decodeURI(normalizedPathname);
63 if (build.basename && build.basename !== "/") {
64 let strippedPath = stripBasename(decodedPath, build.basename);
65 if (strippedPath == null) {
66 errorHandler(new ErrorResponseImpl(404, "Not Found", `Refusing to prerender the \`${decodedPath}\` path because it does not start with the basename \`${build.basename}\``), {
67 context: loadContext,
68 params,
69 request
70 });
71 return new Response("Not Found", {
72 status: 404,
73 statusText: "Not Found"
74 });
75 }
76 decodedPath = strippedPath;
77 }
78 if (build.prerender.length === 0) isSpaMode = true;
79 else if (!build.prerender.some((p) => removeTrailingSlash(p) === removeTrailingSlash(decodedPath))) if (requestUrl.pathname.endsWith(".data")) {
80 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.`), {
81 context: loadContext,
82 params,
83 request
84 });
85 return new Response("Not Found", {
86 status: 404,
87 statusText: "Not Found"
88 });
89 } else isSpaMode = true;
90 }
91 let manifestUrl = getManifestPath(build.routeDiscovery.manifestPath, build.basename);
92 if (build.routeDiscovery.mode === "lazy" && requestUrl.pathname === manifestUrl) try {
93 return await handleManifestRequest(build, staticHandler.dataRoutes, staticHandler._internalRouteBranches, requestUrl);
94 } catch (e) {
95 handleError(e);
96 return new Response("Unknown Server Error", { status: 500 });
97 }
98 let matches = matchServerRoutes(build.routes, staticHandler.dataRoutes, staticHandler._internalRouteBranches, normalizedPathname, build.basename);
99 if (matches && matches.length > 0) Object.assign(params, matches[0].params);
100 if (requestHandlerInstrumentations?.length) loadContext.set(instrumentationResultMetaContext, {
101 url: createDataFunctionUrl(request, normalizedPath),
102 pattern: matches ? getRoutePattern(matches) : "",
103 params: matches?.[0]?.params ? { ...matches[0].params } : {}
104 });
105 let response;
106 if (requestUrl.pathname.endsWith(".data")) {
107 response = await handleSingleFetchRequest(serverMode, build, staticHandler, request, loadContext, handleError);
108 if (isRedirectResponse(response)) response = generateSingleFetchRedirectResponse(response, request, build, serverMode);
109 if (build.entry.module.handleDataRequest) {
110 response = await build.entry.module.handleDataRequest(response, {
111 context: loadContext,
112 params: matches ? matches[0].params : {},
113 request
114 });
115 if (isRedirectResponse(response)) response = generateSingleFetchRedirectResponse(response, request, build, serverMode);
116 }
117 } 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);
118 else {
119 let { pathname } = requestUrl;
120 let criticalCss = void 0;
121 if (build.unstable_getCriticalCss) criticalCss = await build.unstable_getCriticalCss({ pathname });
122 else if (mode === "development" && getDevServerHooks()?.getCriticalCss) criticalCss = await getDevServerHooks()?.getCriticalCss?.(pathname);
123 response = await handleDocumentRequest(serverMode, build, staticHandler, request, loadContext, handleError, isSpaMode, criticalCss);
124 }
125 if (request.method === "HEAD") return new Response(null, {
126 headers: response.headers,
127 status: response.status,
128 statusText: response.statusText
129 });
130 return response;
131 };
132 if (requestHandlerInstrumentations?.length) requestHandler = instrumentHandler(requestHandler, requestHandlerInstrumentations);
133 return {
134 serverMode,
135 staticHandler,
136 errorHandler,
137 requestHandler
138 };
139}
140/**
141* Creates a request handler for a React Router server build.
142*
143* This is a low-level API used by server adapters to translate incoming
144* requests into React Router responses.
145*
146* @category Utils
147* @param build The server build, or a function that resolves to the server
148* build, used to handle requests.
149* @param mode The mode in which the server build is running.
150* @returns A request handler that returns a response for each incoming request.
151*/
152const createRequestHandler = (build, mode) => {
153 let _build;
154 let serverMode;
155 let staticHandler;
156 let errorHandler;
157 let _requestHandler;
158 return async function requestHandler(request, initialContext) {
159 _build = typeof build === "function" ? await build() : build;
160 if (typeof build === "function") {
161 let derived = derive(_build, mode);
162 serverMode = derived.serverMode;
163 staticHandler = derived.staticHandler;
164 errorHandler = derived.errorHandler;
165 _requestHandler = derived.requestHandler;
166 } else if (!serverMode || !staticHandler || !errorHandler || !_requestHandler) {
167 let derived = derive(_build, mode);
168 serverMode = derived.serverMode;
169 staticHandler = derived.staticHandler;
170 errorHandler = derived.errorHandler;
171 _requestHandler = derived.requestHandler;
172 }
173 return _requestHandler(request, initialContext);
174 };
175};
176async function handleManifestRequest(build, dataRoutes, branches, url) {
177 if (url.toString().length > 7680) return new Response(null, {
178 statusText: "Bad Request",
179 status: 400
180 });
181 if (build.assets.version !== url.searchParams.get("version")) return new Response(null, {
182 status: 204,
183 headers: { "X-Remix-Reload-Document": "true" }
184 });
185 let patches = {};
186 if (url.searchParams.has("paths")) {
187 let pathParam = url.searchParams.get("paths") || "";
188 let paths = new Set(pathParam.split(",").filter(Boolean));
189 for (let path of paths) {
190 if (!path.startsWith("/")) path = `/${path}`;
191 let matches = matchServerRoutes(build.routes, dataRoutes, branches, path, build.basename);
192 if (matches) for (let match of matches) {
193 let routeId = match.route.id;
194 let route = build.assets.routes[routeId];
195 if (route) patches[routeId] = route;
196 }
197 }
198 return Response.json(patches, { headers: { "Cache-Control": "public, max-age=31536000, immutable" } });
199 }
200 return new Response("Invalid Request", { status: 400 });
201}
202async function handleSingleFetchRequest(serverMode, build, staticHandler, request, loadContext, handleError) {
203 return isMutationMethod(request.method) ? await singleFetchAction(build, serverMode, staticHandler, request, loadContext, handleError) : await singleFetchLoaders(build, serverMode, staticHandler, request, loadContext, handleError);
204}
205async function handleDocumentRequest(serverMode, build, staticHandler, request, loadContext, handleError, isSpaMode, criticalCss) {
206 try {
207 if (isMutationMethod(request.method)) try {
208 throwIfPotentialCSRFAttack(request, Array.isArray(build.allowedActionOrigins) ? build.allowedActionOrigins : []);
209 } catch (e) {
210 handleError(e);
211 return new Response("Bad Request", { status: 400 });
212 }
213 let result = await staticHandler.query(request, {
214 requestContext: loadContext,
215 generateMiddlewareResponse: async (query) => {
216 try {
217 let innerResult = await query(request);
218 if (!isResponse(innerResult)) innerResult = await renderHtml(innerResult, isSpaMode);
219 return innerResult;
220 } catch (error) {
221 handleError(error);
222 return new Response(null, { status: 500 });
223 }
224 },
225 normalizePath: (r) => getNormalizedPath(r)
226 });
227 if (!isResponse(result)) result = await renderHtml(result, isSpaMode);
228 return result;
229 } catch (error) {
230 handleError(error);
231 return new Response(null, { status: 500 });
232 }
233 async function renderHtml(context, isSpaMode) {
234 let headers = getDocumentHeaders(context, build);
235 if (SERVER_NO_BODY_STATUS_CODES.has(context.statusCode)) return new Response(null, {
236 status: context.statusCode,
237 headers
238 });
239 if (context.errors) {
240 Object.values(context.errors).forEach((err) => {
241 if (!isRouteErrorResponse(err) || err.error) handleError(err);
242 });
243 context.errors = sanitizeErrors(context.errors, serverMode);
244 }
245 let state = {
246 loaderData: context.loaderData,
247 actionData: context.actionData,
248 errors: context.errors
249 };
250 let baseServerHandoff = {
251 basename: build.basename,
252 future: build.future,
253 routeDiscovery: build.routeDiscovery,
254 ssr: build.ssr,
255 isSpaMode
256 };
257 let entryContext = {
258 manifest: build.assets,
259 branches: staticHandler._internalRouteBranches,
260 routeModules: createEntryRouteModules(build.routes),
261 staticHandlerContext: context,
262 criticalCss,
263 serverHandoffString: createServerHandoffString({
264 ...baseServerHandoff,
265 criticalCss
266 }),
267 serverHandoffStream: encodeViaTurboStream(state, request.signal, build.entry.module.streamTimeout, serverMode),
268 renderMeta: {},
269 future: build.future,
270 ssr: build.ssr,
271 routeDiscovery: build.routeDiscovery,
272 isSpaMode,
273 serializeError: (err) => serializeError(err, serverMode)
274 };
275 let handleDocumentRequestFunction = build.entry.module.default;
276 try {
277 return await handleDocumentRequestFunction(request, context.statusCode, headers, entryContext, loadContext);
278 } catch (error) {
279 handleError(error);
280 let errorForSecondRender = error;
281 if (isResponse(error)) try {
282 let data = await unwrapResponse(error);
283 errorForSecondRender = new ErrorResponseImpl(error.status, error.statusText, data);
284 } catch (e) {}
285 context = getStaticContextFromError(staticHandler.dataRoutes, context, errorForSecondRender);
286 if (context.errors) context.errors = sanitizeErrors(context.errors, serverMode);
287 let state = {
288 loaderData: context.loaderData,
289 actionData: context.actionData,
290 errors: context.errors
291 };
292 entryContext = {
293 ...entryContext,
294 staticHandlerContext: context,
295 serverHandoffString: createServerHandoffString(baseServerHandoff),
296 serverHandoffStream: encodeViaTurboStream(state, request.signal, build.entry.module.streamTimeout, serverMode),
297 renderMeta: {}
298 };
299 try {
300 return await handleDocumentRequestFunction(request, context.statusCode, headers, entryContext, loadContext);
301 } catch (error) {
302 handleError(error);
303 return returnLastResortErrorResponse(error, serverMode);
304 }
305 }
306 }
307}
308async function handleResourceRequest(serverMode, build, staticHandler, routeId, request, loadContext, handleError) {
309 try {
310 return handleQueryRouteResult(await staticHandler.queryRoute(request, {
311 routeId,
312 requestContext: loadContext,
313 generateMiddlewareResponse: async (queryRoute) => {
314 try {
315 return handleQueryRouteResult(await queryRoute(request));
316 } catch (error) {
317 return handleQueryRouteError(error);
318 }
319 },
320 normalizePath: (r) => getNormalizedPath(r)
321 }));
322 } catch (error) {
323 return handleQueryRouteError(error);
324 }
325 function handleQueryRouteResult(result) {
326 if (isResponse(result)) return result;
327 if (typeof result === "string") return new Response(result);
328 return Response.json(result);
329 }
330 function handleQueryRouteError(error) {
331 if (isResponse(error)) return error;
332 if (isRouteErrorResponse(error)) {
333 handleError(error);
334 return errorResponseToJson(error, serverMode);
335 }
336 if (error instanceof Error && error.message === "Expected a response from queryRoute") {
337 let newError = /* @__PURE__ */ new Error("Expected a Response to be returned from resource route handler");
338 handleError(newError);
339 return returnLastResortErrorResponse(newError, serverMode);
340 }
341 handleError(error);
342 return returnLastResortErrorResponse(error, serverMode);
343 }
344}
345function errorResponseToJson(errorResponse, serverMode) {
346 return Response.json(serializeError(errorResponse.error || /* @__PURE__ */ new Error("Unexpected Server Error"), serverMode), {
347 status: errorResponse.status,
348 statusText: errorResponse.statusText
349 });
350}
351function returnLastResortErrorResponse(error, serverMode) {
352 let message = "Unexpected Server Error";
353 if (serverMode !== "production") message += `\n\n${String(error)}`;
354 return new Response(message, {
355 status: 500,
356 headers: { "Content-Type": "text/plain" }
357 });
358}
359function unwrapResponse(response) {
360 let contentType = response.headers.get("Content-Type");
361 return contentType && /\bapplication\/json\b/.test(contentType) ? response.body == null ? null : response.json() : response.text();
362}
363//#endregion
364export { createRequestHandler };