chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
// When proxying long-polling requests, content-encoding & content-length are added
|
||||
// erroneously (saying the body is gzipped when it's not) so we'll just remove
|
||||
// them to avoid content decoding errors in the browser.
|
||||
//
|
||||
|
||||
import { logger } from "~/services/logger.server";
|
||||
|
||||
// Similar-ish problem to https://github.com/wintercg/fetch/issues/23
|
||||
export async function longPollingFetch(
|
||||
url: string,
|
||||
options?: RequestInit,
|
||||
rewriteResponseHeaders?: Record<string, string>
|
||||
) {
|
||||
let upstream: Response | undefined;
|
||||
try {
|
||||
upstream = await fetch(url, options);
|
||||
let response = upstream;
|
||||
|
||||
if (response.headers.get("content-encoding")) {
|
||||
const headers = new Headers(response.headers);
|
||||
headers.delete("content-encoding");
|
||||
headers.delete("content-length");
|
||||
|
||||
response = new Response(response.body, {
|
||||
headers,
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
});
|
||||
}
|
||||
|
||||
if (rewriteResponseHeaders) {
|
||||
const headers = new Headers(response.headers);
|
||||
|
||||
for (const [fromKey, toKey] of Object.entries(rewriteResponseHeaders)) {
|
||||
const value = headers.get(fromKey);
|
||||
if (value) {
|
||||
headers.set(toKey, value);
|
||||
headers.delete(fromKey);
|
||||
}
|
||||
}
|
||||
|
||||
response = new Response(response.body, {
|
||||
headers,
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
});
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
// Release upstream undici socket + buffers explicitly. Without this the
|
||||
// ReadableStream stays open and undici keeps buffering chunks into memory
|
||||
// until the upstream times out (see H1 isolation test — ~44 KB retained
|
||||
// per unconsumed-body fetch in RSS).
|
||||
try {
|
||||
await upstream?.body?.cancel();
|
||||
} catch {}
|
||||
|
||||
// AbortError is the expected path when downstream disconnects with a
|
||||
// propagated signal — treat as a clean client-close, not a server error.
|
||||
if (error instanceof Error && error.name === "AbortError") {
|
||||
throw new Response(null, { status: 499 });
|
||||
}
|
||||
if (error instanceof TypeError) {
|
||||
logger.error("Network error:", { error: error.message });
|
||||
throw new Response("Network error occurred", { status: 503 });
|
||||
} else if (error instanceof Error) {
|
||||
logger.error("Fetch error:", { error: error.message });
|
||||
throw new Response(error.message, { status: 500 });
|
||||
} else {
|
||||
logger.error("Unknown error occurred during fetch");
|
||||
throw new Response("An unknown error occurred", { status: 500 });
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user