Skip to content

Commit 4c0ef6f

Browse files
committed
Update Node to support multiple single-value headers in H2
1 parent dbc3b6c commit 4c0ef6f

5 files changed

Lines changed: 59 additions & 23 deletions

File tree

package-lock.json

Lines changed: 8 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
"@types/dns-packet": "^5.6.5",
5151
"@types/lodash": "^4.17.0",
5252
"@types/mocha": "^10.0.6",
53-
"@types/node": "^22.15.30",
53+
"@types/node": "^24.0.0",
5454
"@types/ws": "^8.18.1",
5555
"chai": "^5.1.0",
5656
"destroyable-server": "^1.0.1",

src/endpoints/http-index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@ import { EndpointMeta, EndpointGroup } from './groups.js';
66
export type { EndpointMeta, EndpointGroup };
77

88
export type HttpRequest = http.IncomingMessage | http2.Http2ServerRequest;
9-
export type HttpResponse = http.ServerResponse | http2.Http2ServerResponse;
9+
// The @types/node v24 writeHead overloads diverged between http and http2, breaking
10+
// union-type calls. We intersect with a compatible signature so call sites can use it.
11+
export type HttpResponse = (http.ServerResponse | http2.Http2ServerResponse) & {
12+
writeHead(statusCode: number, headers?: http.OutgoingHttpHeaders): HttpResponse;
13+
writeHead(statusCode: number, statusMessage: string, headers?: http.OutgoingHttpHeaders): HttpResponse;
14+
};
1015

1116
export type HttpHandler = (
1217
req: HttpRequest,

src/http-handler.ts

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -257,21 +257,24 @@ export function createHttp2Handler(options: {
257257
rootDomain: string
258258
}) {
259259
const handleRequest = createHttpRequestHandler(options);
260-
const handler = http2.createServer(async (req, res) => {
261-
try {
262-
await handleRequest(req, res);
263-
} catch (e) {
264-
console.error(e);
265-
266-
if (res.closed) return;
267-
else if (res.headersSent) {
268-
res.destroy();
269-
} else {
270-
res.writeHead(500);
271-
res.end('HTTP handler failed');
260+
const handler = http2.createServer(
261+
{ strictSingleValueFields: false } as http2.ServerOptions,
262+
async (req, res) => {
263+
try {
264+
await handleRequest(req, res);
265+
} catch (e) {
266+
console.error(e);
267+
268+
if (res.closed) return;
269+
else if (res.headersSent) {
270+
res.destroy();
271+
} else {
272+
res.writeHead(500);
273+
res.end('HTTP handler failed');
274+
}
272275
}
273276
}
274-
});
277+
);
275278

276279
handler.on('error', (err) => console.error('HTTP handler error', err));
277280

test/response-headers.spec.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as net from 'net';
2+
import * as http2 from 'http2';
23
import { expect } from 'chai';
34
import { DestroyableServer, makeDestroyable } from 'destroyable-server';
45

@@ -57,4 +58,31 @@ describe("Response-headers endpoint", () => {
5758
expect(response.status).to.equal(200);
5859
expect(await response.json()).to.deep.equal({});
5960
});
61+
62+
it("returns duplicate single-value headers over HTTP/2", async () => {
63+
const client = http2.connect(`http://localhost:${serverPort}`, { strictSingleValueFields: false });
64+
try {
65+
const request = client.request({
66+
':path': '/response-headers?X-Content-Type-Options=b&X-Content-Type-Options=c',
67+
':method': 'GET'
68+
});
69+
request.end();
70+
71+
const { status, rawHeaders } = await new Promise<{
72+
status: number,
73+
rawHeaders: string[]
74+
}>((resolve) =>
75+
request.on('response', (headers, _flags, rawHeaders) => resolve({
76+
status: headers[':status'] as number,
77+
rawHeaders
78+
}))
79+
);
80+
81+
expect(status).to.equal(200);
82+
expect(rawHeaders).to.include.members(['x-content-type-options', 'b']);
83+
expect(rawHeaders).to.include.members(['x-content-type-options', 'c']);
84+
} finally {
85+
client.close();
86+
}
87+
});
6088
});

0 commit comments

Comments
 (0)