fix(api): add explicit HEAD handler for /v1/models to prevent ~6s hang (#6400) (#6517)

fix(api): add explicit HEAD handler for /v1/models to prevent ~6s hang (#6400) Integrated into release/v3.8.47. (thanks @chirag127)
This commit is contained in:
Chirag Singhal
2026-07-08 04:40:31 +05:30
committed by GitHub
parent 1a87c90ff7
commit 529ebde7a3
3 changed files with 76 additions and 1 deletions

View File

@@ -9,6 +9,18 @@ export async function OPTIONS() {
return handleCorsOptions();
}
/**
* HEAD /v1/models/{model} — availability probe (RFC 9110 §9.3.2).
* Prevents Next.js auto-derived HEAD from streaming the full GET body,
* which caused ~6s hangs for SDK/gateway health probes (#6400).
*/
export async function HEAD() {
return new Response(null, {
status: 200,
headers: { "content-type": "application/json" },
});
}
/**
* GET /v1/models/{model} — OpenAI-compatible single-model retrieval (#4674).
*

View File

@@ -6,12 +6,27 @@ import { getUnifiedModelsResponse } from "./catalog";
export async function OPTIONS() {
return new Response(null, {
headers: {
"Access-Control-Allow-Methods": "GET, OPTIONS",
"Access-Control-Allow-Methods": "GET, HEAD, OPTIONS",
"Access-Control-Allow-Headers": "*",
},
});
}
/**
* HEAD /v1/models - availability probe (RFC 9110 §9.3.2: HEAD returns headers only)
*
* Explicit handler prevents Next.js auto-derived HEAD from streaming the full
* GET body (200+ providers enumerated on-demand), which caused ~6s hang for
* clients like the OpenAI SDK that use HEAD as a health/preflight probe.
* See: https://github.com/diegosouzapw/OmniRoute/issues/6400
*/
export async function HEAD() {
return new Response(null, {
status: 200,
headers: { "content-type": "application/json" },
});
}
/**
* GET /v1/models - OpenAI compatible models list
*/

View File

@@ -0,0 +1,48 @@
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import * as modelsRoute from "../../src/app/api/v1/models/route";
/**
* Regression test for issue #6400.
*
* Before the fix, `src/app/api/v1/models/route.ts` exported only `OPTIONS` and
* `GET`. Next.js 16 App Router auto-derives `HEAD` from `GET` when no explicit
* handler exists, and the derived `HEAD` streams the full `GET` body (which
* the client discards). Because `getUnifiedModelsResponse()` enumerates 200+
* providers on-demand, the body stream stayed open ~6s — clients issuing HEAD
* as an availability probe (OpenAI SDK, openai-python, httpx, gateway
* health-checkers) stalled until their timeout fired.
*
* RFC 9110 §9.3.2: HEAD MUST close after the headers.
*
* Fix: explicit `HEAD` handler that returns `{ status: 200, body: null }`, and
* `OPTIONS` advertises `HEAD` in `Access-Control-Allow-Methods`.
*/
describe("issue #6400 — HEAD /v1/models returns immediately", () => {
it("exports an explicit HEAD handler", () => {
assert.equal(
typeof (modelsRoute as { HEAD?: unknown }).HEAD,
"function",
"HEAD export missing — Next.js will auto-derive from GET and stream the body"
);
});
it("HEAD returns 200 with a null body (no streaming)", async () => {
const head = (modelsRoute as unknown as {
HEAD: () => Promise<Response>;
}).HEAD;
const response = await head();
assert.equal(response.status, 200);
assert.equal(response.body, null, "HEAD body must be null per RFC 9110 §9.3.2");
});
it("OPTIONS advertises HEAD in Access-Control-Allow-Methods", async () => {
const response = await modelsRoute.OPTIONS();
const methods = response.headers.get("Access-Control-Allow-Methods") ?? "";
assert.ok(
/\bHEAD\b/.test(methods),
`expected HEAD in Access-Control-Allow-Methods, got: ${methods}`
);
});
});