Skip to content

Commit

Permalink
chore: add http2 alpn test (nodejs#34)
Browse files Browse the repository at this point in the history
* 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
mkaufmaner authored and metcoder95 committed Jun 21, 2023
1 parent 70377a8 commit f692cd4
Showing 1 changed file with 275 additions and 0 deletions.
275 changes: 275 additions & 0 deletions test/http2-alpn.js
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)
})

0 comments on commit f692cd4

Please sign in to comment.