diff --git a/wolai-frontend/src/app/cache/[...path]/route.ts b/wolai-frontend/src/app/cache/[...path]/route.ts index 201ba701..6dbbc567 100644 --- a/wolai-frontend/src/app/cache/[...path]/route.ts +++ b/wolai-frontend/src/app/cache/[...path]/route.ts @@ -1,7 +1,9 @@ import type { NextRequest } from "next/server"; import { NextResponse } from "next/server"; +import { gzipSync } from "node:zlib"; export const dynamic = "force-dynamic"; +export const runtime = "nodejs"; // 说明:ONLYOFFICE 文档服务器在编辑过程中会从 `/cache/*` 拉取二进制缓存(例如 Editor.bin)。 // 当我们通过 `/onlyoffice-server/*` 反代文档服务器时,这些 `/cache/*` 请求会落到 Next 上, @@ -53,6 +55,21 @@ const isStrongCache = (cc: string) => { return v.includes("max-age=604800"); }; +const shouldGzip = (request: NextRequest, contentType: string) => { + const ae = String(request.headers.get("accept-encoding") || "").toLowerCase(); + if (!ae.includes("gzip")) return false; + + const ct = String(contentType || "").toLowerCase(); + const compressible = + ct.startsWith("text/") || + ct.includes("javascript") || + ct.includes("json") || + ct.includes("xml") || + ct.includes("svg"); + + return compressible; +}; + const proxyCache = async (request: NextRequest, pathParts: string[]) => { const incomingUrl = new URL(request.url); const target = new URL( @@ -62,7 +79,7 @@ const proxyCache = async (request: NextRequest, pathParts: string[]) => { const headers = new Headers(request.headers); headers.delete("host"); - // 说明:避免上游 gzip 后被自动解压但仍带 content-encoding,导致浏览器二次解压错误 + // 说明:让上游返回明文(identity),我们在此处按需 gzip 压缩,保证 content-encoding 与 body 一致。 headers.set("accept-encoding", "identity"); stripHopByHopHeaders(headers); @@ -80,11 +97,13 @@ const proxyCache = async (request: NextRequest, pathParts: string[]) => { const upstream = await fetch(target, init); + const upstreamContentLength = upstream.headers.get("content-length"); const outHeaders = new Headers(upstream.headers); stripHopByHopHeaders(outHeaders); outHeaders.delete("content-encoding"); outHeaders.delete("content-length"); - const cacheControl = pickCacheControl(pathParts, upstream.headers.get("content-type") || "", request.method); + const contentType = upstream.headers.get("content-type") || ""; + const cacheControl = pickCacheControl(pathParts, contentType, request.method); outHeaders.set("cache-control", cacheControl); outHeaders.delete("pragma"); outHeaders.delete("expires"); @@ -99,10 +118,32 @@ const proxyCache = async (request: NextRequest, pathParts: string[]) => { } } - return new NextResponse(upstream.body, { - status: upstream.status, - headers: outHeaders, - }); + // 说明:同 onlyoffice-server 路由,对强缓存二进制改为定长响应,提升浏览器落盘缓存命中率。 + if (isStrongCache(cacheControl)) { + outHeaders.delete("vary"); + outHeaders.set("vary", "Accept-Encoding"); + if (method === "HEAD") { + if (upstreamContentLength) outHeaders.set("content-length", upstreamContentLength); + return new NextResponse(null, { status: upstream.status, headers: outHeaders }); + } + const buf = await upstream.arrayBuffer(); + + if (shouldGzip(request, contentType)) { + try { + const gz = gzipSync(Buffer.from(buf), { level: 6 }); + outHeaders.set("content-encoding", "gzip"); + outHeaders.set("content-length", String(gz.byteLength)); + return new NextResponse(gz, { status: upstream.status, headers: outHeaders }); + } catch { + // ignore + } + } + + outHeaders.set("content-length", String(buf.byteLength)); + return new NextResponse(buf, { status: upstream.status, headers: outHeaders }); + } + + return new NextResponse(upstream.body, { status: upstream.status, headers: outHeaders }); }; type RouteCtx = { params: Promise<{ path: string[] }> }; diff --git a/wolai-frontend/src/app/onlyoffice-server/[...path]/route.ts b/wolai-frontend/src/app/onlyoffice-server/[...path]/route.ts index 8f536a6b..ecbae909 100644 --- a/wolai-frontend/src/app/onlyoffice-server/[...path]/route.ts +++ b/wolai-frontend/src/app/onlyoffice-server/[...path]/route.ts @@ -1,18 +1,23 @@ import type { NextRequest } from "next/server"; import { NextResponse } from "next/server"; import { getMnoteRuntimeConfig } from "@/lib/runtime-config"; +import { gzipSync } from "node:zlib"; export const dynamic = "force-dynamic"; +export const runtime = "nodejs"; const ONLYOFFICE_INTERNAL_URL = (process.env.ONLYOFFICE_INTERNAL_URL || "http://127.0.0.1:8081").replace(/\/+$/, ""); -const DISABLE_SERVICE_WORKER_SNIPPET = ` +const SERVICE_WORKER_SAFE_PATCH_SNIPPET = `