INDIE UNPKG

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