diff options
Diffstat (limited to 'test/http2-alpn.js')
-rw-r--r-- | test/http2-alpn.js | 277 |
1 files changed, 277 insertions, 0 deletions
diff --git a/test/http2-alpn.js b/test/http2-alpn.js new file mode 100644 index 0000000..04b8cb6 --- /dev/null +++ b/test/http2-alpn.js @@ -0,0 +1,277 @@ +'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' + }, + allowH2: true + }) + + // 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' + }, + allowH2: true + }) + + // 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) +}) |