fix(basemap-tile): buffer body + drop upstream encoding/length headers
Initial proxy streamed upstream.body straight through with the upstream
Content-Encoding + Content-Length headers. Two ways that broke:
- Node's fetch auto-decodes gzip/br responses, so the body coming
out of upstream.body is already plain bytes. Forwarding
Content-Encoding: gzip made the browser (and curl) try to gunzip
plain bytes and fail.
- Content-Length was the upstream (compressed) length, not the
decoded byte count. Mid-stream the H2 layer noticed the mismatch
and dropped with INTERNAL_ERROR (curl returned status=000 + a
0-byte file).
Switch to arrayBuffer() + emit only Content-Type. Node serializes
the response with the right length and no encoding header, so the
browser gets the plain PBF / PNG / JSON it expects.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -70,12 +70,17 @@ export async function GET(
|
|||||||
return NextResponse.json({ error: "upstream_too_large" }, { status: 413 });
|
return NextResponse.json({ error: "upstream_too_large" }, { status: 413 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Buffer the body and re-emit. We deliberately drop upstream
|
||||||
|
// Content-Encoding + Content-Length: Node's fetch auto-decodes
|
||||||
|
// gzip/br responses, so forwarding the original encoding header makes
|
||||||
|
// the browser try to gunzip already-decoded bytes (or, worse, makes
|
||||||
|
// Next.js stream a Content-Length that doesn't match the decoded
|
||||||
|
// payload → HTTP/2 INTERNAL_ERROR mid-stream).
|
||||||
|
const body = await upstream.arrayBuffer();
|
||||||
|
|
||||||
const headers = new Headers();
|
const headers = new Headers();
|
||||||
const ct = upstream.headers.get("content-type");
|
const ct = upstream.headers.get("content-type");
|
||||||
if (ct) headers.set("Content-Type", ct);
|
if (ct) headers.set("Content-Type", ct);
|
||||||
const ce = upstream.headers.get("content-encoding");
|
|
||||||
if (ce) headers.set("Content-Encoding", ce);
|
|
||||||
if (contentLength) headers.set("Content-Length", contentLength);
|
|
||||||
|
|
||||||
// Tiles + sprites + glyphs are immutable per path (versioned). Cache
|
// Tiles + sprites + glyphs are immutable per path (versioned). Cache
|
||||||
// aggressively to keep architots out of the per-tile critical path.
|
// aggressively to keep architots out of the per-tile critical path.
|
||||||
@@ -84,5 +89,5 @@ export async function GET(
|
|||||||
"public, max-age=86400, stale-while-revalidate=604800, immutable",
|
"public, max-age=86400, stale-while-revalidate=604800, immutable",
|
||||||
);
|
);
|
||||||
|
|
||||||
return new NextResponse(upstream.body, { status: 200, headers });
|
return new NextResponse(body, { status: 200, headers });
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user