UNPKG

15.1 kBJavaScriptView Raw
1/**
2 * react-router v8.1.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}
140const createRequestHandler = (build, mode) => {
141 let _build;
142 let serverMode;
143 let staticHandler;
144 let errorHandler;
145 let _requestHandler;
146 return async function requestHandler(request, initialContext) {
147 _build = typeof build === "function" ? await build() : build;
148 if (typeof build === "function") {
149 let derived = derive(_build, mode);
150 serverMode = derived.serverMode;
151 staticHandler = derived.staticHandler;
152 errorHandler = derived.errorHandler;
153 _requestHandler = derived.requestHandler;
154 } else if (!serverMode || !staticHandler || !errorHandler || !_requestHandler) {
155 let derived = derive(_build, mode);
156 serverMode = derived.serverMode;
157 staticHandler = derived.staticHandler;
158 errorHandler = derived.errorHandler;
159 _requestHandler = derived.requestHandler;
160 }
161 return _requestHandler(request, initialContext);
162 };
163};
164async function handleManifestRequest(build, dataRoutes, branches, url) {
165 if (url.toString().length > 7680) return new Response(null, {
166 statusText: "Bad Request",
167 status: 400
168 });
169 if (build.assets.version !== url.searchParams.get("version")) return new Response(null, {
170 status: 204,
171 headers: { "X-Remix-Reload-Document": "true" }
172 });
173 let patches = {};
174 if (url.searchParams.has("paths")) {
175 let pathParam = url.searchParams.get("paths") || "";
176 let paths = new Set(pathParam.split(",").filter(Boolean));
177 for (let path of paths) {
178 if (!path.startsWith("/")) path = `/${path}`;
179 let matches = matchServerRoutes(build.routes, dataRoutes, branches, path, build.basename);
180 if (matches) for (let match of matches) {
181 let routeId = match.route.id;
182 let route = build.assets.routes[routeId];
183 if (route) patches[routeId] = route;
184 }
185 }
186 return Response.json(patches, { headers: { "Cache-Control": "public, max-age=31536000, immutable" } });
187 }
188 return new Response("Invalid Request", { status: 400 });
189}
190async function handleSingleFetchRequest(serverMode, build, staticHandler, request, loadContext, handleError) {
191 return isMutationMethod(request.method) ? await singleFetchAction(build, serverMode, staticHandler, request, loadContext, handleError) : await singleFetchLoaders(build, serverMode, staticHandler, request, loadContext, handleError);
192}
193async function handleDocumentRequest(serverMode, build, staticHandler, request, loadContext, handleError, isSpaMode, criticalCss) {
194 try {
195 if (isMutationMethod(request.method)) try {
196 throwIfPotentialCSRFAttack(request, Array.isArray(build.allowedActionOrigins) ? build.allowedActionOrigins : []);
197 } catch (e) {
198 handleError(e);
199 return new Response("Bad Request", { status: 400 });
200 }
201 let result = await staticHandler.query(request, {
202 requestContext: loadContext,
203 generateMiddlewareResponse: async (query) => {
204 try {
205 let innerResult = await query(request);
206 if (!isResponse(innerResult)) innerResult = await renderHtml(innerResult, isSpaMode);
207 return innerResult;
208 } catch (error) {
209 handleError(error);
210 return new Response(null, { status: 500 });
211 }
212 },
213 normalizePath: (r) => getNormalizedPath(r)
214 });
215 if (!isResponse(result)) result = await renderHtml(result, isSpaMode);
216 return result;
217 } catch (error) {
218 handleError(error);
219 return new Response(null, { status: 500 });
220 }
221 async function renderHtml(context, isSpaMode) {
222 let headers = getDocumentHeaders(context, build);
223 if (SERVER_NO_BODY_STATUS_CODES.has(context.statusCode)) return new Response(null, {
224 status: context.statusCode,
225 headers
226 });
227 if (context.errors) {
228 Object.values(context.errors).forEach((err) => {
229 if (!isRouteErrorResponse(err) || err.error) handleError(err);
230 });
231 context.errors = sanitizeErrors(context.errors, serverMode);
232 }
233 let state = {
234 loaderData: context.loaderData,
235 actionData: context.actionData,
236 errors: context.errors
237 };
238 let baseServerHandoff = {
239 basename: build.basename,
240 future: build.future,
241 routeDiscovery: build.routeDiscovery,
242 ssr: build.ssr,
243 isSpaMode
244 };
245 let entryContext = {
246 manifest: build.assets,
247 branches: staticHandler._internalRouteBranches,
248 routeModules: createEntryRouteModules(build.routes),
249 staticHandlerContext: context,
250 criticalCss,
251 serverHandoffString: createServerHandoffString({
252 ...baseServerHandoff,
253 criticalCss
254 }),
255 serverHandoffStream: encodeViaTurboStream(state, request.signal, build.entry.module.streamTimeout, serverMode),
256 renderMeta: {},
257 future: build.future,
258 ssr: build.ssr,
259 routeDiscovery: build.routeDiscovery,
260 isSpaMode,
261 serializeError: (err) => serializeError(err, serverMode)
262 };
263 let handleDocumentRequestFunction = build.entry.module.default;
264 try {
265 return await handleDocumentRequestFunction(request, context.statusCode, headers, entryContext, loadContext);
266 } catch (error) {
267 handleError(error);
268 let errorForSecondRender = error;
269 if (isResponse(error)) try {
270 let data = await unwrapResponse(error);
271 errorForSecondRender = new ErrorResponseImpl(error.status, error.statusText, data);
272 } catch (e) {}
273 context = getStaticContextFromError(staticHandler.dataRoutes, context, errorForSecondRender);
274 if (context.errors) context.errors = sanitizeErrors(context.errors, serverMode);
275 let state = {
276 loaderData: context.loaderData,
277 actionData: context.actionData,
278 errors: context.errors
279 };
280 entryContext = {
281 ...entryContext,
282 staticHandlerContext: context,
283 serverHandoffString: createServerHandoffString(baseServerHandoff),
284 serverHandoffStream: encodeViaTurboStream(state, request.signal, build.entry.module.streamTimeout, serverMode),
285 renderMeta: {}
286 };
287 try {
288 return await handleDocumentRequestFunction(request, context.statusCode, headers, entryContext, loadContext);
289 } catch (error) {
290 handleError(error);
291 return returnLastResortErrorResponse(error, serverMode);
292 }
293 }
294 }
295}
296async function handleResourceRequest(serverMode, build, staticHandler, routeId, request, loadContext, handleError) {
297 try {
298 return handleQueryRouteResult(await staticHandler.queryRoute(request, {
299 routeId,
300 requestContext: loadContext,
301 generateMiddlewareResponse: async (queryRoute) => {
302 try {
303 return handleQueryRouteResult(await queryRoute(request));
304 } catch (error) {
305 return handleQueryRouteError(error);
306 }
307 },
308 normalizePath: (r) => getNormalizedPath(r)
309 }));
310 } catch (error) {
311 return handleQueryRouteError(error);
312 }
313 function handleQueryRouteResult(result) {
314 if (isResponse(result)) return result;
315 if (typeof result === "string") return new Response(result);
316 return Response.json(result);
317 }
318 function handleQueryRouteError(error) {
319 if (isResponse(error)) return error;
320 if (isRouteErrorResponse(error)) {
321 handleError(error);
322 return errorResponseToJson(error, serverMode);
323 }
324 if (error instanceof Error && error.message === "Expected a response from queryRoute") {
325 let newError = /* @__PURE__ */ new Error("Expected a Response to be returned from resource route handler");
326 handleError(newError);
327 return returnLastResortErrorResponse(newError, serverMode);
328 }
329 handleError(error);
330 return returnLastResortErrorResponse(error, serverMode);
331 }
332}
333function errorResponseToJson(errorResponse, serverMode) {
334 return Response.json(serializeError(errorResponse.error || /* @__PURE__ */ new Error("Unexpected Server Error"), serverMode), {
335 status: errorResponse.status,
336 statusText: errorResponse.statusText
337 });
338}
339function returnLastResortErrorResponse(error, serverMode) {
340 let message = "Unexpected Server Error";
341 if (serverMode !== "production") message += `\n\n${String(error)}`;
342 return new Response(message, {
343 status: 500,
344 headers: { "Content-Type": "text/plain" }
345 });
346}
347function unwrapResponse(response) {
348 let contentType = response.headers.get("Content-Type");
349 return contentType && /\bapplication\/json\b/.test(contentType) ? response.body == null ? null : response.json() : response.text();
350}
351//#endregion
352export { createRequestHandler };