Skip to content

Commit e305a64

Browse files
rustyconoverclaude
andcommitted
Add Access-Control-Max-Age header on CORS preflight (v0.6.4)
New corsMaxAge option in HttpHandlerOptions sets Access-Control-Max-Age on OPTIONS responses. Default is 7200 seconds (2 hours). Pass null to omit. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent cf42323 commit e305a64

8 files changed

Lines changed: 69 additions & 4 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[ 952ms] [WARNING] Mixed Content: The page at 'https://www.google.com/search?q=ll&oq=ll&gs_lcrp=EgZjaHJvbWUyBggAEEUYOTIGCAEQRRg8MgYIAhBFGDwyBggDEEUYPNIBBzEyM2owajKoAgCwAgA&sourceid=chrome&ie=UTF-8&sei=2TGbaddx0uLk2g_l8f34Dw' was loaded over HTTPS, but requested an insecure element 'http://img.youtube.com/vi/mzZ2J58YSaM/hqdefault.jpg'. This request was automatically upgraded to HTTPS, For more information see https://blog.chromium.org/2019/10/no-more-mixed-messages-about-https.html @ https://www.google.com/search?q=ll&oq=ll&gs_lcrp=EgZjaHJvbWUyBggAEEUYOTIGCAEQRRg8MgYIAhBFGDwyBggDEEUYPNIBBzEyM2owajKoAgCwAgA&sourceid=chrome&ie=UTF-8&sei=2TGbaddx0uLk2g_l8f34Dw:0
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[ 86ms] [ERROR] Failed to load resource: the server responded with a status of 404 (File not found) @ http://127.0.0.1:18923/favicon.ico:0

.vscode/settings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"python-envs.pythonProjects": []
3+
}
15.2 KB
Binary file not shown.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@query-farm/vgi-rpc",
3-
"version": "0.6.3",
3+
"version": "0.6.4",
44
"license": "Apache-2.0",
55
"homepage": "https://vgi-rpc-typescript.query.farm",
66
"repository": {

src/http/handler.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export function createHttpHandler(
4242
const signingKey = options?.signingKey ?? randomBytes(32);
4343
const tokenTtl = options?.tokenTtl ?? 3600;
4444
const corsOrigins = options?.corsOrigins;
45+
const corsMaxAge = options?.corsMaxAge === undefined ? 7200 : options.corsMaxAge;
4546
const maxRequestBytes = options?.maxRequestBytes;
4647
const maxStreamResponseBytes = options?.maxStreamResponseBytes;
4748
const serverId = options?.serverId ?? crypto.randomUUID().replace(/-/g, "").slice(0, 12);
@@ -81,12 +82,15 @@ export function createHttpHandler(
8182
externalLocation,
8283
};
8384

84-
function addCorsHeaders(headers: Headers): void {
85+
function addCorsHeaders(headers: Headers, isOptions = false): void {
8586
if (corsOrigins) {
8687
headers.set("Access-Control-Allow-Origin", corsOrigins);
8788
headers.set("Access-Control-Allow-Methods", "POST, OPTIONS");
8889
headers.set("Access-Control-Allow-Headers", "Content-Type, Authorization");
8990
headers.set("Access-Control-Expose-Headers", "WWW-Authenticate, X-Request-ID, X-VGI-Content-Encoding");
91+
if (isOptions && corsMaxAge != null) {
92+
headers.set("Access-Control-Max-Age", String(corsMaxAge));
93+
}
9094
}
9195
}
9296

@@ -132,7 +136,7 @@ export function createHttpHandler(
132136
if (request.method === "OPTIONS") {
133137
if (path === `${prefix}/__capabilities__`) {
134138
const headers = new Headers();
135-
addCorsHeaders(headers);
139+
addCorsHeaders(headers, true);
136140
if (maxRequestBytes != null) {
137141
headers.set("VGI-Max-Request-Bytes", String(maxRequestBytes));
138142
}
@@ -141,7 +145,7 @@ export function createHttpHandler(
141145

142146
if (corsOrigins) {
143147
const headers = new Headers();
144-
addCorsHeaders(headers);
148+
addCorsHeaders(headers, true);
145149
return new Response(null, { status: 204, headers });
146150
}
147151

src/http/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ export interface HttpHandlerOptions {
1515
tokenTtl?: number;
1616
/** CORS allowed origins. If set, CORS headers are added to all responses. */
1717
corsOrigins?: string;
18+
/** Access-Control-Max-Age value in seconds for preflight OPTIONS responses. Default: 7200 (2 hours). null omits the header. */
19+
corsMaxAge?: number | null;
1820
/** Maximum request body size in bytes. Advertised via VGI-Max-Request-Bytes header. */
1921
maxRequestBytes?: number;
2022
/** Maximum bytes before a producer stream emits a continuation token. */

test/http/handler.test.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,60 @@ describe("HTTP Handler", () => {
338338
);
339339
});
340340

341+
test("CORS preflight includes Access-Control-Max-Age by default", async () => {
342+
const handlerWithCors = createHttpHandler(makeTestProtocol(), {
343+
prefix: "/vgi",
344+
corsOrigins: "*",
345+
});
346+
347+
const res = await handlerWithCors(new Request(`${BASE}/vgi/add`, { method: "OPTIONS" }));
348+
expect(res.headers.get("Access-Control-Max-Age")).toBe("7200");
349+
});
350+
351+
test("CORS preflight custom corsMaxAge", async () => {
352+
const handlerWithCors = createHttpHandler(makeTestProtocol(), {
353+
prefix: "/vgi",
354+
corsOrigins: "*",
355+
corsMaxAge: 3600,
356+
});
357+
358+
const res = await handlerWithCors(new Request(`${BASE}/vgi/add`, { method: "OPTIONS" }));
359+
expect(res.headers.get("Access-Control-Max-Age")).toBe("3600");
360+
});
361+
362+
test("CORS preflight corsMaxAge=null omits header", async () => {
363+
const handlerWithCors = createHttpHandler(makeTestProtocol(), {
364+
prefix: "/vgi",
365+
corsOrigins: "*",
366+
corsMaxAge: null,
367+
});
368+
369+
const res = await handlerWithCors(new Request(`${BASE}/vgi/add`, { method: "OPTIONS" }));
370+
expect(res.headers.get("Access-Control-Max-Age")).toBeNull();
371+
});
372+
373+
test("Access-Control-Max-Age not set on non-OPTIONS", async () => {
374+
const handlerWithCors = createHttpHandler(makeTestProtocol(), {
375+
prefix: "/vgi",
376+
corsOrigins: "*",
377+
serverId: "max-age-test",
378+
});
379+
380+
const paramSchema = new Schema([new Field("a", new Float64(), false), new Field("b", new Float64(), false)]);
381+
const body = buildRequestIpc(paramSchema, { a: [1], b: [2] }, "add");
382+
383+
const res = await handlerWithCors(
384+
new Request(`${BASE}/vgi/add`, {
385+
method: "POST",
386+
headers: { "Content-Type": ARROW_CONTENT_TYPE },
387+
body,
388+
}),
389+
);
390+
391+
expect(res.status).toBe(200);
392+
expect(res.headers.get("Access-Control-Max-Age")).toBeNull();
393+
});
394+
341395
// -- Producer stream --
342396

343397
test("producer stream init", async () => {

0 commit comments

Comments
 (0)