forked from nodejs/undici
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore: add http2 alpn test (nodejs#34)
* chore: add http2 alpn test using fastify * chore: update to test https 1 with http2 * chore: update alpn test to return server request alpn protocol and http version * chore: add alpn with body * fix: remove fastify from package json
- Loading branch information
1 parent
70377a8
commit f692cd4
Showing
1 changed file
with
275 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,275 @@ | ||
'use strict' | ||
|
||
const https = require('node:https') | ||
const { once } = require('node:events') | ||
const { createSecureServer } = require('node:http2') | ||
const { readFileSync } = require('node:fs') | ||
const { join } = require('node:path') | ||
const { test } = require('tap') | ||
|
||
const { Client } = require('..') | ||
|
||
// get the crypto fixtures | ||
const key = readFileSync(join(__dirname, 'fixtures', 'key.pem'), 'utf8') | ||
const cert = readFileSync(join(__dirname, 'fixtures', 'cert.pem'), 'utf8') | ||
const ca = readFileSync(join(__dirname, 'fixtures', 'ca.pem'), 'utf8') | ||
|
||
test('Should upgrade to HTTP/2 when HTTPS/1 is available for GET', async (t) => { | ||
t.plan(10) | ||
|
||
const body = [] | ||
const httpsBody = [] | ||
|
||
// create the server and server stream handler | ||
const server = createSecureServer( | ||
{ | ||
key, | ||
cert, | ||
allowHTTP1: true | ||
}, | ||
(req, res) => { | ||
const { socket: { alpnProtocol } } = req.httpVersion === '2.0' ? req.stream.session : req | ||
|
||
// handle http/1 requests | ||
res.writeHead(200, { | ||
'content-type': 'application/json; charset=utf-8', | ||
'x-custom-request-header': req.headers['x-custom-request-header'] || '', | ||
'x-custom-response-header': `using ${req.httpVersion}` | ||
}) | ||
res.end(JSON.stringify({ | ||
alpnProtocol, | ||
httpVersion: req.httpVersion | ||
})) | ||
} | ||
) | ||
|
||
server.listen(0) | ||
await once(server, 'listening') | ||
|
||
// close the server on teardown | ||
t.teardown(server.close.bind(server)) | ||
|
||
// set the port | ||
const port = server.address().port | ||
|
||
// test undici against http/2 | ||
const client = new Client(`https://localhost:${port}`, { | ||
connect: { | ||
ca, | ||
servername: 'agent1' | ||
} | ||
}) | ||
|
||
// close the client on teardown | ||
t.teardown(client.close.bind(client)) | ||
|
||
// make an undici request using where it wants http/2 | ||
const response = await client.request({ | ||
path: '/', | ||
method: 'GET', | ||
headers: { | ||
'x-custom-request-header': 'want 2.0' | ||
} | ||
}) | ||
|
||
response.body.on('data', chunk => { | ||
body.push(chunk) | ||
}) | ||
|
||
await once(response.body, 'end') | ||
|
||
t.equal(response.statusCode, 200) | ||
t.equal(response.headers['content-type'], 'application/json; charset=utf-8') | ||
t.equal(response.headers['x-custom-request-header'], 'want 2.0') | ||
t.equal(response.headers['x-custom-response-header'], 'using 2.0') | ||
t.equal(Buffer.concat(body).toString('utf8'), JSON.stringify({ | ||
alpnProtocol: 'h2', | ||
httpVersion: '2.0' | ||
})) | ||
|
||
// make an https request for http/1 to confirm undici is using http/2 | ||
const httpsOptions = { | ||
ca, | ||
servername: 'agent1', | ||
headers: { | ||
'x-custom-request-header': 'want 1.1' | ||
} | ||
} | ||
|
||
const httpsResponse = await new Promise((resolve, reject) => { | ||
const httpsRequest = https.get(`https://localhost:${port}/`, httpsOptions, (res) => { | ||
res.on('data', (chunk) => { | ||
httpsBody.push(chunk) | ||
}) | ||
|
||
res.on('end', () => { | ||
resolve(res) | ||
}) | ||
}).on('error', (err) => { | ||
reject(err) | ||
}) | ||
|
||
t.teardown(httpsRequest.destroy.bind(httpsRequest)) | ||
}) | ||
|
||
t.equal(httpsResponse.statusCode, 200) | ||
t.equal(httpsResponse.headers['content-type'], 'application/json; charset=utf-8') | ||
t.equal(httpsResponse.headers['x-custom-request-header'], 'want 1.1') | ||
t.equal(httpsResponse.headers['x-custom-response-header'], 'using 1.1') | ||
t.equal(Buffer.concat(httpsBody).toString('utf8'), JSON.stringify({ | ||
alpnProtocol: false, | ||
httpVersion: '1.1' | ||
})) | ||
}) | ||
|
||
test('Should upgrade to HTTP/2 when HTTPS/1 is available for POST', async (t) => { | ||
t.plan(15) | ||
|
||
const requestChunks = [] | ||
const responseBody = [] | ||
|
||
const httpsRequestChunks = [] | ||
const httpsResponseBody = [] | ||
|
||
const expectedBody = 'hello' | ||
const buf = Buffer.from(expectedBody) | ||
const body = new ArrayBuffer(buf.byteLength) | ||
|
||
buf.copy(new Uint8Array(body)) | ||
|
||
// create the server and server stream handler | ||
const server = createSecureServer( | ||
{ | ||
key, | ||
cert, | ||
allowHTTP1: true | ||
}, | ||
(req, res) => { | ||
// use the stream handler for http2 | ||
if (req.httpVersion === '2.0') { | ||
return | ||
} | ||
|
||
const { socket: { alpnProtocol } } = req | ||
|
||
req.on('data', (chunk) => { | ||
httpsRequestChunks.push(chunk) | ||
}) | ||
|
||
req.on('end', () => { | ||
// handle http/1 requests | ||
res.writeHead(201, { | ||
'content-type': 'text/plain; charset=utf-8', | ||
'x-custom-request-header': req.headers['x-custom-request-header'] || '', | ||
'x-custom-alpn-protocol': alpnProtocol | ||
}) | ||
res.end('hello http/1!') | ||
}) | ||
} | ||
) | ||
|
||
server.on('stream', (stream, headers) => { | ||
t.equal(headers[':method'], 'POST') | ||
t.equal(headers[':path'], '/') | ||
t.equal(headers[':scheme'], 'https') | ||
|
||
const { socket: { alpnProtocol } } = stream.session | ||
|
||
stream.on('data', (chunk) => { | ||
requestChunks.push(chunk) | ||
}) | ||
|
||
stream.respond({ | ||
':status': 201, | ||
'content-type': 'text/plain; charset=utf-8', | ||
'x-custom-request-header': headers['x-custom-request-header'] || '', | ||
'x-custom-alpn-protocol': alpnProtocol | ||
}) | ||
|
||
stream.end('hello h2!') | ||
}) | ||
|
||
server.listen(0) | ||
await once(server, 'listening') | ||
|
||
// close the server on teardown | ||
t.teardown(server.close.bind(server)) | ||
|
||
// set the port | ||
const port = server.address().port | ||
|
||
// test undici against http/2 | ||
const client = new Client(`https://localhost:${port}`, { | ||
connect: { | ||
ca, | ||
servername: 'agent1' | ||
} | ||
}) | ||
|
||
// close the client on teardown | ||
t.teardown(client.close.bind(client)) | ||
|
||
// make an undici request using where it wants http/2 | ||
const response = await client.request({ | ||
path: '/', | ||
method: 'POST', | ||
headers: { | ||
'x-custom-request-header': 'want 2.0' | ||
}, | ||
body | ||
}) | ||
|
||
response.body.on('data', (chunk) => { | ||
responseBody.push(chunk) | ||
}) | ||
|
||
await once(response.body, 'end') | ||
|
||
t.equal(response.statusCode, 201) | ||
t.equal(response.headers['content-type'], 'text/plain; charset=utf-8') | ||
t.equal(response.headers['x-custom-request-header'], 'want 2.0') | ||
t.equal(response.headers['x-custom-alpn-protocol'], 'h2') | ||
t.equal(Buffer.concat(responseBody).toString('utf-8'), 'hello h2!') | ||
t.equal(Buffer.concat(requestChunks).toString('utf-8'), expectedBody) | ||
|
||
// make an https request for http/1 to confirm undici is using http/2 | ||
const httpsOptions = { | ||
ca, | ||
servername: 'agent1', | ||
method: 'POST', | ||
headers: { | ||
'content-type': 'text/plain; charset=utf-8', | ||
'content-length': Buffer.byteLength(body), | ||
'x-custom-request-header': 'want 1.1' | ||
} | ||
} | ||
|
||
const httpsResponse = await new Promise((resolve, reject) => { | ||
const httpsRequest = https.request(`https://localhost:${port}/`, httpsOptions, (res) => { | ||
res.on('data', (chunk) => { | ||
httpsResponseBody.push(chunk) | ||
}) | ||
|
||
res.on('end', () => { | ||
resolve(res) | ||
}) | ||
}).on('error', (err) => { | ||
reject(err) | ||
}) | ||
|
||
httpsRequest.on('error', (err) => { | ||
reject(err) | ||
}) | ||
|
||
httpsRequest.write(Buffer.from(body)) | ||
|
||
t.teardown(httpsRequest.destroy.bind(httpsRequest)) | ||
}) | ||
|
||
t.equal(httpsResponse.statusCode, 201) | ||
t.equal(httpsResponse.headers['content-type'], 'text/plain; charset=utf-8') | ||
t.equal(httpsResponse.headers['x-custom-request-header'], 'want 1.1') | ||
t.equal(httpsResponse.headers['x-custom-alpn-protocol'], 'false') | ||
t.equal(Buffer.concat(httpsResponseBody).toString('utf-8'), 'hello http/1!') | ||
t.equal(Buffer.concat(httpsRequestChunks).toString('utf-8'), expectedBody) | ||
}) |