mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-21 22:52:19 +03:00
fix(relay): strip stale upstream encoding headers on bifrost error response
The new !upstream.ok error path copies upstream.headers (which can carry a content-length/content-encoding/transfer-encoding for the ORIGINAL upstream body) onto the freshly re-serialized JSON error Response, causing a byte-length mismatch. Route the headers through stripStaleEncodingHeaders() before serializing the error body, and add a regression test that mocks an upstream Response with an explicit content-length header. Co-authored-by: OPX-Aminul <305739020+OPX-Aminul@users.noreply.github.com>
This commit is contained in:
@@ -33,6 +33,7 @@ import {
|
||||
import { getProviderPluginManifestEntryForModel } from "@omniroute/open-sse/config/providerPluginManifestRegistry.ts";
|
||||
import { getProviderPluginManifestHeader } from "@omniroute/open-sse/config/providerPluginManifestUrl.ts";
|
||||
import { finalizeReadableStream } from "./streamFinalizer";
|
||||
import { stripStaleEncodingHeaders } from "@omniroute/open-sse/utils/upstreamResponseHeaders.ts";
|
||||
import {
|
||||
clearBifrostFailure,
|
||||
getActiveBifrostCooldown,
|
||||
@@ -125,7 +126,7 @@ async function forwardToBifrost(
|
||||
sanitizeErrorMessage(parsed.message),
|
||||
parsed.responseBody
|
||||
);
|
||||
const errorHeaders = new Headers(headers);
|
||||
const errorHeaders = stripStaleEncodingHeaders(headers);
|
||||
errorHeaders.set("Content-Type", "application/json");
|
||||
if (parsed.retryAfterMs && parsed.retryAfterMs > 0) {
|
||||
errorHeaders.set("Retry-After", String(Math.ceil(parsed.retryAfterMs / 1000)));
|
||||
@@ -174,7 +175,8 @@ async function forwardToBifrost(
|
||||
startTime,
|
||||
clientIp,
|
||||
userAgent,
|
||||
upstream.status >= 200 && upstream.status < 300 ? "success" : "error",
|
||||
// upstream.ok is guaranteed true here (the !upstream.ok branch above returns early).
|
||||
"success",
|
||||
upstream.status
|
||||
);
|
||||
|
||||
|
||||
@@ -163,6 +163,59 @@ test("relay route: normalizes HTML 502 from Bifrost into JSON error (Issue #1)",
|
||||
restoreEnv();
|
||||
});
|
||||
|
||||
test("relay route: strips stale upstream content-length before serializing JSON error body", async () => {
|
||||
setupBifrostEnv();
|
||||
const relayToken = seedRelayToken(`relay_err_${Date.now()}`);
|
||||
|
||||
// The upstream Response carries an EXPLICIT content-length for its own (HTML)
|
||||
// body. Once the route replaces that body with a freshly-serialized JSON error,
|
||||
// a stale content-length copied verbatim onto the outgoing Response would
|
||||
// mismatch the real byte length of the new body.
|
||||
globalThis.fetch = async () => {
|
||||
const html = "<html><body>404 page not found, upstream sidecar unreachable</body></html>";
|
||||
return new Response(html, {
|
||||
status: 404,
|
||||
headers: {
|
||||
"content-type": "text/html",
|
||||
"content-length": String(Buffer.byteLength(html)),
|
||||
"content-encoding": "gzip",
|
||||
"transfer-encoding": "chunked",
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const { POST } = await import(
|
||||
`../../../../src/app/api/v1/relay/chat/completions/route.ts?case=${Date.now()}-${Math.random()}`
|
||||
);
|
||||
|
||||
const req = new Request("http://localhost/api/v1/relay/chat/completions", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
authorization: `Bearer ${relayToken.rawToken}`,
|
||||
"content-type": "application/json",
|
||||
"x-request-id": "relay-err-stale-length",
|
||||
},
|
||||
body: JSON.stringify({ model: "gpt-4", messages: [{ role: "user", content: "hi" }] }),
|
||||
});
|
||||
|
||||
const res = await POST(req);
|
||||
assert.equal(res.status, 404);
|
||||
assert.equal(res.headers.get("content-encoding"), null, "stale content-encoding must be stripped");
|
||||
assert.equal(res.headers.get("transfer-encoding"), null, "stale transfer-encoding must be stripped");
|
||||
|
||||
const raw = await res.text();
|
||||
const declaredLength = res.headers.get("content-length");
|
||||
if (declaredLength !== null) {
|
||||
assert.equal(
|
||||
Number(declaredLength),
|
||||
Buffer.byteLength(raw),
|
||||
"content-length, if present, must match the actual serialized JSON error body"
|
||||
);
|
||||
}
|
||||
|
||||
restoreEnv();
|
||||
});
|
||||
|
||||
test("relay route: upstream 401 recorded as analytics error not success (Issue #3)", async () => {
|
||||
setupBifrostEnv();
|
||||
const relayToken = seedRelayToken(`relay_err_${Date.now()}`);
|
||||
|
||||
Reference in New Issue
Block a user