Fix canvas transfer timeouts and move images out of snapshots
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { fetchCanvasResponse } from "@/lib/canvas-response";
|
||||
import { sessionCookie, sessionMaxAge, SESSION_EXPIRY_HEADER } from "@/lib/session-cookie";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
@@ -36,6 +37,8 @@ async function handler(
|
||||
req: NextRequest,
|
||||
{ params }: { params: Promise<{ path: string[] }> },
|
||||
) {
|
||||
const requestStarted = performance.now();
|
||||
const requestStartedAt = new Date().toISOString();
|
||||
const responseHeaders = new Headers({ "Cache-Control": "no-store" });
|
||||
const json = (body: unknown, status: number) =>
|
||||
NextResponse.json(body, { status, headers: responseHeaders });
|
||||
@@ -61,6 +64,7 @@ async function handler(
|
||||
return json({ error: tooLarge ? "body_too_large" : "invalid_json" }, tooLarge ? 413 : 400);
|
||||
}
|
||||
}
|
||||
const bodyReceived = performance.now();
|
||||
const origin = process.env.FOUNDRY_API_ORIGIN || "https://api.foundry.entroia.com";
|
||||
const project = process.env.NEXT_PUBLIC_FOUNDRY_PROJECT || "endoleap";
|
||||
const headers: Record<string, string> = { "Content-Type": asset ? (req.headers.get("content-type") || "application/octet-stream") : "application/json" };
|
||||
@@ -72,20 +76,31 @@ async function handler(
|
||||
if (value !== null) headers[h] = value;
|
||||
}
|
||||
const query = new URLSearchParams();
|
||||
for (const key of ["kind", "sort", "q", "limit", "offset", "after_seq", "status", "include_descendants", "include_subgraphs", "cursor"]) {
|
||||
for (const key of ["kind", "sort", "q", "limit", "offset", "after_seq", "status", "include_descendants", "include_subgraphs", "cursor", "variant", "width"]) {
|
||||
const values = req.nextUrl.searchParams.getAll(key);
|
||||
if (values.length === 1 && values[0].length < 300) query.set(key, values[0]);
|
||||
}
|
||||
const encodingStarted = performance.now();
|
||||
const outboundBody = req.method === "GET" ? undefined : asset ? requestBody as BodyInit : JSON.stringify(requestBody || {});
|
||||
const encodeMs = performance.now() - encodingStarted;
|
||||
const started = performance.now();
|
||||
try {
|
||||
const upstream = await fetch(
|
||||
const snapshotRead = canvas && path.endsWith("/canvas") && req.method === "GET";
|
||||
const streamedRead = snapshotRead || asset && req.method === "GET";
|
||||
const snapshotWrite = canvas && path.endsWith("/canvas") && req.method === "PATCH";
|
||||
const traceId = streamedRead || snapshotWrite ? crypto.randomUUID() : "";
|
||||
const requestUpstream = streamedRead ? (url: string, options: RequestInit) => fetchCanvasResponse(url, options, undefined, fetch, (trace) => {
|
||||
console.info(snapshotRead ? "[canvas-transfer]" : "[asset-transfer]", JSON.stringify({ trace_id: traceId, resource: path, ...trace }));
|
||||
}) : fetch;
|
||||
if (streamedRead || snapshotWrite) responseHeaders.set("X-Canvas-Trace-Id", traceId);
|
||||
const upstream = await requestUpstream(
|
||||
origin + "/projects/" + encodeURIComponent(project) + "/" + path +
|
||||
(query.size ? "?" + query.toString() : ""),
|
||||
{
|
||||
method: req.method,
|
||||
headers,
|
||||
body: req.method === "GET" ? undefined : asset ? requestBody as BodyInit : JSON.stringify(requestBody || {}),
|
||||
signal: AbortSignal.any([AbortSignal.timeout(15000), req.signal]),
|
||||
body: outboundBody,
|
||||
signal: streamedRead ? req.signal : AbortSignal.any([AbortSignal.timeout(15000), req.signal]),
|
||||
redirect: "manual",
|
||||
cache: "no-store",
|
||||
},
|
||||
@@ -103,8 +118,11 @@ async function handler(
|
||||
}
|
||||
if (asset && upstream.ok && req.method === "GET") {
|
||||
const type = upstream.headers.get("content-type") || "";
|
||||
if (!/^image\/(png|jpeg|webp|gif)(?:;|$)/i.test(type)) return json({ error: "invalid_asset" }, 502);
|
||||
if (!/^image\/(png|jpeg|webp|gif)(?:;|$)/i.test(type)) { await upstream.body?.cancel(); return json({ error: "invalid_asset" }, 502); }
|
||||
responseHeaders.set("Content-Type", type); responseHeaders.set("X-Content-Type-Options", "nosniff");
|
||||
const variant = upstream.headers.get("x-asset-variant");
|
||||
if (variant === "original" || variant === "preview") responseHeaders.set("X-Asset-Variant", variant);
|
||||
responseHeaders.set("Server-Timing", `upstream;dur=${(performance.now() - started).toFixed(1)}`);
|
||||
return new NextResponse(upstream.body, { status: upstream.status, headers: responseHeaders });
|
||||
}
|
||||
const headersReceived = performance.now();
|
||||
@@ -116,6 +134,15 @@ async function handler(
|
||||
return new NextResponse(upstream.body, { status: upstream.status, headers: responseHeaders });
|
||||
}
|
||||
const body = upstream.status === 204 ? null : await upstream.json();
|
||||
if (snapshotWrite) console.info("[canvas-save]", JSON.stringify({
|
||||
trace_id: traceId, resource: path, status: upstream.status, started_at: requestStartedAt, finished_at: new Date().toISOString(),
|
||||
client_body_ms: Math.round((bodyReceived - requestStarted) * 10) / 10,
|
||||
encode_ms: Math.round(encodeMs * 10) / 10,
|
||||
request_bytes: typeof outboundBody === "string" ? new TextEncoder().encode(outboundBody).length : 0,
|
||||
upload_and_server_ms: Math.round((headersReceived - started) * 10) / 10,
|
||||
response_body_ms: Math.round((performance.now() - headersReceived) * 10) / 10,
|
||||
total_ms: Math.round((performance.now() - requestStarted) * 10) / 10,
|
||||
}));
|
||||
responseHeaders.set(
|
||||
"Server-Timing",
|
||||
`upstream;dur=${(headersReceived - started).toFixed(1)}, total;dur=${(performance.now() - started).toFixed(1)}`,
|
||||
|
||||
Reference in New Issue
Block a user