summaryrefslogtreecommitdiffstats
path: root/test/http2-alpn.js
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--test/http2-alpn.js277
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)
+})