feat: enhance HTTP/2 support by ensuring Host header is set and adding multiplexed request tests
This commit is contained in:
@@ -255,81 +255,62 @@ tap.test('HTTPS with TLS termination: multiple requests through TLS', async (too
|
||||
// ===========================================================================
|
||||
// 5. TLS ALPN negotiation verification
|
||||
// ===========================================================================
|
||||
tap.test('TLS ALPN negotiation: h2 advertised, h1.1 functional', async (tools) => {
|
||||
tap.test('HTTP/2 end-to-end: ALPN h2 with multiplexed requests', async (tools) => {
|
||||
tools.timeout(15000);
|
||||
|
||||
// Verify the Rust TLS layer advertises h2 via ALPN by inspecting a raw TLS socket
|
||||
const alpnProtocol = await new Promise<string>((resolve, reject) => {
|
||||
const socket = tls.connect(
|
||||
{
|
||||
host: 'localhost',
|
||||
port: PROXY_HTTPS_PORT,
|
||||
ALPNProtocols: ['h2', 'http/1.1'],
|
||||
rejectUnauthorized: false,
|
||||
servername: 'localhost',
|
||||
},
|
||||
() => {
|
||||
const proto = (socket as any).alpnProtocol || 'none';
|
||||
socket.destroy();
|
||||
resolve(proto);
|
||||
},
|
||||
);
|
||||
socket.on('error', reject);
|
||||
socket.setTimeout(5000, () => {
|
||||
socket.destroy(new Error('timeout'));
|
||||
});
|
||||
// Connect an HTTP/2 session over TLS
|
||||
const session = http2.connect(`https://localhost:${PROXY_HTTPS_PORT}`, {
|
||||
rejectUnauthorized: false,
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
session.on('connect', () => resolve());
|
||||
session.on('error', reject);
|
||||
setTimeout(() => reject(new Error('h2 connect timeout')), 5000);
|
||||
});
|
||||
|
||||
// Verify ALPN negotiated h2
|
||||
const alpnProtocol = (session.socket as tls.TLSSocket).alpnProtocol;
|
||||
console.log(`TLS ALPN negotiated protocol: ${alpnProtocol}`);
|
||||
// Rust advertises h2+http/1.1; the negotiated protocol should be one of them
|
||||
expect(['h2', 'http/1.1'].includes(alpnProtocol)).toBeTrue();
|
||||
expect(alpnProtocol).toEqual('h2');
|
||||
|
||||
// Now try an actual HTTP/2 session — Rust may or may not support h2 end-to-end
|
||||
let h2Supported = false;
|
||||
try {
|
||||
const session = http2.connect(`https://localhost:${PROXY_HTTPS_PORT}`, {
|
||||
rejectUnauthorized: false,
|
||||
});
|
||||
// Send 5 multiplexed POST requests on the same h2 session
|
||||
const REQUEST_COUNT = 5;
|
||||
const promises: Promise<{ status: number; body: string }>[] = [];
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
session.on('connect', () => resolve());
|
||||
session.on('error', reject);
|
||||
setTimeout(() => reject(new Error('h2 connect timeout')), 5000);
|
||||
});
|
||||
for (let i = 0; i < REQUEST_COUNT; i++) {
|
||||
promises.push(
|
||||
new Promise<{ status: number; body: string }>((resolve, reject) => {
|
||||
const reqStream = session.request({
|
||||
':method': 'POST',
|
||||
':path': '/echo',
|
||||
'content-type': 'text/plain',
|
||||
});
|
||||
|
||||
// If we get here, h2 session connected. Try a request.
|
||||
const result = await new Promise<{ status: number; body: string }>((resolve, reject) => {
|
||||
const reqStream = session.request({
|
||||
':method': 'POST',
|
||||
':path': '/echo',
|
||||
'content-type': 'text/plain',
|
||||
});
|
||||
let data = '';
|
||||
let status = 0;
|
||||
|
||||
let data = '';
|
||||
let status = 0;
|
||||
|
||||
reqStream.on('response', (headers) => {
|
||||
status = headers[':status'] as number;
|
||||
});
|
||||
reqStream.on('data', (chunk: Buffer) => {
|
||||
data += chunk.toString();
|
||||
});
|
||||
reqStream.on('end', () => resolve({ status, body: data }));
|
||||
reqStream.on('error', reject);
|
||||
reqStream.end('h2-test');
|
||||
});
|
||||
|
||||
expect(result.status).toEqual(200);
|
||||
expect(result.body).toEqual('echo:h2-test');
|
||||
h2Supported = true;
|
||||
|
||||
await new Promise<void>((resolve) => session.close(() => resolve()));
|
||||
} catch {
|
||||
// h2 end-to-end not yet supported — that's OK, h1.1 over TLS is verified above
|
||||
console.log('HTTP/2 end-to-end not yet supported by Rust engine (expected)');
|
||||
reqStream.on('response', (headers) => {
|
||||
status = headers[':status'] as number;
|
||||
});
|
||||
reqStream.on('data', (chunk: Buffer) => {
|
||||
data += chunk.toString();
|
||||
});
|
||||
reqStream.on('end', () => resolve({ status, body: data }));
|
||||
reqStream.on('error', reject);
|
||||
reqStream.end(`h2-msg-${i}`);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
console.log(`HTTP/2 full support: ${h2Supported ? 'yes' : 'no (ALPN advertised but h2 framing not handled)'}`);
|
||||
const results = await Promise.all(promises);
|
||||
for (let i = 0; i < REQUEST_COUNT; i++) {
|
||||
expect(results[i].status).toEqual(200);
|
||||
expect(results[i].body).toEqual(`echo:h2-msg-${i}`);
|
||||
}
|
||||
|
||||
await new Promise<void>((resolve) => session.close(() => resolve()));
|
||||
console.log(`HTTP/2 end-to-end: ${REQUEST_COUNT} multiplexed requests completed successfully`);
|
||||
});
|
||||
|
||||
// ===========================================================================
|
||||
|
||||
Reference in New Issue
Block a user