Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add H2 support #2061

Merged
merged 58 commits into from
Sep 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
c635305
feat: port H2 work with latest main
metcoder95 Apr 12, 2023
0a383ce
fix: linting errors
metcoder95 Apr 19, 2023
e83ff26
refactor: adjust support for headers and set testing
metcoder95 Apr 19, 2023
4360f97
test: add testing for h2
metcoder95 Apr 21, 2023
385a540
refactor: make http2 session handle shorter
metcoder95 Apr 26, 2023
4da1170
feat: add support for sending body over http2
metcoder95 May 3, 2023
690194e
feat: ensure support for streams over H2
metcoder95 May 10, 2023
5c3825e
refactor: remove noisy logs
metcoder95 May 10, 2023
73ab219
feat: support 100 continue
metcoder95 May 12, 2023
ca5855e
feat: support for iterators
metcoder95 May 17, 2023
69e093b
feat: add support for Blobs
metcoder95 May 17, 2023
2c5a583
refactor: adapt contracts to h2 support
metcoder95 May 17, 2023
5e4dae1
refactor: cleanup
metcoder95 May 19, 2023
3b9b6b6
feat: support for content-length
metcoder95 May 19, 2023
7d27bf6
refactor: body write
metcoder95 May 19, 2023
986d5ea
test: refactor check continue test
metcoder95 May 24, 2023
add512b
fix: bad check for headers
metcoder95 May 24, 2023
4ad0eb0
fix: bad change
metcoder95 May 24, 2023
d0d9a0a
chore: add http2 alpn test (#34)
mkaufmaner May 30, 2023
0da1933
refactor: remove leftover
metcoder95 May 31, 2023
916d62e
test: ensure dispatch feature
metcoder95 Jun 2, 2023
625b008
feat(h2): support connect
metcoder95 Jun 21, 2023
55d4a56
fix: pass signal down the road
metcoder95 Jun 21, 2023
d909fd7
test: ensure stream works as expected
metcoder95 Jun 23, 2023
e67372e
test: ensure pipeline works as expected
metcoder95 Jun 23, 2023
1919c25
test: ensure upgrade fails
metcoder95 Jun 23, 2023
0475988
test: ensure destroy works as expected
metcoder95 Jun 23, 2023
00187b7
feat: allow to disable H2 calls upon request
metcoder95 Jun 28, 2023
c18c6bf
fix: linting
metcoder95 Jun 28, 2023
4758fc2
feat: support GOAWAY frame (server-side)
metcoder95 Jul 3, 2023
cae8657
refactor; use h2 constants
metcoder95 Jul 4, 2023
d9ea2cf
feat: initial shape of concurrent stream handling
metcoder95 Jul 12, 2023
bb83cdf
refactor: header processing
metcoder95 Jul 14, 2023
ff52023
chore: http/2 benchmark (#35)
mkaufmaner Jul 17, 2023
5b7f6f5
refactor: adjust accordingly to review
metcoder95 Jul 19, 2023
a8d2665
fix: add missing error handler for socket
metcoder95 Jul 21, 2023
0cadb9f
refactor: headers handling
metcoder95 Jul 26, 2023
c89c003
feat: initial concurrent stream support
metcoder95 Jul 28, 2023
d72aa31
fix: lint
metcoder95 Jul 28, 2023
16e1334
refactor: adjust several pieces
metcoder95 Aug 11, 2023
92be000
fix: support h2 headers for fetch
metcoder95 Aug 16, 2023
837e629
feat: enhance h2 for fetch
metcoder95 Aug 16, 2023
bf2383f
refactor: apply review suggestions
metcoder95 Aug 16, 2023
5908efc
refactor: set allowh2 to false
metcoder95 Aug 16, 2023
efa11b9
fix: linting
metcoder95 Aug 16, 2023
4d5b3ca
refactor: implement kHTTPConnVersion symbol
metcoder95 Aug 18, 2023
e1b794c
test: adjust testing
metcoder95 Aug 18, 2023
4bbd684
feat: buil factory
metcoder95 Aug 18, 2023
d27be8a
fix: rebase
metcoder95 Aug 20, 2023
37c73ba
feat: enhance TS types for maxConcurrent streams
metcoder95 Aug 20, 2023
224c26e
test: move fetch tests to fetch folder
metcoder95 Aug 23, 2023
904f334
feat: add experimental warning
metcoder95 Aug 23, 2023
bbd1ffe
test: refactor suite
metcoder95 Aug 23, 2023
718c3e9
Merge branch 'main' into http2
metcoder95 Aug 24, 2023
e44b078
refactor: apply several changes
metcoder95 Aug 25, 2023
75f6a2c
test: split tests between v20 and lower
metcoder95 Aug 30, 2023
b0a9667
Merge branch 'main' into http2
metcoder95 Aug 30, 2023
1433624
Merge branch 'main' into http2
mcollina Sep 8, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
306 changes: 306 additions & 0 deletions benchmarks/benchmark-http2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,306 @@
'use strict'

const { connect } = require('http2')
const { createSecureContext } = require('tls')
const os = require('os')
const path = require('path')
const { readFileSync } = require('fs')
const { table } = require('table')
const { Writable } = require('stream')
const { WritableStream } = require('stream/web')
const { isMainThread } = require('worker_threads')

const { Pool, Client, fetch, Agent, setGlobalDispatcher } = require('..')

const ca = readFileSync(path.join(__dirname, '..', 'test', 'fixtures', 'ca.pem'), 'utf8')
const servername = 'agent1'

const iterations = (parseInt(process.env.SAMPLES, 10) || 10) + 1
const errorThreshold = parseInt(process.env.ERROR_THRESHOLD, 10) || 3
const connections = parseInt(process.env.CONNECTIONS, 10) || 50
const pipelining = parseInt(process.env.PIPELINING, 10) || 10
const parallelRequests = parseInt(process.env.PARALLEL, 10) || 100
const headersTimeout = parseInt(process.env.HEADERS_TIMEOUT, 10) || 0
const bodyTimeout = parseInt(process.env.BODY_TIMEOUT, 10) || 0
const dest = {}

if (process.env.PORT) {
dest.port = process.env.PORT
dest.url = `https://localhost:${process.env.PORT}`
} else {
dest.url = 'https://localhost'
dest.socketPath = path.join(os.tmpdir(), 'undici.sock')
}

const httpsBaseOptions = {
ca,
servername,
protocol: 'https:',
hostname: 'localhost',
method: 'GET',
path: '/',
query: {
frappucino: 'muffin',
goat: 'scone',
pond: 'moose',
foo: ['bar', 'baz', 'bal'],
bool: true,
numberKey: 256
},
...dest
}

const http2ClientOptions = {
secureContext: createSecureContext({ ca }),
servername
}

const undiciOptions = {
path: '/',
method: 'GET',
headersTimeout,
bodyTimeout
}

const Class = connections > 1 ? Pool : Client
const dispatcher = new Class(httpsBaseOptions.url, {
allowH2: true,
pipelining,
connections,
connect: {
rejectUnauthorized: false,
ca,
servername
},
...dest
})

setGlobalDispatcher(new Agent({
allowH2: true,
pipelining,
connections,
connect: {
rejectUnauthorized: false,
ca,
servername
}
}))

class SimpleRequest {
constructor (resolve) {
this.dst = new Writable({
write (chunk, encoding, callback) {
callback()
}
}).on('finish', resolve)
}

onConnect (abort) { }

onHeaders (statusCode, headers, resume) {
this.dst.on('drain', resume)
}

onData (chunk) {
return this.dst.write(chunk)
}

onComplete () {
this.dst.end()
}

onError (err) {
throw err
}
}

function makeParallelRequests (cb) {
return Promise.all(Array.from(Array(parallelRequests)).map(() => new Promise(cb)))
}

function printResults (results) {
// Sort results by least performant first, then compare relative performances and also printing padding
let last

const rows = Object.entries(results)
// If any failed, put on the top of the list, otherwise order by mean, ascending
.sort((a, b) => (!a[1].success ? -1 : b[1].mean - a[1].mean))
.map(([name, result]) => {
if (!result.success) {
return [name, result.size, 'Errored', 'N/A', 'N/A']
}

// Calculate throughput and relative performance
const { size, mean, standardError } = result
const relative = last !== 0 ? (last / mean - 1) * 100 : 0

// Save the slowest for relative comparison
if (typeof last === 'undefined') {
last = mean
}

return [
name,
size,
`${((connections * 1e9) / mean).toFixed(2)} req/sec`,
`± ${((standardError / mean) * 100).toFixed(2)} %`,
relative > 0 ? `+ ${relative.toFixed(2)} %` : '-'
]
})

console.log(results)

// Add the header row
rows.unshift(['Tests', 'Samples', 'Result', 'Tolerance', 'Difference with slowest'])

return table(rows, {
columns: {
0: {
alignment: 'left'
},
1: {
alignment: 'right'
},
2: {
alignment: 'right'
},
3: {
alignment: 'right'
},
4: {
alignment: 'right'
}
},
drawHorizontalLine: (index, size) => index > 0 && index < size,
border: {
bodyLeft: '│',
bodyRight: '│',
bodyJoin: '│',
joinLeft: '|',
joinRight: '|',
joinJoin: '|'
}
})
}

const experiments = {
'http2 - request' () {
return makeParallelRequests(resolve => {
connect(dest.url, http2ClientOptions, (session) => {
const headers = {
':path': '/',
':method': 'GET',
':scheme': 'https',
':authority': `localhost:${dest.port}`
}

const request = session.request(headers)

request.pipe(
new Writable({
write (chunk, encoding, callback) {
callback()
}
})
).on('finish', resolve)
})
})
},
'undici - pipeline' () {
return makeParallelRequests(resolve => {
dispatcher
.pipeline(undiciOptions, data => {
return data.body
})
.end()
.pipe(
new Writable({
write (chunk, encoding, callback) {
callback()
}
})
)
.on('finish', resolve)
})
},
'undici - request' () {
return makeParallelRequests(resolve => {
try {
dispatcher.request(undiciOptions).then(({ body }) => {
body
.pipe(
new Writable({
write (chunk, encoding, callback) {
callback()
}
})
)
.on('error', (err) => {
console.log('undici - request - dispatcher.request - body - error', err)
})
.on('finish', () => {
resolve()
})
})
} catch (err) {
console.error('undici - request - dispatcher.request - requestCount', err)
}
})
},
'undici - stream' () {
return makeParallelRequests(resolve => {
return dispatcher
.stream(undiciOptions, () => {
return new Writable({
write (chunk, encoding, callback) {
callback()
}
})
})
.then(resolve)
})
},
'undici - dispatch' () {
return makeParallelRequests(resolve => {
dispatcher.dispatch(undiciOptions, new SimpleRequest(resolve))
})
}
}

if (process.env.PORT) {
// fetch does not support the socket
experiments['undici - fetch'] = () => {
return makeParallelRequests(resolve => {
fetch(dest.url, {}).then(res => {
res.body.pipeTo(new WritableStream({ write () { }, close () { resolve() } }))
}).catch(console.log)
})
}
}

async function main () {
const { cronometro } = await import('cronometro')

cronometro(
experiments,
{
iterations,
errorThreshold,
print: false
},
(err, results) => {
if (err) {
throw err
}

console.log(printResults(results))
dispatcher.destroy()
}
)
}

if (isMainThread) {
main()
} else {
module.exports = main
}