UNPKG

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