Socket
Socket
Sign inDemoInstall

undici

Package Overview
Dependencies
Maintainers
2
Versions
212
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

undici - npm Package Compare versions

Comparing version 2.0.0 to 2.0.1

lib/abort-signal.js

169

benchmarks/index.js

@@ -5,9 +5,5 @@ 'use strict'

const Benchmark = require('benchmark')
const undici = require('..')
const { Client } = require('..')
// # Start the h2o server (in h2o repository)
// # Then change the port below to 8080
// h2o -c examples/h2o/h2o.conf
//
// # Alternatively start the Node.js server
// # Start the Node.js server
// node benchmarks/server.js

@@ -21,8 +17,8 @@ //

hostname: 'localhost',
socketPath: '/var/tmp/undici.sock',
method: 'GET',
path: '/',
port: 3009,
agent: new http.Agent({
keepAlive: true,
maxSockets: 100
maxSockets: 1
})

@@ -37,7 +33,11 @@ }

const pool = undici(`http://${httpOptions.hostname}:${httpOptions.port}`, {
connections: 100,
pipelining: 10
const client = new Client(`http://${httpOptions.hostname}`, {
pipelining: 10,
socketPath: '/var/tmp/undici.sock'
})
client.on('disconnect', (err) => {
throw err
})
const suite = new Benchmark.Suite()

@@ -51,14 +51,13 @@

fn: deferred => {
http.get(httpOptions, response => {
const stream = new Writable({
write (chunk, encoding, callback) {
callback()
}
Promise.all(Array.from(Array(10)).map(() => new Promise((resolve) => {
http.get(httpOptions, (res) => {
res
.pipe(new Writable({
write (chunk, encoding, callback) {
callback()
}
}))
.on('close', resolve)
})
stream.once('finish', () => {
deferred.resolve()
})
response.pipe(stream)
})
}))).then(() => deferred.resolve())
}

@@ -69,15 +68,15 @@ })

fn: deferred => {
pool
.pipeline(undiciOptions, data => {
return data.body
})
.end()
.pipe(new Writable({
write (chunk, encoding, callback) {
callback()
}
}))
.once('finish', () => {
deferred.resolve()
})
Promise.all(Array.from(Array(10)).map(() => new Promise((resolve) => {
client
.pipeline(undiciOptions, data => {
return data.body
})
.end()
.pipe(new Writable({
write (chunk, encoding, callback) {
callback()
}
}))
.on('close', resolve)
}))).then(() => deferred.resolve())
}

@@ -88,18 +87,15 @@ })

fn: deferred => {
pool.request(undiciOptions, (error, { body }) => {
if (error) {
throw error
}
const stream = new Writable({
write (chunk, encoding, callback) {
callback()
}
})
stream.once('finish', () => {
deferred.resolve()
})
body.pipe(stream)
})
Promise.all(Array.from(Array(10)).map(() => new Promise((resolve) => {
client
.request(undiciOptions)
.then(({ body }) => {
body
.pipe(new Writable({
write (chunk, encoding, callback) {
callback()
}
}))
.on('close', resolve)
})
}))).then(() => deferred.resolve())
}

@@ -110,18 +106,11 @@ })

fn: deferred => {
pool.stream(undiciOptions, () => {
const stream = new Writable({
write (chunk, encoding, callback) {
callback()
}
Promise.all(Array.from(Array(10)).map(() => {
return client.stream(undiciOptions, () => {
return new Writable({
write (chunk, encoding, callback) {
callback()
}
})
})
stream.once('finish', () => {
deferred.resolve()
})
return stream
}, error => {
if (error) {
throw error
}
})
})).then(() => deferred.resolve())
}

@@ -132,11 +121,5 @@ })

fn: deferred => {
const stream = new Writable({
write (chunk, encoding, callback) {
callback()
}
})
stream.once('finish', () => {
deferred.resolve()
})
pool.dispatch(undiciOptions, new SimpleRequest(stream))
Promise.all(Array.from(Array(10)).map(() => new Promise((resolve) => {
client.dispatch(undiciOptions, new SimpleRequest(resolve))
}))).then(() => deferred.resolve())
}

@@ -147,13 +130,21 @@ })

fn: deferred => {
pool.dispatch(undiciOptions, new NoopRequest(deferred))
Promise.all(Array.from(Array(10)).map(() => new Promise((resolve) => {
client.dispatch(undiciOptions, new NoopRequest(resolve))
}))).then(() => deferred.resolve())
}
})
.on('cycle', event => {
console.log(String(event.target))
.on('cycle', ({ target }) => {
// Multiply results by 10x to get opts/sec since we do 10 requests
// per run.
target.hz *= 10
console.log(String(target))
})
.on('complete', () => {
client.destroy()
})
.run()
class NoopRequest {
constructor (deferred) {
this.deferred = deferred
constructor (resolve) {
this.resolve = resolve
}

@@ -174,9 +165,17 @@

onComplete (trailers) {
this.deferred.resolve()
this.resolve()
}
onError (err) {
throw err
}
}
class SimpleRequest {
constructor (dst) {
this.dst = dst
constructor (resolve) {
this.dst = new Writable({
write (chunk, encoding, callback) {
callback()
}
}).on('close', resolve)
}

@@ -198,2 +197,6 @@

}
onError (err) {
throw err
}
}

@@ -7,2 +7,2 @@ 'use strict'

res.end('hello world')
}).listen(3009)
}).listen('/var/tmp/undici.sock')
'use strict'
const {
InvalidArgumentError,
RequestAbortedError
} = require('./core/errors')
const { InvalidArgumentError } = require('./core/errors')
const { AsyncResource } = require('async_hooks')
const util = require('./core/util')
const { addSignal, removeSignal } = require('./abort-signal')

@@ -28,11 +26,3 @@ class ConnectHandler extends AsyncResource {

if (signal) {
util.addListener(signal, () => {
if (this.abort) {
this.abort()
} else {
this.onError(new RequestAbortedError())
}
})
}
addSignal(this, signal)
}

@@ -51,2 +41,4 @@

removeSignal(this)
this.callback = null

@@ -64,2 +56,4 @@ this.runInAsyncScope(callback, null, null, {

removeSignal(this)
if (callback) {

@@ -66,0 +60,0 @@ this.callback = null

@@ -16,2 +16,3 @@ 'use strict'

const { assert } = require('console')
const { addSignal, removeSignal } = require('./abort-signal')

@@ -90,7 +91,3 @@ const kResume = Symbol('resume')

if (signal) {
util.addListener(signal, () => {
util.destroy(this.ret, new RequestAbortedError())
})
}
addSignal(this, signal)

@@ -133,2 +130,4 @@ this.req = new PipelineRequest().on('error', util.nop)

removeSignal(this)
callback(err)

@@ -135,0 +134,0 @@ }

@@ -10,2 +10,3 @@ 'use strict'

const { AsyncResource } = require('async_hooks')
const { addSignal, removeSignal } = require('./abort-signal')

@@ -74,11 +75,3 @@ const kAbort = Symbol('abort')

if (signal) {
util.addListener(signal, () => {
if (this.abort) {
this.abort()
} else {
this.onError(new RequestAbortedError())
}
})
}
addSignal(this, signal)
}

@@ -121,2 +114,5 @@

const { res } = this
removeSignal(this)
res.push(null)

@@ -128,2 +124,4 @@ }

removeSignal(this)
if (callback) {

@@ -130,0 +128,0 @@ this.callback = null

@@ -6,7 +6,7 @@ 'use strict'

InvalidArgumentError,
InvalidReturnValueError,
RequestAbortedError
InvalidReturnValueError
} = require('./core/errors')
const util = require('./core/util')
const { AsyncResource } = require('async_hooks')
const { addSignal, removeSignal } = require('./abort-signal')

@@ -60,11 +60,3 @@ class StreamHandler extends AsyncResource {

if (signal) {
util.addListener(signal, () => {
if (this.abort) {
this.abort()
} else {
this.onError(new RequestAbortedError())
}
})
}
addSignal(this, signal)
}

@@ -126,2 +118,3 @@

const { res } = this
return res.write(chunk)

@@ -133,2 +126,4 @@ }

removeSignal(this)
this.trailers = trailers ? util.parseHeaders(trailers) : {}

@@ -142,2 +137,4 @@

removeSignal(this)
this.factory = null

@@ -144,0 +141,0 @@

'use strict'
const {
InvalidArgumentError,
RequestAbortedError
} = require('./core/errors')
const { InvalidArgumentError } = require('./core/errors')
const { AsyncResource } = require('async_hooks')
const util = require('./core/util')
const { addSignal, removeSignal } = require('./abort-signal')

@@ -28,11 +26,3 @@ class UpgradeHandler extends AsyncResource {

if (signal) {
util.addListener(signal, () => {
if (this.abort) {
this.abort()
} else {
this.onError(new RequestAbortedError())
}
})
}
addSignal(this, signal)
}

@@ -51,2 +41,4 @@

removeSignal(this)
this.callback = null

@@ -63,2 +55,4 @@ this.runInAsyncScope(callback, null, null, {

removeSignal(this)
if (callback) {

@@ -65,0 +59,0 @@ this.callback = null

@@ -6,3 +6,3 @@ 'use strict'

const tls = require('tls')
const HTTPParser = require('./node/http-parser')
const HTTPParser = require('../node/http-parser')
const EventEmitter = require('events')

@@ -1051,36 +1051,40 @@ const assert = require('assert')

const onData = function (chunk) {
assert(!finished)
try {
assert(!finished)
const len = Buffer.byteLength(chunk)
if (!len) {
return
}
const len = Buffer.byteLength(chunk)
if (!len) {
return
}
// TODO: What if not ended and bytesWritten === contentLength?
// We should defer writing chunks.
if (contentLength !== null && bytesWritten + len > contentLength) {
util.destroy(socket, new ContentLengthMismatchError())
return
}
// TODO: What if not ended and bytesWritten === contentLength?
// We should defer writing chunks.
if (contentLength !== null && bytesWritten + len > contentLength) {
util.destroy(socket, new ContentLengthMismatchError())
return
}
if (bytesWritten === 0) {
if (!expectsPayload) {
client[kReset] = true
if (bytesWritten === 0) {
if (!expectsPayload) {
client[kReset] = true
}
if (contentLength === null) {
socket.write(`${header}transfer-encoding: chunked\r\n`, 'ascii')
} else {
socket.write(`${header}content-length: ${contentLength}\r\n\r\n`, 'ascii')
}
}
if (contentLength === null) {
socket.write(`${header}transfer-encoding: chunked\r\n`, 'ascii')
} else {
socket.write(`${header}content-length: ${contentLength}\r\n\r\n`, 'ascii')
socket.write(`\r\n${len.toString(16)}\r\n`, 'ascii')
}
}
if (contentLength === null) {
socket.write(`\r\n${len.toString(16)}\r\n`, 'ascii')
}
bytesWritten += len
bytesWritten += len
if (!socket.write(chunk) && this.pause) {
this.pause()
if (!socket.write(chunk) && this.pause) {
this.pause()
}
} catch (err) {
util.destroy(this, err)
}

@@ -1087,0 +1091,0 @@ }

@@ -50,2 +50,3 @@ 'use strict'

const KEEPALIVE_TIMEOUT_EXPR = /timeout=(\d+)s/
function parseKeepAliveTimeout (headers) {

@@ -55,4 +56,4 @@ for (let n = 0; n < headers.length; n += 2) {

if (key.length === 10 && key.toLowerCase() === 'keep-alive') {
const timeout = parseInt(headers[n + 1].split('timeout=', 2)[1])
return timeout ? timeout * 1000 : undefined
const m = headers[n + 1].match(KEEPALIVE_TIMEOUT_EXPR)
return m ? parseInt(m[1]) * 1000 : null
}

@@ -85,12 +86,3 @@ }

function addListener (ee, fn) {
if ('addEventListener' in ee) {
ee.addEventListener('abort', fn)
} else {
ee.addListener('abort', fn)
}
}
module.exports = {
addListener,
nop,

@@ -97,0 +89,0 @@ isStream,

@@ -9,3 +9,3 @@ 'use strict'

} = require('./core/errors')
const FixedQueue = require('./core/node/fixed-queue')
const FixedQueue = require('./node/fixed-queue')

@@ -12,0 +12,0 @@ const kClients = Symbol('clients')

{
"name": "undici",
"version": "2.0.0",
"version": "2.0.1",
"description": "An HTTP/1.1 client, written from scratch for Node.js",

@@ -5,0 +5,0 @@ "main": "index.js",

@@ -23,14 +23,24 @@ # undici

Machine: 2.8GHz AMD EPYC 7402P<br/>
Configuration: Node v14.4, HTTP/1.1 without TLS, 100 connections, Linux 5.4.12-1-lts
Node 14
```
http - keepalive x 5,882 ops/sec ±1.87% (274 runs sampled)
undici - pipeline x 9,189 ops/sec ±2.02% (272 runs sampled)
undici - request x 12,623 ops/sec ±0.89% (277 runs sampled)
undici - stream x 14,136 ops/sec ±0.61% (280 runs sampled)
undici - dispatch x 14,883 ops/sec ±0.44% (281 runs sampled)
http - keepalive x 6,770 ops/sec ±7.70% (75 runs sampled)
undici - pipeline x 10,627 ops/sec ±5.88% (79 runs sampled)
undici - request x 10,902 ops/sec ±1.28% (85 runs sampled)
undici - stream x 20,144 ops/sec ±0.84% (86 runs sampled)
undici - dispatch x 20,295 ops/sec ±1.00% (83 runs sampled)
```
The benchmark is a simple `hello world` [example](benchmarks/index.js).
Node 15
```
http - keepalive x 10,337 ops/sec ±6.17% (71 runs sampled)
undici - pipeline x 30,387 ops/sec ±1.37% (80 runs sampled)
undici - request x 40,117 ops/sec ±3.25% (77 runs sampled)
undici - stream x 40,543 ops/sec ±1.30% (80 runs sampled)
undici - dispatch x 50,434 ops/sec ±2.08% (77 runs sampled)
```
The benchmark is a simple `hello world` [example](benchmarks/index.js) using a
single unix socket with pipelining.
## API

@@ -41,4 +51,4 @@

A basic HTTP/1.1 client, mapped on top a single TCP/TLS connection.
Keepalive is enabled by default, and it cannot be turned off.
A basic HTTP/1.1 client, mapped on top a single TCP/TLS connection. Pipelining is disabled
by default.

@@ -75,4 +85,6 @@ `url` can be a string or a [`URL`](https://nodejs.org/api/url.html#url_class_url) object.

- `pipelining: Number`, the amount of concurrent requests to be sent over the
single TCP/TLS connection according to
[RFC7230](https://tools.ietf.org/html/rfc7230#section-6.3.2).
single TCP/TLS connection according to [RFC7230](https://tools.ietf.org/html/rfc7230#section-6.3.2).
Carefully consider your workload and environment before enabling concurrent requests
as pipelining may reduce performance if used incorrectly. Pipelining is sensitive
to network stack settings as well as head of line blocking caused by e.g. long running requests.
Default: `1`.

@@ -210,3 +222,3 @@

const client = new Client'http://localhost:3000')
const client = new Client('http://localhost:3000')
const ee = new EventEmitter()

@@ -213,0 +225,0 @@

@@ -9,3 +9,3 @@ 'use strict'

test('basic connect', (t) => {
t.plan(1)
t.plan(3)

@@ -33,5 +33,10 @@ const server = http.createServer((c) => {

const { socket } = await client.connect({
const signal = new EE()
const promise = client.connect({
signal,
path: '/'
})
t.strictEqual(signal.listenerCount('abort'), 1)
const { socket } = await promise
t.strictEqual(signal.listenerCount('abort'), 0)

@@ -194,3 +199,3 @@ let recvData = ''

test('connect aborted', (t) => {
t.plan(4)
t.plan(6)

@@ -222,5 +227,7 @@ const server = http.createServer((req, res) => {

t.strictEqual(opaque, 'asd')
t.strictEqual(signal.listenerCount('abort'), 0)
t.ok(err instanceof errors.RequestAbortedError)
})
t.strictEqual(client.busy, true)
t.strictEqual(signal.listenerCount('abort'), 1)
signal.emit('abort')

@@ -227,0 +234,0 @@

@@ -902,1 +902,29 @@ 'use strict'

})
test('invalid body chunk does not crash', (t) => {
t.plan(1)
const server = createServer()
server.on('request', (req, res) => {
res.end()
})
t.tearDown(server.close.bind(server))
server.listen(0, () => {
const client = new Client(`http://localhost:${server.address().port}`)
t.tearDown(client.destroy.bind(client))
client.request({
path: '/',
body: new Readable({
objectMode: true,
read () {
this.push({})
}
}),
method: 'GET'
}, (err) => {
t.strictEqual(err.code, 'ERR_INVALID_ARG_TYPE')
})
})
})

@@ -16,3 +16,3 @@ 'use strict'

test('pipeline get', (t) => {
t.plan(14)
t.plan(17)

@@ -35,5 +35,7 @@ const server = createServer((req, res) => {

const bufs = []
client.pipeline({ path: '/', method: 'GET' }, ({ statusCode, headers, body }) => {
const signal = new EE()
client.pipeline({ signal, path: '/', method: 'GET' }, ({ statusCode, headers, body }) => {
t.strictEqual(statusCode, 200)
t.strictEqual(headers['content-type'], 'text/plain')
t.strictEqual(signal.listenerCount('abort'), 1)
return body

@@ -48,2 +50,6 @@ })

})
.on('close', () => {
t.strictEqual(signal.listenerCount('abort'), 0)
})
t.strictEqual(signal.listenerCount('abort'), 1)
}

@@ -50,0 +56,0 @@

@@ -11,3 +11,3 @@ 'use strict'

test('request abort before headers', (t) => {
t.plan(2)
t.plan(6)

@@ -32,3 +32,6 @@ const signal = new EE()

t.ok(err instanceof errors.RequestAbortedError)
t.strictEqual(signal.listenerCount('abort'), 0)
})
t.strictEqual(signal.listenerCount('abort'), 1)
client.request({

@@ -40,3 +43,5 @@ path: '/',

t.ok(err instanceof errors.RequestAbortedError)
t.strictEqual(signal.listenerCount('abort'), 0)
})
t.strictEqual(signal.listenerCount('abort'), 2)
})

@@ -43,0 +48,0 @@ })

@@ -10,3 +10,3 @@ 'use strict'

test('stream get', (t) => {
t.plan(7)
t.plan(9)

@@ -26,3 +26,5 @@ const server = createServer((req, res) => {

const signal = new EE()
client.stream({
signal,
path: '/',

@@ -43,4 +45,6 @@ method: 'GET',

}, (err) => {
t.strictEqual(signal.listenerCount('abort'), 0)
t.error(err)
})
t.strictEqual(signal.listenerCount('abort'), 1)
})

@@ -398,3 +402,3 @@ })

test('stream factory abort', (t) => {
t.plan(1)
t.plan(3)

@@ -419,4 +423,6 @@ const server = createServer((req, res) => {

}, (err) => {
t.strictEqual(signal.listenerCount('abort'), 0)
t.ok(err instanceof errors.RequestAbortedError)
})
t.strictEqual(signal.listenerCount('abort'), 1)
})

@@ -423,0 +429,0 @@ })

@@ -10,3 +10,3 @@ 'use strict'

test('basic upgrade', (t) => {
t.plan(3)
t.plan(5)

@@ -33,3 +33,5 @@ const server = net.createServer((c) => {

const signal = new EE()
client.upgrade({
signal,
path: '/',

@@ -41,2 +43,4 @@ method: 'GET',

t.strictEqual(signal.listenerCount('abort'), 0)
const { headers, socket } = data

@@ -60,2 +64,3 @@

})
t.strictEqual(signal.listenerCount('abort'), 1)
})

@@ -276,3 +281,3 @@ })

test('upgrade aborted', (t) => {
t.plan(4)
t.plan(6)

@@ -301,4 +306,6 @@ const server = http.createServer((req, res) => {

t.ok(err instanceof errors.RequestAbortedError)
t.strictEqual(signal.listenerCount('abort'), 0)
})
t.strictEqual(client.busy, true)
t.strictEqual(signal.listenerCount('abort'), 1)
signal.emit('abort')

@@ -305,0 +312,0 @@

@@ -12,3 +12,3 @@ 'use strict'

test('basic get', (t) => {
t.plan(20)
t.plan(23)

@@ -36,3 +36,5 @@ const server = createServer((req, res) => {

const signal = new EE()
client.request({
signal,
path: '/',

@@ -45,2 +47,3 @@ method: 'GET',

t.strictEqual(statusCode, 200)
t.strictEqual(signal.listenerCount('abort'), 1)
t.strictEqual(headers['content-type'], 'text/plain')

@@ -52,5 +55,8 @@ const bufs = []

body.on('end', () => {
t.strictEqual(signal.listenerCount('abort'), 0)
t.strictEqual('hello', Buffer.concat(bufs).toString('utf8'))
})
})
t.strictEqual(signal.listenerCount('abort'), 1)
client.request({

@@ -57,0 +63,0 @@ path: '/',

@@ -5,3 +5,3 @@ 'use strict'

const FixedQueue = require('../lib/core/node/fixed-queue')
const FixedQueue = require('../lib/node/fixed-queue')

@@ -8,0 +8,0 @@ test('fixed queue 1', (t) => {

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc