Socket
Socket
Sign inDemoInstall

undici

Package Overview
Dependencies
Maintainers
2
Versions
211
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.2.1 to 3.0.0

116

benchmarks/index.js

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

const Benchmark = require('benchmark')
const { Client } = require('..')
const { Client, Pool } = require('..')
const os = require('os')
const path = require('path')

@@ -14,8 +16,25 @@ // # Start the Node.js server

const httpOptions = {
const connections = parseInt(process.env.CONNECTIONS, 10) || 50
const parallelRequests = parseInt(process.env.PARALLEL, 10) || 10
const pipelining = parseInt(process.env.PIPELINING, 10) || 10
const dest = {}
if (process.env.PORT) {
dest.port = process.env.PORT
dest.url = `http://localhost:${process.env.PORT}`
} else {
dest.url = 'http://localhost'
dest.socketPath = path.join(os.tmpdir(), 'undici.sock')
}
const httpNoAgent = {
protocol: 'http:',
hostname: 'localhost',
socketPath: '/var/tmp/undici.sock',
method: 'GET',
path: '/',
...dest
}
const httpOptions = {
...httpNoAgent,
agent: new http.Agent({

@@ -27,15 +46,26 @@ keepAlive: true,

const httpOptionsMultiSocket = {
...httpNoAgent,
agent: new http.Agent({
keepAlive: true,
maxSockets: connections
})
}
const undiciOptions = {
path: '/',
method: 'GET',
requestTimeout: 0
headersTimeout: 0,
bodyTimeout: 0
}
const client = new Client(`http://${httpOptions.hostname}`, {
pipelining: 10,
socketPath: '/var/tmp/undici.sock'
const client = new Client(httpOptions.url, {
pipelining,
...dest
})
client.on('disconnect', (err) => {
throw err
const pool = new Pool(httpOptions.url, {
pipelining,
connections,
...dest
})

@@ -45,9 +75,25 @@

Benchmark.options.minSamples = 200
// Benchmark.options.minSamples = 200
suite
.add('http - no agent ', {
defer: true,
fn: deferred => {
Promise.all(Array.from(Array(parallelRequests)).map(() => new Promise((resolve) => {
http.get(httpOptions, (res) => {
res
.pipe(new Writable({
write (chunk, encoding, callback) {
callback()
}
}))
.on('finish', resolve)
})
}))).then(() => deferred.resolve())
}
})
.add('http - keepalive', {
defer: true,
fn: deferred => {
Promise.all(Array.from(Array(10)).map(() => new Promise((resolve) => {
Promise.all(Array.from(Array(parallelRequests)).map(() => new Promise((resolve) => {
http.get(httpOptions, (res) => {

@@ -65,6 +111,22 @@ res

})
.add('http - keepalive - multiple sockets', {
defer: true,
fn: deferred => {
Promise.all(Array.from(Array(parallelRequests)).map(() => new Promise((resolve) => {
http.get(httpOptionsMultiSocket, (res) => {
res
.pipe(new Writable({
write (chunk, encoding, callback) {
callback()
}
}))
.on('finish', resolve)
})
}))).then(() => deferred.resolve())
}
})
.add('undici - pipeline', {
defer: true,
fn: deferred => {
Promise.all(Array.from(Array(10)).map(() => new Promise((resolve) => {
Promise.all(Array.from(Array(parallelRequests)).map(() => new Promise((resolve) => {
client

@@ -87,3 +149,3 @@ .pipeline(undiciOptions, data => {

fn: deferred => {
Promise.all(Array.from(Array(10)).map(() => new Promise((resolve) => {
Promise.all(Array.from(Array(parallelRequests)).map(() => new Promise((resolve) => {
client

@@ -103,6 +165,24 @@ .request(undiciOptions)

})
.add('undici - pool - request - multiple sockets', {
defer: true,
fn: deferred => {
Promise.all(Array.from(Array(parallelRequests)).map(() => new Promise((resolve) => {
pool
.request(undiciOptions)
.then(({ body }) => {
body
.pipe(new Writable({
write (chunk, encoding, callback) {
callback()
}
}))
.on('finish', resolve)
})
}))).then(() => deferred.resolve())
}
})
.add('undici - stream', {
defer: true,
fn: deferred => {
Promise.all(Array.from(Array(10)).map(() => {
Promise.all(Array.from(Array(parallelRequests)).map(() => {
return client.stream(undiciOptions, () => {

@@ -121,3 +201,3 @@ return new Writable({

fn: deferred => {
Promise.all(Array.from(Array(10)).map(() => new Promise((resolve) => {
Promise.all(Array.from(Array(parallelRequests)).map(() => new Promise((resolve) => {
client.dispatch(undiciOptions, new SimpleRequest(resolve))

@@ -130,3 +210,3 @@ }))).then(() => deferred.resolve())

fn: deferred => {
Promise.all(Array.from(Array(10)).map(() => new Promise((resolve) => {
Promise.all(Array.from(Array(parallelRequests)).map(() => new Promise((resolve) => {
client.dispatch(undiciOptions, new NoopRequest(resolve))

@@ -137,5 +217,5 @@ }))).then(() => deferred.resolve())

.on('cycle', ({ target }) => {
// Multiply results by 10x to get opts/sec since we do 10 requests
// Multiply results by parallelRequests to get opts/sec since we do mutiple requests
// per run.
target.hz *= 10
target.hz *= parallelRequests
console.log(String(target))

@@ -142,0 +222,0 @@ })

'use strict'
const { createServer } = require('http')
const os = require('os')
const path = require('path')
const port = process.env.PORT || path.join(os.tmpdir(), 'undici.sock')
const timeout = parseInt(process.env.TIMEOUT, 10) || 1
createServer((req, res) => {
res.end('hello world')
}).listen('/var/tmp/undici.sock')
setTimeout(function () {
res.end('hello world')
}, timeout)
}).listen(port)

42

examples/proxy/proxy.js

@@ -66,3 +66,3 @@ const net = require('net')

this.res.on('drain', resume)
writeHead(this.res, statusCode, getHeaders({
this.res.writeHead(statusCode, getHeaders({
headers,

@@ -79,2 +79,5 @@ proxyName: this.proxyName,

onComplete () {
this.res.off('close', this.abort)
this.res.off('drain', this.resume)
this.res.end()

@@ -87,2 +90,3 @@ this.callback()

this.res.off('drain', this.resume)
this.callback(err)

@@ -118,2 +122,4 @@ }

onUpgrade (statusCode, headers, socket) {
this.socket.off('close', this.abort)
// TODO: Check statusCode?

@@ -127,4 +133,2 @@

let head = 'HTTP/1.1 101 Switching Protocols\r\nconnection: upgrade\r\nupgrade: websocket'
headers = getHeaders({

@@ -136,11 +140,8 @@ headers,

let head = ''
for (let n = 0; n < headers.length; n += 2) {
const key = headers[n + 0]
const val = headers[n + 1]
head += `\r\n${key}: ${val}`
head += `\r\n${headers[n + 0]}: ${headers[n + 1]}`
}
head += '\r\n\r\n'
this.socket.write(head)
this.socket.write(`HTTP/1.1 101 Switching Protocols\r\nconnection: upgrade\r\nupgrade: websocket${head}\r\n\r\n`)

@@ -152,2 +153,3 @@ pipeline(socket, this.socket, socket, this.callback)

this.socket.off('close', this.abort)
this.callback(err)

@@ -262,23 +264,1 @@ }

}
function writeHead (res, statusCode, headers) {
// TODO (perf): res.writeHead should support Array and/or string.
const obj = {}
for (var i = 0; i < headers.length; i += 2) {
var key = headers[i]
var val = obj[key]
if (!val) {
obj[key] = headers[i + 1]
} else {
if (!Array.isArray(val)) {
val = [val]
obj[key] = val
}
val.push(headers[i + 1])
}
}
res.writeHead(statusCode, obj)
return res
}

@@ -11,5 +11,5 @@ import Pool from './types/pool'

declare namespace Undici {
var Pool: typeof import('./types/pool');
var Client: typeof import('./types/client');
var errors: typeof import('./types/errors');
var Pool: typeof import('./types/pool');
var Client: typeof import('./types/client');
var errors: typeof import('./types/errors');
}

@@ -6,4 +6,12 @@ const { RequestAbortedError } = require('./core/errors')

function abort (self) {
if (self.abort) {
self.abort()
} else {
self.onError(new RequestAbortedError())
}
}
function addSignal (self, signal) {
self[kSignal] = signal
self[kSignal] = null
self[kListener] = null

@@ -15,8 +23,10 @@

if (signal.aborted) {
abort(self)
return
}
self[kSignal] = signal
self[kListener] = () => {
if (self.abort) {
self.abort()
} else {
self.onError(new RequestAbortedError())
}
abort(self)
}

@@ -23,0 +33,0 @@

'use strict'
const { InvalidArgumentError } = require('./core/errors')
const { InvalidArgumentError, RequestAbortedError } = require('./core/errors')
const { AsyncResource } = require('async_hooks')

@@ -31,6 +31,6 @@ const util = require('./core/util')

if (!this.callback) {
abort()
} else {
this.abort = abort
throw new RequestAbortedError()
}
this.abort = abort
}

@@ -85,4 +85,3 @@

servername,
signal,
requestTimeout
signal
} = opts

@@ -94,4 +93,3 @@ this.dispatch({

servername,
signal,
requestTimeout
signal
}, connectHandler)

@@ -98,0 +96,0 @@ } catch (err) {

@@ -90,4 +90,2 @@ 'use strict'

addSignal(this, signal)
this.req = new PipelineRequest().on('error', util.nop)

@@ -141,2 +139,4 @@

this.res = null
addSignal(this, signal)
}

@@ -148,6 +148,6 @@

if (ret.destroyed) {
abort()
} else {
this.abort = abort
throw new RequestAbortedError()
}
this.abort = abort
}

@@ -237,4 +237,3 @@

servername,
signal,
requestTimeout
signal
} = opts

@@ -248,4 +247,3 @@ this.dispatch({

servername,
signal,
requestTimeout
signal
}, pipelineHandler)

@@ -252,0 +250,0 @@ return pipelineHandler.ret

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

this.body = body
this.trailers = {}

@@ -80,6 +81,6 @@ if (util.isStream(body)) {

if (!this.callback) {
abort()
} else {
this.abort = abort
throw new RequestAbortedError()
}
this.abort = abort
}

@@ -102,2 +103,3 @@

headers: util.parseHeaders(headers),
trailers: this.trailers,
opaque,

@@ -118,2 +120,6 @@ body

if (trailers) {
util.parseHeaders(trailers, this.trailers)
}
res.push(null)

@@ -120,0 +126,0 @@ }

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

InvalidArgumentError,
InvalidReturnValueError
InvalidReturnValueError,
RequestAbortedError
} = require('./core/errors')

@@ -65,6 +66,6 @@ const util = require('./core/util')

if (!this.callback) {
abort()
} else {
this.abort = abort
throw new RequestAbortedError()
}
this.abort = abort
}

@@ -114,2 +115,8 @@

this.res = res
const needDrain = res.writableNeedDrain !== undefined
? res.writableNeedDrain
: res._writableState && res._writableState.needDrain
return needDrain !== true
}

@@ -116,0 +123,0 @@

'use strict'
const { InvalidArgumentError } = require('./core/errors')
const { InvalidArgumentError, RequestAbortedError } = require('./core/errors')
const { AsyncResource } = require('async_hooks')

@@ -31,6 +31,6 @@ const util = require('./core/util')

if (!this.callback) {
abort()
} else {
this.abort = abort
throw new RequestAbortedError()
}
this.abort = abort
}

@@ -86,3 +86,2 @@

signal,
requestTimeout,
protocol

@@ -96,3 +95,2 @@ } = opts

signal,
requestTimeout,
upgrade: protocol || 'Websocket'

@@ -99,0 +97,0 @@ }, upgradeHandler)

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

ContentLengthMismatchError,
SocketTimeoutError,
TrailerMismatchError,
InvalidArgumentError,
RequestAbortedError,
HeadersTimeoutError,
ClientDestroyedError,
ClientClosedError,
HeadersTimeoutError,
SocketError,
InformationalError
InformationalError,
BodyTimeoutError
} = require('./errors')

@@ -27,5 +27,3 @@ const {

kReset,
kPause,
kHost,
kResume,
kClient,

@@ -39,4 +37,4 @@ kParser,

kTLSServerName,
kIdleTimeout,
kSocketTimeout,
kKeepAliveDefaultTimeout,
kHostHeader,
kTLSOpts,

@@ -50,17 +48,15 @@ kClosed,

kPipelining,
kRetryDelay,
kRetryTimeout,
kSocket,
kSocketPath,
kKeepAliveTimeout,
kKeepAliveTimeoutValue,
kMaxHeadersSize,
kHeadersTimeout,
kKeepAliveMaxTimeout,
kKeepAliveTimeoutThreshold,
kKeepAlive,
kTLSSession
kTLSSession,
kIdleTimeout,
kIdleTimeoutValue,
kHeadersTimeout,
kBodyTimeout
} = require('./symbols')
const kHostHeader = Symbol('host header')
const nodeVersions = process.version.split('.')

@@ -85,6 +81,7 @@ const nodeMajorVersion = parseInt(nodeVersions[0].slice(1))

socketTimeout,
bodyTimeout,
idleTimeout,
keepAliveTimeout,
maxKeepAliveTimeout,
keepAlive,
keepAliveMaxTimeout = maxKeepAliveTimeout,
keepAliveMaxTimeout,
keepAliveTimeoutThreshold,

@@ -97,2 +94,14 @@ socketPath,

if (socketTimeout !== undefined) {
throw new InvalidArgumentError('unsupported socketTimeout')
}
if (idleTimeout !== undefined) {
throw new InvalidArgumentError('unsupported idleTimeout, use keepAliveTimeout instead')
}
if (maxKeepAliveTimeout !== undefined) {
throw new InvalidArgumentError('unsupported maxKeepAliveTimeout, use keepAliveMaxTimeout instead')
}
if (typeof url === 'string') {

@@ -130,10 +139,6 @@ url = new URL(url)

if (socketTimeout != null && !Number.isFinite(socketTimeout)) {
throw new InvalidArgumentError('invalid socketTimeout')
if (keepAliveTimeout != null && (!Number.isFinite(keepAliveTimeout) || keepAliveTimeout <= 0)) {
throw new InvalidArgumentError('invalid keepAliveTimeout')
}
if (idleTimeout != null && (!Number.isFinite(idleTimeout) || idleTimeout <= 0)) {
throw new InvalidArgumentError('invalid idleTimeout')
}
if (keepAliveMaxTimeout != null && (!Number.isFinite(keepAliveMaxTimeout) || keepAliveMaxTimeout <= 0)) {

@@ -143,6 +148,2 @@ throw new InvalidArgumentError('invalid keepAliveMaxTimeout')

if (keepAlive != null && typeof keepAlive !== 'boolean') {
throw new InvalidArgumentError('invalid keepAlive')
}
if (keepAliveTimeoutThreshold != null && !Number.isFinite(keepAliveTimeoutThreshold)) {

@@ -152,19 +153,19 @@ throw new InvalidArgumentError('invalid keepAliveTimeoutThreshold')

if (headersTimeout != null && !Number.isFinite(headersTimeout)) {
throw new InvalidArgumentError('invalid headersTimeout')
if (headersTimeout != null && (!Number.isInteger(headersTimeout) || headersTimeout < 0)) {
throw new InvalidArgumentError('headersTimeout must be a positive integer or zero')
}
if (bodyTimeout != null && (!Number.isInteger(bodyTimeout) || bodyTimeout < 0)) {
throw new InvalidArgumentError('bodyTimeout must be a positive integer or zero')
}
this[kSocket] = null
this[kReset] = false
this[kPipelining] = pipelining || 1
this[kPipelining] = pipelining != null ? pipelining : 1
this[kMaxHeadersSize] = maxHeaderSize || 16384
this[kHeadersTimeout] = headersTimeout == null ? 30e3 : headersTimeout
this[kUrl] = url
this[kSocketPath] = socketPath
this[kSocketTimeout] = socketTimeout == null ? 30e3 : socketTimeout
this[kIdleTimeout] = idleTimeout == null ? 4e3 : idleTimeout
this[kKeepAliveDefaultTimeout] = keepAliveTimeout == null ? 4e3 : keepAliveTimeout
this[kKeepAliveMaxTimeout] = keepAliveMaxTimeout == null ? 600e3 : keepAliveMaxTimeout
this[kKeepAliveTimeoutThreshold] = keepAliveTimeoutThreshold == null ? 1e3 : keepAliveTimeoutThreshold
this[kKeepAliveTimeout] = this[kIdleTimeout]
this[kKeepAlive] = keepAlive == null ? true : keepAlive
this[kKeepAliveTimeoutValue] = this[kKeepAliveDefaultTimeout]
this[kClosed] = false

@@ -175,15 +176,9 @@ this[kDestroyed] = false

this[kHost] = null
this[kRetryDelay] = 0
this[kRetryTimeout] = null
this[kOnDestroyed] = []
this[kWriting] = false
this[kResuming] = 0 // 0, idle, 1, scheduled, 2 resuming
this[kNeedDrain] = 0 // 0, idle, 1, scheduled, 2 resuming
this[kResume] = () => {
if (this[kResuming] === 0) {
resume(this, true)
}
}
this[kTLSSession] = null
this[kHostHeader] = `host: ${this[kUrl].hostname}${this[kUrl].port ? `:${this[kUrl].port}` : ''}\r\n`
this[kBodyTimeout] = bodyTimeout != null ? bodyTimeout : 30e3
this[kHeadersTimeout] = headersTimeout != null ? headersTimeout : 30e3

@@ -238,3 +233,4 @@ // kQueue is built up of 3 sections separated by

get busy () {
return this[kReset] || this[kWriting] || this.pending > 0
const socket = this[kSocket]
return (socket && (socket[kReset] || socket[kWriting])) || this.pending > 0
}

@@ -278,2 +274,6 @@

} catch (err) {
if (typeof handler.onError !== 'function') {
throw new InvalidArgumentError('invalid onError method')
}
handler.onError(err)

@@ -345,4 +345,2 @@ }

clearTimeout(this[kRetryTimeout])
this[kRetryTimeout] = null
this[kClosed] = true

@@ -378,3 +376,3 @@ this[kDestroyed] = true

{},
client[kHeadersTimeout]
0
)

@@ -387,3 +385,3 @@ } else if (nodeMajorVersion === 12 && nodeMinorVersion >= 19) {

client[kMaxHeadersSize],
client[kHeadersTimeout]
0
)

@@ -397,3 +395,3 @@ } else if (nodeMajorVersion > 12) {

insecureHTTPParser,
client[kHeadersTimeout]
0
)

@@ -406,3 +404,3 @@ } else {

this.socket = socket
this.timeout = null
this.statusCode = null

@@ -412,16 +410,51 @@ this.upgrade = false

this.shouldKeepAlive = false
this.read = 0
this.request = null
}
this.paused = false
[HTTPParser.kOnTimeout] () {
/* istanbul ignore next: https://github.com/nodejs/node/pull/34578 */
if (this.statusCode) {
this.socket._unrefTimer()
} else {
util.destroy(this.socket, new HeadersTimeoutError())
// Parser can't be paused from within a callback.
// Use a buffer in JS land in order to stop further progress while paused.
this.resuming = false
this.queue = []
this._resume = () => {
if (!this.paused || this.resuming) {
return
}
this.paused = false
this.resuming = true
while (this.queue.length) {
const [fn, ...args] = this.queue.shift()
Reflect.apply(fn, this, args)
if (this.paused) {
this.resuming = false
return
}
}
this.resuming = false
socketResume(socket)
}
this._pause = () => {
if (this.paused) {
return
}
this.paused = true
socketPause(socket)
}
}
[HTTPParser.kOnHeaders] (rawHeaders) {
/* istanbul ignore next: difficult to make a test case for */
if (this.paused) {
this.queue.push([this[HTTPParser.kOnHeaders], rawHeaders])
return
}
if (this.headers) {

@@ -435,2 +468,7 @@ Array.prototype.push.apply(this.headers, rawHeaders)

[HTTPParser.kOnExecute] (ret) {
if (this.paused) {
this.queue.push([this[HTTPParser.kOnExecute], ret])
return
}
const { upgrade, socket } = this

@@ -444,8 +482,2 @@

// When the underlying `net.Socket` instance is consumed - no
// `data` events are emitted, and thus `socket.setTimeout` fires the
// callback even if the data is constantly flowing into the socket.
// See, https://github.com/nodejs/node/commit/ec2822adaad76b126b5cccdeaa1addf2376c9aa6
socket._unrefTimer()
// This logic cannot live in kOnHeadersComplete since we

@@ -460,3 +492,4 @@ // have no way of slicing the parsing buffer without knowing

assert(!socket.isPaused())
assert(request.upgrade || request.method === 'CONNECT')
assert(socket._handle && socket._handle.reading)
assert(request.upgrade)

@@ -482,17 +515,14 @@ this.headers = null

request.onUpgrade(statusCode, headers, socket)
} catch (err) {
util.destroy(socket, err)
request.onError(err)
}
if (!socket.destroyed && !request.aborted) {
detachSocket(socket)
if (!socket.destroyed && !request.aborted) {
detachSocket(socket)
client[kSocket] = null
client[kSocket] = null
client[kQueue][client[kRunningIdx]++] = null
client.emit('disconnect', new InformationalError('upgrade'))
client[kQueue][client[kRunningIdx]++] = null
client.emit('disconnect', new InformationalError('upgrade'))
}
setImmediate(() => this.close())
resume(client)
} catch (err) {
util.destroy(socket, err)
}

@@ -504,2 +534,9 @@ }

url, statusCode, statusMessage, upgrade, shouldKeepAlive) {
/* istanbul ignore next: difficult to make a test case for */
if (this.paused) {
this.queue.push([this[HTTPParser.kOnHeadersComplete], versionMajor, versionMinor, rawHeaders, method,
url, statusCode, statusMessage, upgrade, shouldKeepAlive])
return
}
const { client, socket } = this

@@ -514,2 +551,7 @@

clearTimeout(this.timeout)
this.timeout = client[kBodyTimeout]
? setTimeout(onBodyTimeout, client[kBodyTimeout], this)
: null
// TODO: Check for content-length mismatch from server?

@@ -527,3 +569,3 @@

if (upgrade !== Boolean(request.upgrade)) {
if (request.upgrade !== true && upgrade !== Boolean(request.upgrade)) {
util.destroy(socket, new SocketError('bad upgrade'))

@@ -543,3 +585,3 @@ return 1

if (upgrade || request.method === 'CONNECT') {
if (request.upgrade) {
this.unconsume()

@@ -553,6 +595,9 @@ this.upgrade = true

for (let n = 0; n < this.headers.length; n += 2) {
const key = this.headers[n + 0]
const val = this.headers[n + 1]
const { headers } = this
this.headers = null
for (let n = 0; n < headers.length; n += 2) {
const key = headers[n + 0]
const val = headers[n + 1]
if (!keepAlive && key.length === 10 && key.toLowerCase() === 'keep-alive') {

@@ -565,7 +610,5 @@ keepAlive = val

const { headers } = this
this.headers = null
this.trailers = trailers ? trailers.toLowerCase().split(/,\s*/) : null
if (shouldKeepAlive && client[kKeepAlive]) {
if (shouldKeepAlive && client[kPipelining]) {
const keepAliveTimeout = keepAlive ? util.parseKeepAliveTimeout(keepAlive) : null

@@ -578,18 +621,18 @@

)
if (timeout < 1e3) {
client[kReset] = true
if (timeout <= 0) {
socket[kReset] = true
} else {
client[kKeepAliveTimeout] = timeout
client[kKeepAliveTimeoutValue] = timeout
}
} else {
client[kKeepAliveTimeout] = client[kIdleTimeout]
client[kKeepAliveTimeoutValue] = client[kKeepAliveDefaultTimeout]
}
} else {
// Stop more requests from being dispatched.
client[kReset] = true
socket[kReset] = true
}
try {
if (request.onHeaders(statusCode, headers, socket[kResume]) === false) {
socket[kPause]()
if (request.onHeaders(statusCode, headers, this._resume) === false) {
this._pause()
}

@@ -605,4 +648,9 @@ } catch (err) {

[HTTPParser.kOnBody] (chunk, offset, length) {
const { socket, statusCode, request } = this
if (this.paused) {
this.queue.push([this[HTTPParser.kOnBody], chunk, offset, length])
return
}
const { socket, statusCode, request, timeout } = this
if (socket.destroyed) {

@@ -612,7 +660,11 @@ return

if (timeout && timeout.refresh) {
timeout.refresh()
}
assert(statusCode >= 200)
try {
if (request.onBody(chunk, offset, length) === false) {
socket[kPause]()
if (request.onData(chunk.slice(offset, offset + length)) === false) {
this._pause()
}

@@ -625,2 +677,8 @@ } catch (err) {

[HTTPParser.kOnMessageComplete] () {
/* istanbul ignore next: difficult to make a test case for */
if (this.paused) {
this.queue.push([this[HTTPParser.kOnMessageComplete]])
return
}
const { client, socket, statusCode, headers, upgrade, request, trailers } = this

@@ -633,2 +691,3 @@

assert(statusCode >= 100)
assert(this.resuming || (socket._handle && socket._handle.reading))

@@ -646,4 +705,8 @@ if (upgrade) {

clearTimeout(this.timeout)
this.timeout = client[kHeadersTimeout]
? setTimeout(onHeadersTimeout, client[kHeadersTimeout], this)
: null
if (statusCode < 200) {
assert(!socket.isPaused())
return

@@ -681,3 +744,3 @@ }

if (client[kWriting]) {
if (socket[kWriting]) {
// Response completed before request.

@@ -687,3 +750,3 @@ util.destroy(socket, new InformationalError('reset'))

util.destroy(socket, new InformationalError('reset'))
} else if (client[kReset] && !client.running) {
} else if (socket[kReset] && !client.running) {
// Destroy socket once all requests have completed.

@@ -695,3 +758,2 @@ // The request at the tail of the pipeline is the one

} else {
socket[kResume]()
resume(client)

@@ -702,2 +764,4 @@ }

destroy () {
clearTimeout(this.timeout)
this.timeout = null
this.unconsume()

@@ -708,11 +772,15 @@ setImmediate((self) => self.close(), this)

function onBodyTimeout (self) {
if (!self.paused) {
util.destroy(self.socket, new BodyTimeoutError())
}
}
function onHeadersTimeout (self) {
util.destroy(self.socket, new HeadersTimeoutError())
}
function onSocketConnect () {
const { [kClient]: client } = this
assert(!this.destroyed)
assert(!client[kWriting])
assert(!client[kRetryTimeout])
client[kReset] = false
client[kRetryDelay] = 0
client.emit('connect')

@@ -722,6 +790,2 @@ resume(client)

function onSocketTimeout () {
util.destroy(this, new SocketTimeoutError())
}
function onSocketError (err) {

@@ -755,11 +819,27 @@ const { [kClient]: client } = this

function detachSocket (socket) {
clearTimeout(socket[kIdleTimeout])
socket[kIdleTimeout] = null
socket[kIdleTimeoutValue] = null
socket[kParser].destroy()
socket[kParser] = null
socket[kClient] = null
socket[kError] = null
socket
.removeListener('session', onSocketSession)
.removeListener('error', onSocketError)
.removeListener('end', onSocketEnd)
.removeListener('close', onSocketClose)
}
function onSocketClose () {
const { [kClient]: client, [kParser]: parser } = this
const { [kClient]: client } = this
const err = this[kError] || new SocketError('closed')
detachSocket(this)
client[kSocket] = null
parser.destroy()
if (err.code !== 'UND_ERR_INFO') {

@@ -800,20 +880,4 @@ // Evict session on errors.

function detachSocket (socket) {
socket[kParser].destroy()
socket[kParser] = null
socket[kPause] = null
socket[kResume] = null
socket[kClient] = null
socket[kError] = null
socket
.removeListener('timeout', onSocketTimeout)
.removeListener('session', onSocketSession)
.removeListener('error', onSocketError)
.removeListener('end', onSocketEnd)
.removeListener('close', onSocketClose)
}
function connect (client) {
assert(!client[kSocket])
assert(!client[kRetryTimeout])

@@ -855,4 +919,6 @@ const { protocol, port, hostname } = client[kUrl]

socket[kPause] = socketPause.bind(socket)
socket[kResume] = socketResume.bind(socket)
socket[kIdleTimeout] = null
socket[kIdleTimeoutValue] = null
socket[kWriting] = false
socket[kReset] = false
socket[kError] = null

@@ -863,5 +929,3 @@ socket[kParser] = parser

.setNoDelay(true)
.setTimeout(client[kIdleTimeout])
.on(protocol === 'https:' ? 'secureConnect' : 'connect', onSocketConnect)
.on('timeout', onSocketTimeout)
.on('error', onSocketError)

@@ -872,9 +936,8 @@ .on('end', onSocketEnd)

function socketPause () {
// TODO: Pause parser.
if (this._handle && this._handle.reading) {
this._handle.reading = false
const err = this._handle.readStop()
function socketPause (socket) {
if (socket._handle && socket._handle.reading) {
socket._handle.reading = false
const err = socket._handle.readStop()
if (err) {
this.destroy(util.errnoException(err, 'read'))
socket.destroy(util.errnoException(err, 'read'))
}

@@ -884,9 +947,8 @@ }

function socketResume () {
// TODO: Resume parser.
if (this._handle && !this._handle.reading) {
this._handle.reading = true
const err = this._handle.readStart()
function socketResume (socket) {
if (socket._handle && !socket._handle.reading) {
socket._handle.reading = true
const err = socket._handle.readStart()
if (err) {
this.destroy(util.errnoException(err, 'read'))
socket.destroy(util.errnoException(err, 'read'))
}

@@ -930,8 +992,13 @@ }

if (client[kSocket]) {
const timeout = client.running
? client[kSocketTimeout]
: client[kKeepAliveTimeout]
const socket = client[kSocket]
const timeout = client.running ? 0 : client[kKeepAliveTimeoutValue]
if (client[kSocket].timeout !== timeout) {
client[kSocket].setTimeout(timeout)
if (socket[kIdleTimeoutValue] !== timeout) {
clearTimeout(socket[kIdleTimeout])
if (timeout) {
socket[kIdleTimeout] = setTimeout((socket) => {
util.destroy(socket, new InformationalError('socket idle timeout'))
}, timeout, socket)
}
socket[kIdleTimeoutValue] = timeout
}

@@ -958,2 +1025,3 @@ }

}
return

@@ -964,6 +1032,7 @@ } else {

if (client.running >= client[kPipelining]) {
if (client.running >= (client[kPipelining] || 1)) {
return
}
const socket = client[kSocket]
const request = client[kQueue][client[kPendingIdx]]

@@ -984,4 +1053,4 @@

if (client[kSocket]) {
util.destroy(client[kSocket], new InformationalError('servername changed'))
if (socket) {
util.destroy(socket, new InformationalError('servername changed'))
return

@@ -992,13 +1061,4 @@ }

if (!client[kSocket] && !client[kRetryTimeout]) {
if (client[kRetryDelay]) {
client[kRetryTimeout] = setTimeout(() => {
client[kRetryTimeout] = null
connect(client)
}, client[kRetryDelay])
client[kRetryDelay] = Math.min(client[kRetryDelay] * 2, client[kSocketTimeout])
} else {
connect(client)
client[kRetryDelay] = 1e3
}
if (!socket) {
connect(client)
return

@@ -1011,11 +1071,6 @@ }

if (client[kWriting] || client[kReset]) {
if (socket[kWriting] || socket[kReset]) {
return
}
if (client.running && !client[kKeepAlive]) {
// Don't schedule more if we know connection will reset.
return
}
if (client.running && !request.idempotent) {

@@ -1028,3 +1083,3 @@ // Non-idempotent request cannot be retried.

if (client.running && (request.upgrade || request.method === 'CONNECT')) {
if (client.running && request.upgrade) {
// Don't dispatch an upgrade until all preceeding requests have completed.

@@ -1065,2 +1120,7 @@ // A misbehaving server might upgrade the connection before all pipelined

if (write(client, request)) {
const parser = client[kSocket][kParser]
if (!parser.timeout && client[kHeadersTimeout]) {
parser.timeout = setTimeout(onHeadersTimeout, client[kHeadersTimeout], parser)
}
client[kPendingIdx]++

@@ -1116,7 +1176,20 @@ } else {

if (request.aborted) {
return false
}
try {
request.onConnect(client[kResume])
request.onConnect((err) => {
if (request.aborted) {
return
}
request.onError(err || new RequestAbortedError())
if (client[kResuming] === 0) {
resume(client, true)
}
})
} catch (err) {
request.onError(err)
return false
}

@@ -1128,2 +1201,4 @@

const socket = client[kSocket]
if (method === 'HEAD') {

@@ -1135,10 +1210,10 @@ // https://github.com/mcollina/undici/issues/258

client[kReset] = true
socket[kReset] = true
}
if (method === 'CONNECT' || upgrade) {
if (upgrade) {
// On CONNECT or upgrade, block pipeline from dispatching further
// requests on this connection.
client[kReset] = true
socket[kReset] = true
}

@@ -1154,5 +1229,5 @@

if (upgrade) {
if (typeof upgrade === 'string') {
header = `${method} ${path} HTTP/1.1\r\nconnection: upgrade\r\nupgrade: ${upgrade}\r\n`
} else if (client[kKeepAlive]) {
} else if (client[kPipelining]) {
header = `${method} ${path} HTTP/1.1\r\nconnection: keep-alive\r\n`

@@ -1171,4 +1246,2 @@ } else {

const socket = client[kSocket]
if (!body) {

@@ -1191,6 +1264,6 @@ if (contentLength === 0) {

if (!expectsPayload) {
client[kReset] = true
socket[kReset] = true
}
} else {
client[kWriting] = true
socket[kWriting] = true

@@ -1221,3 +1294,3 @@ assert(util.isStream(body))

if (!expectsPayload) {
client[kReset] = true
socket[kReset] = true
}

@@ -1262,4 +1335,4 @@

assert(client[kWriting] && client.running <= 1)
client[kWriting] = false
assert(socket.destroyed || (socket[kWriting] && client.running <= 1))
socket[kWriting] = false

@@ -1279,7 +1352,10 @@ if (!err && contentLength !== null && bytesWritten !== contentLength) {

request.body = null
util.destroy(body, err)
if (err) {
assert(client.running <= 1, 'pipeline should only contain this request')
util.destroy(socket, err)
}
if (socket.destroyed) {
return

@@ -1286,0 +1362,0 @@ }

@@ -21,22 +21,12 @@ 'use strict'

class SocketTimeoutError extends UndiciError {
class BodyTimeoutError extends UndiciError {
constructor (message) {
super(message)
Error.captureStackTrace(this, SocketTimeoutError)
this.name = 'SocketTimeoutError'
this.message = message || 'Socket Timeout Error'
this.code = 'UND_ERR_SOCKET_TIMEOUT'
Error.captureStackTrace(this, BodyTimeoutError)
this.name = 'BodyTimeoutError'
this.message = message || 'Body Timeout Error'
this.code = 'UND_ERR_BODY_TIMEOUT'
}
}
class RequestTimeoutError extends UndiciError {
constructor (message) {
super(message)
Error.captureStackTrace(this, RequestTimeoutError)
this.name = 'RequestTimeoutError'
this.message = message || 'Request Timeout Error'
this.code = 'UND_ERR_REQUEST_TIMEOUT'
}
}
class InvalidArgumentError extends UndiciError {

@@ -144,5 +134,4 @@ constructor (message) {

UndiciError,
SocketTimeoutError,
HeadersTimeoutError,
RequestTimeoutError,
BodyTimeoutError,
ContentLengthMismatchError,

@@ -149,0 +138,0 @@ TrailerMismatchError,

@@ -5,4 +5,2 @@ 'use strict'

InvalidArgumentError,
RequestAbortedError,
RequestTimeoutError,
NotSupportedError

@@ -13,4 +11,2 @@ } = require('./errors')

const kRequestTimeout = Symbol('request timeout')
const kTimeout = Symbol('timeout')
const kHandler = Symbol('handler')

@@ -29,4 +25,3 @@

idempotent,
upgrade,
requestTimeout
upgrade
}, handler) {

@@ -47,6 +42,2 @@ if (typeof path !== 'string') {

if (requestTimeout != null && (!Number.isInteger(requestTimeout) || requestTimeout < 0)) {
throw new InvalidArgumentError('requestTimeout must be a positive integer or zero')
}
this.method = method

@@ -68,3 +59,3 @@

this.upgrade = upgrade
this.upgrade = upgrade || method === 'CONNECT' || null

@@ -98,42 +89,45 @@ this.path = path

this[kRequestTimeout] = requestTimeout != null ? requestTimeout : 30e3
this[kTimeout] = null
this[kHandler] = handler
}
if (typeof handler.onConnect !== 'function') {
throw new InvalidArgumentError('invalid onConnect method')
}
onConnect (resume) {
assert(!this.aborted)
if (typeof handler.onError !== 'function') {
throw new InvalidArgumentError('invalid onError method')
}
const abort = (err) => {
if (!this.aborted) {
this.onError(err || new RequestAbortedError())
resume()
if (this.upgrade) {
if (typeof handler.onUpgrade !== 'function') {
throw new InvalidArgumentError('invalid onUpgrade method')
}
}
} else {
if (typeof handler.onHeaders !== 'function') {
throw new InvalidArgumentError('invalid onHeaders method')
}
if (this[kRequestTimeout]) {
if (this[kTimeout]) {
clearTimeout(this[kTimeout])
if (typeof handler.onData !== 'function') {
throw new InvalidArgumentError('invalid onData method')
}
this[kTimeout] = setTimeout((abort) => {
abort(new RequestTimeoutError())
}, this[kRequestTimeout], abort)
if (typeof handler.onComplete !== 'function') {
throw new InvalidArgumentError('invalid onComplete method')
}
}
this[kHandler].onConnect(abort)
this[kHandler] = handler
}
onConnect (abort) {
assert(!this.aborted)
return this[kHandler].onConnect(abort)
}
onHeaders (statusCode, headers, resume) {
assert(!this.aborted)
clearRequestTimeout(this)
return this[kHandler].onHeaders(statusCode, headers, resume)
}
onBody (chunk, offset, length) {
onData (chunk) {
assert(!this.aborted)
return this[kHandler].onData(chunk.slice(offset, offset + length))
assert(!this.upgrade)
return this[kHandler].onData(chunk)
}

@@ -143,6 +137,4 @@

assert(!this.aborted)
clearRequestTimeout(this)
this[kHandler].onUpgrade(statusCode, headers, socket)
assert(this.upgrade)
return this[kHandler].onUpgrade(statusCode, headers, socket)
}

@@ -152,6 +144,4 @@

assert(!this.aborted)
clearRequestTimeout(this)
this[kHandler].onComplete(trailers)
assert(!this.upgrade)
return this[kHandler].onComplete(trailers)
}

@@ -165,5 +155,3 @@

clearRequestTimeout(this)
this[kHandler].onError(err)
return this[kHandler].onError(err)
}

@@ -225,10 +213,2 @@ }

function clearRequestTimeout (request) {
const { [kTimeout]: timeout } = request
if (timeout) {
request[kTimeout] = null
clearTimeout(timeout)
}
}
module.exports = Request

@@ -7,10 +7,11 @@ module.exports = {

kConnect: Symbol('connect'),
kResume: Symbol('resume'),
kPause: Symbol('pause'),
kSocketTimeout: Symbol('socket timeout'),
kIdleTimeout: Symbol('idle timeout'),
kIdleTimeoutValue: Symbol('idle timeout value'),
kKeepAliveDefaultTimeout: Symbol('default keep alive timeout'),
kKeepAliveMaxTimeout: Symbol('max keep alive timeout'),
kKeepAliveTimeoutThreshold: Symbol('keep alive timeout threshold'),
kKeepAliveTimeout: Symbol('keep alive timeout'),
kKeepAliveTimeoutValue: Symbol('keep alive timeout'),
kKeepAlive: Symbol('keep alive'),
kHeadersTimeout: Symbol('headers timeout'),
kBodyTimeout: Symbol('body timeout'),
kTLSServerName: Symbol('server name'),

@@ -24,3 +25,2 @@ kHost: Symbol('host'),

kMaxHeadersSize: Symbol('max headers size'),
kHeadersTimeout: Symbol('headers timeout'),
kRunningIdx: Symbol('running index'),

@@ -33,7 +33,6 @@ kPendingIdx: Symbol('pending index'),

kPipelining: Symbol('pipelinig'),
kRetryDelay: Symbol('retry delay'),
kSocketPath: Symbol('socket path'),
kSocket: Symbol('socket'),
kRetryTimeout: Symbol('retry timeout'),
kTLSSession: Symbol('tls session cache')
kTLSSession: Symbol('tls session cache'),
kHostHeader: Symbol('host header')
}

@@ -79,4 +79,3 @@ 'use strict'

function parseHeaders (headers) {
const obj = {}
function parseHeaders (headers, obj = {}) {
for (var i = 0; i < headers.length; i += 2) {

@@ -83,0 +82,0 @@ var key = headers[i].toLowerCase()

@@ -75,2 +75,6 @@ 'use strict'

} catch (err) {
if (typeof handler.onError !== 'function') {
throw new InvalidArgumentError('invalid onError method')
}
handler.onError(err)

@@ -77,0 +81,0 @@ }

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

@@ -13,2 +13,3 @@ "main": "index.js",

"coverage": "standard | snazzy && tap test/*.js",
"coverage:ci": "npm run coverage -- --coverage-report=lcovonly",
"bench": "npx concurrently -k -s first \"node benchmarks/server.js\" \"node -e 'setTimeout(() => {}, 1000)' && node benchmarks\""

@@ -15,0 +16,0 @@ },

# undici
![Node CI](https://github.com/mcollina/undici/workflows/Node%20CI/badge.svg) [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](http://standardjs.com/) [![npm version](https://badge.fury.io/js/undici.svg)](https://badge.fury.io/js/undici)
![Node CI](https://github.com/mcollina/undici/workflows/Node%20CI/badge.svg) [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](http://standardjs.com/) [![npm version](https://badge.fury.io/js/undici.svg)](https://badge.fury.io/js/undici) [![codecov](https://codecov.io/gh/nodejs/undici/branch/master/graph/badge.svg)](https://codecov.io/gh/nodejs/undici)

@@ -49,18 +49,11 @@ A HTTP/1.1 client, written from scratch for Node.js.

- `socketTimeout: Number`, the timeout after which a socket with active requests
will time out. Monitors time between activity on a connected socket.
Use `0` to disable it entirely. Default: `30e3` milliseconds (30s).
- `socketPath: String|Null`, an IPC endpoint, either Unix domain socket or Windows named pipe.
Default: `null`.
- `idleTimeout: Number`, the timeout after which a socket without active requests
- `keepAliveTimeout: Number`, the timeout after which a socket without active requests
will time out. Monitors time between activity on a connected socket.
This value may be overriden by *keep-alive* hints from the server.
This value may be overridden by *keep-alive* hints from the server.
Default: `4e3` milliseconds (4s).
- `keepAlive: Boolean`, enable or disable keep alive connections.
Default: `true`.
- `keepAliveMaxTimeout: Number`, the maximum allowed `idleTimeout` when overriden by
- `keepAliveMaxTimeout: Number`, the maximum allowed `keepAliveTimeout` when overridden by
*keep-alive* hints from the server.

@@ -70,6 +63,14 @@ Default: `600e3` milliseconds (10min).

- `keepAliveTimeoutThreshold: Number`, a number subtracted from server *keep-alive* hints
when overriding `idleTimeout` to account for timing inaccuries caused by e.g.
when overriding `keepAliveTimeout` to account for timing inaccuracies caused by e.g.
transport latency.
Default: `1e3` milliseconds (1s).
- `headersTimeout: Number`, the timeout after which a request will time out, in
milliseconds. Monitors time between receiving a complete headers.
Use `0` to disable it entirely. Default: `30e3` milliseconds (30s).
- `bodyTimeout: Number`, the timeout after which a request will time out, in
milliseconds. Monitors time between receiving a body data.
Use `0` to disable it entirely. Default: `30e3` milliseconds (30s).
- `pipelining: Number`, the amount of concurrent requests to be sent over the

@@ -80,2 +81,3 @@ single TCP/TLS connection according to [RFC7230](https://tools.ietf.org/html/rfc7230#section-6.3.2).

to network stack settings as well as head of line blocking caused by e.g. long running requests.
Set to `0` to disable keep-alive connections.
Default: `1`.

@@ -90,6 +92,2 @@

- `headersTimeout: Number`, the amount of time the parser will wait to receive the complete
HTTP headers (Node 14 and above only).
Default: `30e3` milliseconds (30s).
<a name='request'></a>

@@ -111,8 +109,4 @@ #### `client.request(opts[, callback(err, data)]): Promise|Void`

Default: `null`.
* `requestTimeout: Number`, the timeout after which a request will time out, in
milliseconds. Monitors time between request being enqueued and receiving
a response. Use `0` to disable it entirely.
Default: `30e3` milliseconds (30s).
* `idempotent: Boolean`, whether the requests can be safely retried or not.
If `false` the request won't be sent until all preceeding
If `false` the request won't be sent until all preceding
requests in the pipeline has completed.

@@ -153,2 +147,4 @@ Default: `true` if `method` is `HEAD` or `GET`.

* `headers: Object`, an object where all keys have been lowercased.
* `trailers: Object`, an object where all keys have been lowercased. This object start out
as empty and will be mutated to contain trailers after `body` has emitted `'end'`.
* `body: stream.Readable` response payload. A user **must**

@@ -178,2 +174,3 @@ either fully consume or destroy the body unless there is an error, or no further requests

headers,
trailers,
body

@@ -187,2 +184,5 @@ } = data

body.on('data', console.log)
body.on('end', () => {
console.log('trailers', trailers)
})

@@ -203,3 +203,3 @@ client.close()

A request can may be aborted using either an `AbortController` or an `EventEmitter`.
A request can be aborted using either an `AbortController` or an `EventEmitter`.
To use `AbortController` in Node.js versions earlier than 15, you will need to

@@ -399,6 +399,2 @@ install a shim - `npm i abort-controller`.

Default: `null`
* `requestTimeout: Number`, the timeout after which a request will time out, in
milliseconds. Monitors time between request being enqueued and receiving
a response. Use `0` to disable it entirely.
Default: `30e3` milliseconds (30s).
* `protocol: String`, a string of comma separated protocols, in descending preference order.

@@ -428,6 +424,2 @@ Default: `Websocket`.

Default: `null`
* `requestTimeout: Number`, the timeout after which a request will time out, in
milliseconds. Monitors time between request being enqueued and receiving
a response. Use `0` to disable it entirely.
Default: `30e3` milliseconds (30s).

@@ -446,6 +438,6 @@ The `data` parameter in `callback` is defined as follow:

This is the low level API which all the preceeding APIs are implemented on top of.
This is the low level API which all the preceding APIs are implemented on top of.
This API is expected to evolve through semver-major versions and is less stable
than the preceeding higher level APIs. It is primarily intended for library developers
than the preceding higher level APIs. It is primarily intended for library developers
who implement higher level APIs on top of this.

@@ -461,8 +453,4 @@

Default: `null`.
* `requestTimeout: Number`, the timeout after which a request will time out, in
milliseconds. Monitors time between request being enqueued and receiving
a response. Use `0` to disable it entirely.
Default: `30e3` milliseconds (30s).
* `idempotent: Boolean`, whether the requests can be safely retried or not.
If `false` the request won't be sent until all preceeding
If `false` the request won't be sent until all preceding
requests in the pipeline has completed.

@@ -489,3 +477,3 @@ Default: `true` if `method` is `HEAD` or `GET`.

* `trailers: Array|Null`
* `onError(err): Void`, invoked when an error has occured.
* `onError(err): Void`, invoked when an error has occurred.
* `err: Error`

@@ -538,3 +526,3 @@

True if pipeline is saturated or blocked. Indicicates whether dispatching
True if pipeline is saturated or blocked. Indicates whether dispatching
further requests is meaningful.

@@ -624,4 +612,2 @@

| `InvalidReturnValueError` | `UND_ERR_INVALID_RETURN_VALUE` | returned an invalid value. |
| `SocketTimeoutError` | `UND_ERR_SOCKET_TIMEOUT` | a socket exceeds the `socketTimeout` option. |
| `RequestTimeoutError` | `UND_ERR_REQUEST_TIMEOUT` | a request exceeds the `requestTimeout` option. |
| `RequestAbortedError` | `UND_ERR_ABORTED` | the request has been aborted by the user |

@@ -654,3 +640,3 @@ | `ClientDestroyedError` | `UND_ERR_DESTROYED` | trying to use a destroyed client. |

Undici always assumes that connections are persistent and will immediatly
Undici always assumes that connections are persistent and will immediately
pipeline requests, without checking whether the connection is persistent.

@@ -657,0 +643,0 @@ Hence, automatic fallback to HTTP/1.0 or HTTP/1.1 without pipelining is

@@ -21,2 +21,23 @@ 'use strict'

for (const { AbortControllerImpl, controllerName } of controllers) {
test(`Abort ${controllerName} before creating request`, (t) => {
t.plan(1)
const server = createServer((req, res) => {
t.fail()
})
t.teardown(server.close.bind(server))
server.listen(0, () => {
const client = new Client(`http://localhost:${server.address().port}`)
const abortController = new AbortControllerImpl()
t.teardown(client.destroy.bind(client))
abortController.abort()
client.request({ path: '/', method: 'GET', signal: abortController.signal }, (err, response) => {
t.ok(err instanceof errors.RequestAbortedError)
})
})
})
test(`Abort ${controllerName} before sending request (no body)`, (t) => {

@@ -23,0 +44,0 @@ t.plan(3)

'use strict'
const { test } = require('tap')
const { Client, errors } = require('..')
const { Client, Pool, errors } = require('..')
const http = require('http')
test('dispatch invalid opts', (t) => {
t.plan(1)
t.plan(3)

@@ -21,2 +21,22 @@ const client = new Client('http://localhost:5000')

})
client.dispatch({
path: '/',
method: 'GET',
headersTimeout: 'asd'
}, {
onError (err) {
t.ok(err instanceof errors.InvalidArgumentError)
}
})
client.dispatch({
path: '/',
method: 'GET',
bodyTimeout: 'asd'
}, {
onError (err) {
t.ok(err instanceof errors.InvalidArgumentError)
}
})
})

@@ -308,2 +328,5 @@

},
onHeaders (statusCode, headers) {
t.pass('should not throw')
},
onUpgrade (statusCode, headers, socket) {

@@ -335,1 +358,245 @@ t.strictEqual(count++, 0)

})
test('dispatch onConnect missing', (t) => {
t.plan(1)
const server = http.createServer((req, res) => {
res.end('ad')
})
t.tearDown(server.close.bind(server))
server.listen(0, () => {
const client = new Client(`http://localhost:${server.address().port}`)
t.tearDown(client.close.bind(client))
client.dispatch({
path: '/',
method: 'GET'
}, {
onHeaders (statusCode, headers) {
t.pass('should not throw')
},
onData (buf) {
t.pass('should not throw')
},
onComplete (trailers) {
t.pass('should not throw')
},
onError (err) {
t.strictEqual(err.code, 'UND_ERR_INVALID_ARG')
}
})
})
})
test('dispatch onHeaders missing', (t) => {
t.plan(1)
const server = http.createServer((req, res) => {
res.end('ad')
})
t.tearDown(server.close.bind(server))
server.listen(0, () => {
const client = new Client(`http://localhost:${server.address().port}`)
t.tearDown(client.close.bind(client))
client.dispatch({
path: '/',
method: 'GET'
}, {
onConnect () {
},
onData (buf) {
t.pass('should not throw')
},
onComplete (trailers) {
t.pass('should not throw')
},
onError (err) {
t.strictEqual(err.code, 'UND_ERR_INVALID_ARG')
}
})
})
})
test('dispatch onData missing', (t) => {
t.plan(1)
const server = http.createServer((req, res) => {
res.end('ad')
})
t.tearDown(server.close.bind(server))
server.listen(0, () => {
const client = new Client(`http://localhost:${server.address().port}`)
t.tearDown(client.close.bind(client))
client.dispatch({
path: '/',
method: 'GET'
}, {
onConnect () {
},
onHeaders (statusCode, headers) {
t.pass('should not throw')
},
onComplete (trailers) {
t.pass('should not throw')
},
onError (err) {
t.strictEqual(err.code, 'UND_ERR_INVALID_ARG')
}
})
})
})
test('dispatch onComplete missing', (t) => {
t.plan(1)
const server = http.createServer((req, res) => {
res.end('ad')
})
t.tearDown(server.close.bind(server))
server.listen(0, () => {
const client = new Client(`http://localhost:${server.address().port}`)
t.tearDown(client.close.bind(client))
client.dispatch({
path: '/',
method: 'GET'
}, {
onConnect () {
},
onHeaders (statusCode, headers) {
t.pass('should not throw')
},
onData (buf) {
t.pass('should not throw')
},
onError (err) {
t.strictEqual(err.code, 'UND_ERR_INVALID_ARG')
}
})
})
})
test('dispatch onError missing', (t) => {
t.plan(1)
const server = http.createServer((req, res) => {
res.end('ad')
})
t.tearDown(server.close.bind(server))
server.listen(0, () => {
const client = new Client(`http://localhost:${server.address().port}`)
t.tearDown(client.close.bind(client))
try {
client.dispatch({
path: '/',
method: 'GET'
}, {
onConnect () {
},
onHeaders (statusCode, headers) {
t.pass('should not throw')
},
onData (buf) {
t.pass('should not throw')
},
onComplete (trailers) {
t.pass('should not throw')
}
})
} catch (err) {
t.strictEqual(err.code, 'UND_ERR_INVALID_ARG')
}
})
})
test('dispatch CONNECT onUpgrade missing', (t) => {
t.plan(2)
const server = http.createServer((req, res) => {
res.end('ad')
})
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.dispatch({
path: '/',
method: 'GET',
upgrade: 'Websocket'
}, {
onConnect () {
},
onHeaders (statusCode, headers) {
},
onError (err) {
t.strictEqual(err.code, 'UND_ERR_INVALID_ARG')
t.strictEqual(err.message, 'invalid onUpgrade method')
}
})
})
})
test('dispatch upgrade onUpgrade missing', (t) => {
t.plan(2)
const server = http.createServer((req, res) => {
res.end('ad')
})
t.tearDown(server.close.bind(server))
server.listen(0, () => {
const client = new Client(`http://localhost:${server.address().port}`)
t.tearDown(client.close.bind(client))
client.dispatch({
path: '/',
method: 'GET',
upgrade: 'Websocket'
}, {
onConnect () {
},
onHeaders (statusCode, headers) {
},
onError (err) {
t.strictEqual(err.code, 'UND_ERR_INVALID_ARG')
t.strictEqual(err.message, 'invalid onUpgrade method')
}
})
})
})
test('dispatch pool onError missing', (t) => {
t.plan(2)
const server = http.createServer((req, res) => {
res.end('ad')
})
t.tearDown(server.close.bind(server))
server.listen(0, () => {
const client = new Pool(`http://localhost:${server.address().port}`)
t.tearDown(client.close.bind(client))
try {
client.dispatch({
path: '/',
method: 'GET',
upgrade: 'Websocket'
}, {
})
} catch (err) {
t.strictEqual(err.code, 'UND_ERR_INVALID_ARG')
t.strictEqual(err.message, 'invalid onError method')
}
})
})

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

new Client({ port: 'foobar' }) // eslint-disable-line
t.fail()
} catch (err) {

@@ -253,2 +254,3 @@ t.ok(err instanceof errors.InvalidArgumentError)

new Client(new URL('http://asd:200/somepath')) // eslint-disable-line
t.fail()
} catch (err) {

@@ -261,2 +263,3 @@ t.ok(err instanceof errors.InvalidArgumentError)

new Client(new URL('http://asd:200?q=asd')) // eslint-disable-line
t.fail()
} catch (err) {

@@ -269,2 +272,3 @@ t.ok(err instanceof errors.InvalidArgumentError)

new Client(new URL('http://asd:200#asd')) // eslint-disable-line
t.fail()
} catch (err) {

@@ -279,2 +283,3 @@ t.ok(err instanceof errors.InvalidArgumentError)

})
t.fail()
} catch (err) {

@@ -287,7 +292,8 @@ t.ok(err instanceof errors.InvalidArgumentError)

new Client(new URL('http://localhost:200'), { // eslint-disable-line
headersTimeout: 'asd'
keepAliveTimeout: 'asd'
}) // eslint-disable-line
t.fail()
} catch (err) {
t.ok(err instanceof errors.InvalidArgumentError)
t.strictEqual(err.message, 'invalid headersTimeout')
t.strictEqual(err.message, 'invalid keepAliveTimeout')
}

@@ -297,13 +303,5 @@

new Client(new URL('http://localhost:200'), { // eslint-disable-line
idleTimeout: 'asd'
}) // eslint-disable-line
} catch (err) {
t.ok(err instanceof errors.InvalidArgumentError)
t.strictEqual(err.message, 'invalid idleTimeout')
}
try {
new Client(new URL('http://localhost:200'), { // eslint-disable-line
keepAliveMaxTimeout: 'asd'
}) // eslint-disable-line
t.fail()
} catch (err) {

@@ -318,2 +316,3 @@ t.ok(err instanceof errors.InvalidArgumentError)

}) // eslint-disable-line
t.fail()
} catch (err) {

@@ -328,2 +327,3 @@ t.ok(err instanceof errors.InvalidArgumentError)

}) // eslint-disable-line
t.fail()
} catch (err) {

@@ -335,14 +335,6 @@ t.ok(err instanceof errors.InvalidArgumentError)

try {
new Client(new URL('http://localhost:200'), { // eslint-disable-line
socketTimeout: 'asd'
})
} catch (err) {
t.ok(err instanceof errors.InvalidArgumentError)
t.strictEqual(err.message, 'invalid socketTimeout')
}
try {
new Client({ // eslint-disable-line
protocol: 'asd'
})
t.fail()
} catch (err) {

@@ -357,2 +349,3 @@ t.ok(err instanceof errors.InvalidArgumentError)

})
t.fail()
} catch (err) {

@@ -367,2 +360,3 @@ t.ok(err instanceof errors.InvalidArgumentError)

})
t.fail()
} catch (err) {

@@ -375,2 +369,3 @@ t.ok(err instanceof errors.InvalidArgumentError)

new Client(1) // eslint-disable-line
t.fail()
} catch (err) {

@@ -384,2 +379,3 @@ t.ok(err instanceof errors.InvalidArgumentError)

client.destroy(null, null)
t.fail()
} catch (err) {

@@ -393,2 +389,3 @@ t.ok(err instanceof errors.InvalidArgumentError)

client.close(null)
t.fail()
} catch (err) {

@@ -400,8 +397,31 @@ t.ok(err instanceof errors.InvalidArgumentError)

try {
new Client(new URL('http://localhost:200'), { keepAlive: 'true' }) // eslint-disable-line
const client = new Client(new URL('http://localhost:200'), { maxKeepAliveTimeout: 1e3 })
client.close(null)
t.fail()
} catch (err) {
t.ok(err instanceof errors.InvalidArgumentError)
t.strictEqual(err.message, 'invalid keepAlive')
t.strictEqual(err.message, 'unsupported maxKeepAliveTimeout, use keepAliveMaxTimeout instead')
}
try {
new Client(new URL('http://localhost:200'), { idleTimeout: 30e3 }) // eslint-disable-line
} catch (err) {
t.ok(err instanceof errors.InvalidArgumentError)
t.strictEqual(err.message, 'unsupported idleTimeout, use keepAliveTimeout instead')
}
try {
new Client(new URL('http://localhost:200'), { socketTimeout: 30e3 }) // eslint-disable-line
} catch (err) {
t.ok(err instanceof errors.InvalidArgumentError)
t.strictEqual(err.message, 'unsupported socketTimeout')
}
try {
new Client(new URL('http://localhost:200'), { headersTimeout: 30e3 }) // eslint-disable-line
} catch (err) {
t.ok(err instanceof errors.InvalidArgumentError)
t.strictEqual(err.message, 'unsupported headersTimeout')
}
t.end()

@@ -408,0 +428,0 @@ })

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

const http = require('http')
const FakeTimers = require('@sinonjs/fake-timers')

@@ -34,3 +35,3 @@ test('keep-alive header', (t) => {

t.fail()
}, 3e3)
}, 2e3)
client.on('disconnect', () => {

@@ -45,2 +46,38 @@ t.pass()

test('keep-alive header 0', (t) => {
t.plan(2)
const clock = FakeTimers.install()
t.teardown(clock.uninstall.bind(clock))
const server = createServer((socket) => {
socket.write('HTTP/1.1 200 OK\r\n')
socket.write('Content-Length: 0\r\n')
socket.write('Keep-Alive: timeout=1s\r\n')
socket.write('Connection: keep-alive\r\n')
socket.write('\r\n\r\n')
})
t.teardown(server.close.bind(server))
server.listen(0, () => {
const client = new Client(`http://localhost:${server.address().port}`, {
keepAliveTimeoutThreshold: 500
})
t.teardown(client.destroy.bind(client))
client.request({
path: '/',
method: 'GET'
}, (err, { body }) => {
t.error(err)
body.on('end', () => {
client.on('disconnect', () => {
t.pass()
})
clock.tick(600)
}).resume()
})
})
})
test('keep-alive header 1', (t) => {

@@ -128,3 +165,3 @@ t.plan(2)

const client = new Client(`http://localhost:${server.address().port}`, {
idleTimeout: 1e3
keepAliveTimeout: 1e3
})

@@ -165,3 +202,3 @@ t.teardown(client.destroy.bind(client))

const client = new Client(`http://localhost:${server.address().port}`, {
idleTimeout: 30e3,
keepAliveTimeout: 30e3,
keepAliveTimeoutThreshold: 29e3

@@ -203,3 +240,3 @@ })

const client = new Client(`http://localhost:${server.address().port}`, {
idleTimeout: 30e3,
keepAliveTimeout: 30e3,
keepAliveMaxTimeout: 1e3

@@ -300,3 +337,3 @@ })

server.listen(0, () => {
const client = new Client(`http://localhost:${server.address().port}`, { keepAlive: false })
const client = new Client(`http://localhost:${server.address().port}`, { pipelining: 0 })
t.teardown(client.destroy.bind(client))

@@ -323,43 +360,1 @@

})
test('Disable keep alive concurrency 1', (t) => {
t.plan(8)
const ports = []
const server = http.createServer((req, res) => {
t.false(ports.includes(req.socket.remotePort))
ports.push(req.socket.remotePort)
t.match(req.headers, { connection: 'close' })
res.writeHead(200, { connection: 'close' })
res.end()
})
t.teardown(server.close.bind(server))
server.listen(0, () => {
const client = new Client(`http://localhost:${server.address().port}`, {
keepAlive: false,
pipelining: 2
})
t.teardown(client.destroy.bind(client))
client.request({
path: '/',
method: 'GET'
}, (err, { body }) => {
t.error(err)
body.on('end', () => {
t.pass()
}).resume()
})
client.request({
path: '/',
method: 'GET'
}, (err, { body }) => {
t.error(err)
body.on('end', () => {
t.pass()
}).resume()
})
})
})

@@ -73,1 +73,30 @@ 'use strict'

})
test('trailers', (t) => {
t.plan(2)
const server = createServer((req, res) => {
res.writeHead(200, { Trailer: 'Content-MD5' })
res.addTrailers({ 'Content-MD5': 'test' })
res.end()
})
t.tearDown(server.close.bind(server))
server.listen(0, async () => {
const client = new Client(`http://localhost:${server.address().port}`)
t.tearDown(client.close.bind(client))
const { body, trailers } = await client.request({
path: '/',
method: 'GET'
})
t.strictDeepEqual(trailers, {})
body
.on('data', () => t.fail())
.on('end', () => {
t.strictDeepEqual(trailers, { 'content-md5': 'test' })
})
})
})

@@ -660,1 +660,108 @@ 'use strict'

})
test('stream needDrain', (t) => {
t.plan(3)
const server = createServer((req, res) => {
res.end(Buffer.alloc(4096))
})
t.tearDown(server.close.bind(server))
server.listen(0, () => {
const client = new Client(`http://localhost:${server.address().port}`)
t.tearDown(() => {
client.destroy()
})
const dst = new PassThrough()
dst.pause()
if (dst.writableNeedDrain === undefined) {
Object.defineProperty(dst, 'writableNeedDrain', {
get () {
return this._writableState.needDrain
}
})
}
while (dst.write(Buffer.alloc(4096))) {
}
const orgWrite = dst.write
dst.write = () => t.fail()
const p = client.stream({
path: '/',
method: 'GET'
}, () => {
t.strictEqual(dst._writableState.needDrain, true)
t.strictEqual(dst.writableNeedDrain, true)
setImmediate(() => {
dst.write = (...args) => {
orgWrite.call(dst, ...args)
}
dst.resume()
})
return dst
})
p.then(() => {
t.pass()
})
})
})
test('stream legacy needDrain', (t) => {
t.plan(3)
const server = createServer((req, res) => {
res.end(Buffer.alloc(4096))
})
t.tearDown(server.close.bind(server))
server.listen(0, () => {
const client = new Client(`http://localhost:${server.address().port}`)
t.tearDown(() => {
client.destroy()
})
const dst = new PassThrough()
dst.pause()
if (dst.writableNeedDrain !== undefined) {
Object.defineProperty(dst, 'writableNeedDrain', {
get () {
}
})
}
while (dst.write(Buffer.alloc(4096))) {
}
const orgWrite = dst.write
dst.write = () => t.fail()
const p = client.stream({
path: '/',
method: 'GET'
}, () => {
t.strictEqual(dst._writableState.needDrain, true)
t.strictEqual(dst.writableNeedDrain, undefined)
setImmediate(() => {
dst.write = (...args) => {
orgWrite.call(dst, ...args)
}
dst.resume()
})
return dst
})
p.then(() => {
t.pass()
})
})
})

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

server.listen(0, () => {
const client = new Client(`http://localhost:${server.address().port}`)
const client = new Client(`http://localhost:${server.address().port}`, {
keepAliveTimeout: 300e3
})
t.tearDown(client.close.bind(client))

@@ -324,3 +326,3 @@

},
requestTimeout: 0,
headersTimeout: 0,
body: createReadStream(__filename)

@@ -360,3 +362,3 @@ }, (err, { statusCode, headers, body }) => {

method: 'POST',
requestTimeout: 0,
headersTimeout: 0,
body

@@ -363,0 +365,0 @@ }, (err, data) => {

@@ -23,5 +23,3 @@ const net = require('net')

server.listen(0, () => {
const client = new Client(`http://localhost:${server.address().port}`, {
headersTimeout: 1e3
})
const client = new Client(`http://localhost:${server.address().port}`)
t.tearDown(client.destroy.bind(client))

@@ -31,3 +29,4 @@

method: 'GET',
path: '/nxt/_changes?feed=continuous&heartbeat=5000'
path: '/nxt/_changes?feed=continuous&heartbeat=5000',
headersTimeout: 1e3
}, (err, data) => {

@@ -34,0 +33,0 @@ t.error(err)

@@ -428,2 +428,4 @@ 'use strict'

t.strictEqual(buf, 'asd')
},
onError () {
}

@@ -563,2 +565,4 @@ })

t.pass()
},
onError () {
}

@@ -618,2 +622,4 @@ })

t.pass()
},
onError () {
}

@@ -662,2 +668,4 @@ })

t.pass()
},
onError () {
}

@@ -706,2 +714,4 @@ })

t.pass()
},
onError () {
}

@@ -708,0 +718,0 @@ })

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

server.listen(0, () => {
const client = new Client(`http://localhost:${server.address().port}`)
const client = new Client(`http://localhost:${server.address().port}`, { headersTimeout: 50 })
t.teardown(client.destroy.bind(client))
client.request({ path: '/', method: 'GET', requestTimeout: 50 }, (err, response) => {
t.ok(err instanceof errors.RequestTimeoutError)
client.request({ path: '/', method: 'GET' }, (err, response) => {
t.ok(err instanceof errors.HeadersTimeoutError)
})

@@ -44,4 +44,4 @@

test('Subsequent request starves', (t) => {
t.plan(3)
test('body timeout', (t) => {
t.plan(2)

@@ -52,6 +52,3 @@ const clock = FakeTimers.install()

const server = createServer((req, res) => {
setTimeout(() => {
res.end('hello')
}, 100)
clock.tick(50)
res.write('hello')
})

@@ -61,20 +58,15 @@ t.teardown(server.close.bind(server))

server.listen(0, () => {
const client = new Client(`http://localhost:${server.address().port}`, {
pipelining: 2
})
const client = new Client(`http://localhost:${server.address().port}`, { bodyTimeout: 50 })
t.teardown(client.destroy.bind(client))
client.request({ path: '/', method: 'GET' }, (err, response) => {
client.request({ path: '/', method: 'GET' }, (err, { body }) => {
t.error(err)
response.body
.resume()
.on('end', () => {
t.pass()
})
body.on('data', () => {
clock.tick(100)
}).on('error', (err) => {
t.ok(err instanceof errors.BodyTimeoutError)
})
})
client.request({ path: '/', method: 'GET', requestTimeout: 50 }, (err, response) => {
t.ok(err instanceof errors.RequestTimeoutError)
clock.tick(100)
})
clock.tick(50)
})

@@ -98,8 +90,10 @@ })

server.listen(0, () => {
const client = new Client(`http://localhost:${server.address().port}`)
const client = new Client(`http://localhost:${server.address().port}`, {
headersTimeout: 50
})
const ee = new EventEmitter()
t.teardown(client.destroy.bind(client))
client.request({ path: '/', method: 'GET', requestTimeout: 50, signal: ee }, (err, response) => {
t.ok(err instanceof errors.RequestTimeoutError)
client.request({ path: '/', method: 'GET', signal: ee }, (err, response) => {
t.ok(err instanceof errors.HeadersTimeoutError)
})

@@ -126,8 +120,10 @@

server.listen(0, () => {
const client = new Client(`http://localhost:${server.address().port}`)
const client = new Client(`http://localhost:${server.address().port}`, {
headersTimeout: 50
})
const abortController = new AbortController()
t.teardown(client.destroy.bind(client))
client.request({ path: '/', method: 'GET', requestTimeout: 50, signal: abortController.signal }, (err, response) => {
t.ok(err instanceof errors.RequestTimeoutError)
client.request({ path: '/', method: 'GET', signal: abortController.signal }, (err, response) => {
t.ok(err instanceof errors.HeadersTimeoutError)
})

@@ -156,6 +152,8 @@

server.listen(0, () => {
const client = new Client(`http://localhost:${server.address().port}`)
const client = new Client(`http://localhost:${server.address().port}`, {
headersTimeout: 50
})
t.teardown(client.destroy.bind(client))
client.request({ path: '/', method: 'GET', requestTimeout: 50, signal: ee }, (err, response) => {
client.request({ path: '/', method: 'GET', signal: ee }, (err, response) => {
t.ok(err instanceof errors.RequestAbortedError)

@@ -184,6 +182,8 @@ clock.tick(100)

server.listen(0, () => {
const client = new Client(`http://localhost:${server.address().port}`)
const client = new Client(`http://localhost:${server.address().port}`, {
headersTimeout: 50
})
t.teardown(client.destroy.bind(client))
client.request({ path: '/', method: 'GET', requestTimeout: 50, signal: abortController.signal }, (err, response) => {
client.request({ path: '/', method: 'GET', signal: abortController.signal }, (err, response) => {
t.ok(err instanceof errors.RequestAbortedError)

@@ -195,56 +195,2 @@ clock.tick(100)

test('Abort after timeout (EE)', (t) => {
t.plan(1)
const clock = FakeTimers.install()
t.teardown(clock.uninstall.bind(clock))
const ee = new EventEmitter()
const server = createServer((req, res) => {
setTimeout(() => {
res.end('hello')
}, 100)
clock.tick(50)
ee.emit('abort')
})
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: '/', method: 'GET', requestTimeout: 50, signal: ee }, (err, response) => {
t.ok(err instanceof errors.RequestTimeoutError)
clock.tick(100)
})
})
})
test('Abort after timeout (abort-controller)', (t) => {
t.plan(1)
const clock = FakeTimers.install()
t.teardown(clock.uninstall.bind(clock))
const abortController = new AbortController()
const server = createServer((req, res) => {
setTimeout(() => {
res.end('hello')
}, 100)
clock.tick(50)
abortController.abort()
})
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: '/', method: 'GET', requestTimeout: 50, signal: abortController.signal }, (err, response) => {
t.ok(err instanceof errors.RequestTimeoutError)
clock.tick(100)
})
})
})
test('Timeout with pipelining', (t) => {

@@ -265,15 +211,18 @@ t.plan(3)

server.listen(0, () => {
const client = new Client(`http://localhost:${server.address().port}`, { pipelining: 10 })
const client = new Client(`http://localhost:${server.address().port}`, {
pipelining: 10,
headersTimeout: 50
})
t.teardown(client.destroy.bind(client))
client.request({ path: '/', method: 'GET', requestTimeout: 50 }, (err, response) => {
t.ok(err instanceof errors.RequestTimeoutError)
client.request({ path: '/', method: 'GET' }, (err, response) => {
t.ok(err instanceof errors.HeadersTimeoutError)
})
client.request({ path: '/', method: 'GET', requestTimeout: 50 }, (err, response) => {
t.ok(err instanceof errors.RequestTimeoutError)
client.request({ path: '/', method: 'GET' }, (err, response) => {
t.ok(err instanceof errors.HeadersTimeoutError)
})
client.request({ path: '/', method: 'GET', requestTimeout: 50 }, (err, response) => {
t.ok(err instanceof errors.RequestTimeoutError)
client.request({ path: '/', method: 'GET' }, (err, response) => {
t.ok(err instanceof errors.HeadersTimeoutError)
})

@@ -298,7 +247,9 @@ })

server.listen(0, () => {
const client = new Client(`http://localhost:${server.address().port}`)
const client = new Client(`http://localhost:${server.address().port}`, {
headersTimeout: 50
})
t.teardown(client.destroy.bind(client))
client.request({ path: '/', method: 'GET', requestTimeout: 50 }, (err, response) => {
t.ok(err instanceof errors.RequestTimeoutError)
client.request({ path: '/', method: 'GET' }, (err, response) => {
t.ok(err instanceof errors.HeadersTimeoutError)
})

@@ -325,7 +276,9 @@

server.listen(0, () => {
const client = new Client(`http://localhost:${server.address().port}`)
const client = new Client(`http://localhost:${server.address().port}`, {
headersTimeout: 50
})
t.teardown(client.destroy.bind(client))
client.request({ path: '/', method: 'GET', requestTimeout: 50 }, (err, response) => {
t.ok(err instanceof errors.RequestTimeoutError)
client.request({ path: '/', method: 'GET' }, (err, response) => {
t.ok(err instanceof errors.HeadersTimeoutError)
})

@@ -346,5 +299,7 @@

server.listen(0, () => {
const client = new Client(`http://localhost:${server.address().port}`)
const client = new Client(`http://localhost:${server.address().port}`, {
headersTimeout: 100
})
client.request({ path: '/', method: 'GET', requestTimeout: 100 }, (err, response) => {
client.request({ path: '/', method: 'GET' }, (err, response) => {
t.ok(err instanceof errors.ClientDestroyedError)

@@ -370,7 +325,9 @@ })

server.listen(0, () => {
const client = new Client(`http://localhost:${server.address().port}`)
const client = new Client(`http://localhost:${server.address().port}`, {
headersTimeout: 100
})
t.teardown(client.destroy.bind(client))
client.request({ path: '/', method: 'GET', requestTimeout: 100 }, (err, response) => {
t.ok(err instanceof errors.RequestTimeoutError)
client.request({ path: '/', method: 'GET' }, (err, response) => {
t.ok(err instanceof errors.HeadersTimeoutError)
})

@@ -391,10 +348,39 @@

test('Validation', (t) => {
t.plan(1)
t.plan(4)
const client = new Client('http://localhost:3000')
t.teardown(client.destroy.bind(client))
try {
const client = new Client('http://localhost:3000', {
headersTimeout: 'foobar'
})
t.teardown(client.destroy.bind(client))
} catch (err) {
t.ok(err instanceof errors.InvalidArgumentError)
}
client.request({ path: '/', method: 'GET', requestTimeout: 'foobar' }, (err, response) => {
try {
const client = new Client('http://localhost:3000', {
headersTimeout: -1
})
t.teardown(client.destroy.bind(client))
} catch (err) {
t.ok(err instanceof errors.InvalidArgumentError)
})
}
try {
const client = new Client('http://localhost:3000', {
bodyTimeout: 'foobar'
})
t.teardown(client.destroy.bind(client))
} catch (err) {
t.ok(err instanceof errors.InvalidArgumentError)
}
try {
const client = new Client('http://localhost:3000', {
bodyTimeout: -1
})
t.teardown(client.destroy.bind(client))
} catch (err) {
t.ok(err instanceof errors.InvalidArgumentError)
}
})

@@ -417,6 +403,8 @@

server.listen(0, () => {
const client = new Client(`http://localhost:${server.address().port}`)
const client = new Client(`http://localhost:${server.address().port}`, {
headersTimeout: 0
})
t.teardown(client.destroy.bind(client))
client.request({ path: '/', method: 'GET', requestTimeout: 0 }, (err, response) => {
client.request({ path: '/', method: 'GET' }, (err, response) => {
t.error(err)

@@ -451,6 +439,8 @@ const bufs = []

server.listen(0, () => {
const client = new Client(`http://localhost:${server.address().port}`)
const client = new Client(`http://localhost:${server.address().port}`, {
headersTimeout: 0
})
t.teardown(client.destroy.bind(client))
client.request({ path: '/', method: 'GET', requestTimeout: 0 }, (err, response) => {
client.request({ path: '/', method: 'GET' }, (err, response) => {
t.error(err)

@@ -495,3 +485,3 @@ const bufs = []

}, (err) => {
t.ok(err instanceof errors.RequestTimeoutError)
t.ok(err instanceof errors.HeadersTimeoutError)
})

@@ -516,3 +506,5 @@ })

server.listen(0, () => {
const client = new Client(`http://localhost:${server.address().port}`)
const client = new Client(`http://localhost:${server.address().port}`, {
headersTimeout: 30e3
})
t.teardown(client.destroy.bind(client))

@@ -523,8 +515,7 @@

method: 'GET',
opaque: new PassThrough(),
requestTimeout: 30e3
opaque: new PassThrough()
}, (result) => {
t.fail('Should not be called')
}, (err) => {
t.ok(err instanceof errors.RequestTimeoutError)
t.ok(err instanceof errors.HeadersTimeoutError)
})

@@ -577,3 +568,3 @@ })

(err) => {
t.ok(err instanceof errors.RequestTimeoutError)
t.ok(err instanceof errors.HeadersTimeoutError)
}

@@ -599,3 +590,5 @@ )

server.listen(0, () => {
const client = new Client(`http://localhost:${server.address().port}`)
const client = new Client(`http://localhost:${server.address().port}`, {
headersTimeout: 30e3
})
t.teardown(client.destroy.bind(client))

@@ -613,4 +606,3 @@

path: '/',
method: 'PUT',
requestTimeout: 30e3
method: 'PUT'
}, (result) => {

@@ -630,3 +622,3 @@ t.fail('Should not be called')

(err) => {
t.ok(err instanceof errors.RequestTimeoutError)
t.ok(err instanceof errors.HeadersTimeoutError)
}

@@ -649,3 +641,4 @@ )

const client = new Client(`http://localhost:${server.address().port}`, {
socketTimeout: 200
bodyTimeout: 200,
headersTimeout: 100
})

@@ -657,6 +650,5 @@ t.teardown(client.destroy.bind(client))

path: '/',
method: 'GET',
requestTimeout: 100
method: 'GET'
}, (err, response) => {
t.ok(err instanceof errors.RequestTimeoutError)
t.ok(err instanceof errors.HeadersTimeoutError)
})

@@ -663,0 +655,0 @@

@@ -28,12 +28,20 @@ 'use strict'

pipelining: 1,
socketTimeout: 500
headersTimeout: 500,
bodyTimeout: 500
})
t.tearDown(client.close.bind(client))
client.request({ path: '/', method: 'GET', opaque: 'asd' }, (err, data) => {
t.ok(err instanceof errors.SocketTimeoutError) // we are expecting an error
client.request({
path: '/',
method: 'GET',
opaque: 'asd'
}, (err, data) => {
t.ok(err instanceof errors.HeadersTimeoutError) // we are expecting an error
t.strictEqual(data.opaque, 'asd')
})
client.request({ path: '/', method: 'GET' }, (err, { statusCode, headers, body }) => {
client.request({
path: '/',
method: 'GET'
}, (err, { statusCode, headers, body }) => {
t.error(err)

@@ -70,7 +78,8 @@ t.strictEqual(statusCode, 200)

const client = new Client(`http://localhost:${server.address().port}`, {
socketTimeout: 0
bodyTimeout: 0,
headersTimeout: 0
})
t.tearDown(client.close.bind(client))
client.request({ path: '/', method: 'GET', requestTimeout: 0 }, (err, result) => {
client.request({ path: '/', method: 'GET' }, (err, result) => {
t.error(err)

@@ -77,0 +86,0 @@ const bufs = []

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

const client = new Client(`https://localhost:${server.address().port}`, {
keepAlive: false,
pipelining: 0,
tls: {

@@ -38,0 +38,0 @@ ca,

@@ -9,70 +9,70 @@ import { Duplex, Readable, Writable } from 'stream'

{
const client = new Client('')
const client = new Client('')
// methods
expectAssignable<number>(client.pipelining)
expectAssignable<number>(client.pending)
expectAssignable<number>(client.running)
expectAssignable<number>(client.size)
expectAssignable<boolean>(client.connected)
expectAssignable<boolean>(client.busy)
expectAssignable<boolean>(client.closed)
expectAssignable<boolean>(client.destroyed)
// methods
expectAssignable<number>(client.pipelining)
expectAssignable<number>(client.pending)
expectAssignable<number>(client.running)
expectAssignable<number>(client.size)
expectAssignable<boolean>(client.connected)
expectAssignable<boolean>(client.busy)
expectAssignable<boolean>(client.closed)
expectAssignable<boolean>(client.destroyed)
// request
expectAssignable<PromiseLike<Client.ResponseData>>(client.request({ path: '', method: '' }))
expectAssignable<void>(client.request({ path: '', method: '' }, (err, data) => {
expectAssignable<Error | null>(err)
expectAssignable<Client.ResponseData>(data)
}))
// request
expectAssignable<PromiseLike<Client.ResponseData>>(client.request({ path: '', method: '' }))
expectAssignable<void>(client.request({ path: '', method: '' }, (err, data) => {
expectAssignable<Error | null>(err)
expectAssignable<Client.ResponseData>(data)
}))
// stream
expectAssignable<PromiseLike<Client.StreamData>>(client.stream({ path: '', method: '' }, data => {
expectAssignable<Client.StreamFactoryData>(data)
return new Writable()
}))
expectAssignable<void>(client.stream(
{ path: '', method: '' },
data => {
expectAssignable<Client.StreamFactoryData>(data)
return new Writable()
},
(err, data) => {
expectAssignable<Error | null>(err)
expectAssignable<Client.StreamData>(data)
}
))
// stream
expectAssignable<PromiseLike<Client.StreamData>>(client.stream({ path: '', method: '' }, data => {
expectAssignable<Client.StreamFactoryData>(data)
return new Writable()
}))
expectAssignable<void>(client.stream(
{ path: '', method: '' },
data => {
expectAssignable<Client.StreamFactoryData>(data)
return new Writable()
},
(err, data) => {
expectAssignable<Error | null>(err)
expectAssignable<Client.StreamData>(data)
}
))
// pipeline
expectAssignable<Duplex>(client.pipeline({ path: '', method: '' }, data => {
expectAssignable<Client.PipelineHandlerData>(data)
return new Readable()
}))
// pipeline
expectAssignable<Duplex>(client.pipeline({ path: '', method: '' }, data => {
expectAssignable<Client.PipelineHandlerData>(data)
return new Readable()
}))
// upgrade
expectAssignable<PromiseLike<Client.UpgradeData>>(client.upgrade({ path: '' }))
expectAssignable<void>(client.upgrade({ path: '' }, (err, data) => {
expectAssignable<Error | null>(err)
expectAssignable<Client.UpgradeData>(data)
}))
// upgrade
expectAssignable<PromiseLike<Client.UpgradeData>>(client.upgrade({ path: '' }))
expectAssignable<void>(client.upgrade({ path: '' }, (err, data) => {
expectAssignable<Error | null>(err)
expectAssignable<Client.UpgradeData>(data)
}))
// connect
expectAssignable<PromiseLike<Client.ConnectData>>(client.connect({ path: '' }))
expectAssignable<void>(client.connect({ path: '' }, (err, data) => {
expectAssignable<Error | null>(err)
expectAssignable<Client.ConnectData>(data)
}))
// connect
expectAssignable<PromiseLike<Client.ConnectData>>(client.connect({ path: '' }))
expectAssignable<void>(client.connect({ path: '' }, (err, data) => {
expectAssignable<Error | null>(err)
expectAssignable<Client.ConnectData>(data)
}))
// dispatch
expectAssignable<void>(client.dispatch({ path: '', method: '' }, {}))
// dispatch
expectAssignable<void>(client.dispatch({ path: '', method: '' }, {}))
// close
expectAssignable<PromiseLike<void>>(client.close())
expectAssignable<void>(client.close(() => {}))
// close
expectAssignable<PromiseLike<void>>(client.close())
expectAssignable<void>(client.close(() => {}))
// destroy
expectAssignable<PromiseLike<void>>(client.destroy())
expectAssignable<PromiseLike<void>>(client.destroy(new Error()))
expectAssignable<void>(client.destroy(() => {}))
expectAssignable<void>(client.destroy(new Error(), () => {}))
}
// destroy
expectAssignable<PromiseLike<void>>(client.destroy())
expectAssignable<PromiseLike<void>>(client.destroy(new Error()))
expectAssignable<void>(client.destroy(() => {}))
expectAssignable<void>(client.destroy(new Error(), () => {}))
}

@@ -16,7 +16,2 @@ import { expectAssignable } from 'tsd'

expectAssignable<Errors.UndiciError>(new Errors.RequestTimeoutError())
expectAssignable<Errors.RequestTimeoutError>(new Errors.RequestTimeoutError())
expectAssignable<'RequestTimeoutError'>(new Errors.RequestTimeoutError().name)
expectAssignable<'UND_ERR_REQUEST_TIMEOUT'>(new Errors.RequestTimeoutError().code)
expectAssignable<Errors.UndiciError>(new Errors.InvalidReturnError())

@@ -63,12 +58,12 @@ expectAssignable<Errors.InvalidReturnError>(new Errors.InvalidReturnError())

{
// @ts-ignore
function f (): Errors.HeadersTimeoutError | Errors.SocketTimeoutError { return }
// @ts-ignore
function f (): Errors.HeadersTimeoutError | Errors.SocketTimeoutError { return }
const e = f()
const e = f()
if (e.code === 'UND_ERR_HEADERS_TIMEOUT') {
expectAssignable<Errors.HeadersTimeoutError>(e)
} else if (e.code === 'UND_ERR_SOCKET_TIMEOUT') {
expectAssignable<Errors.SocketTimeoutError>(e)
}
}
if (e.code === 'UND_ERR_HEADERS_TIMEOUT') {
expectAssignable<Errors.HeadersTimeoutError>(e)
} else if (e.code === 'UND_ERR_SOCKET_TIMEOUT') {
expectAssignable<Errors.SocketTimeoutError>(e)
}
}

@@ -9,70 +9,70 @@ import { Duplex, Readable, Writable } from 'stream'

{
const pool = new Pool('', {})
const pool = new Pool('', {})
// methods
expectAssignable<number>(pool.pipelining)
expectAssignable<number>(pool.pending)
expectAssignable<number>(pool.running)
expectAssignable<number>(pool.size)
expectAssignable<boolean>(pool.connected)
expectAssignable<boolean>(pool.busy)
expectAssignable<boolean>(pool.closed)
expectAssignable<boolean>(pool.destroyed)
// methods
expectAssignable<number>(pool.pipelining)
expectAssignable<number>(pool.pending)
expectAssignable<number>(pool.running)
expectAssignable<number>(pool.size)
expectAssignable<boolean>(pool.connected)
expectAssignable<boolean>(pool.busy)
expectAssignable<boolean>(pool.closed)
expectAssignable<boolean>(pool.destroyed)
// request
expectAssignable<PromiseLike<Client.ResponseData>>(pool.request({ path: '', method: '' }))
expectAssignable<void>(pool.request({ path: '', method: '' }, (err, data) => {
expectAssignable<Error | null>(err)
expectAssignable<Client.ResponseData>(data)
}))
// request
expectAssignable<PromiseLike<Client.ResponseData>>(pool.request({ path: '', method: '' }))
expectAssignable<void>(pool.request({ path: '', method: '' }, (err, data) => {
expectAssignable<Error | null>(err)
expectAssignable<Client.ResponseData>(data)
}))
// stream
expectAssignable<PromiseLike<Client.StreamData>>(pool.stream({ path: '', method: '' }, data => {
expectAssignable<Client.StreamFactoryData>(data)
return new Writable()
}))
expectAssignable<void>(pool.stream(
{ path: '', method: '' },
data => {
expectAssignable<Client.StreamFactoryData>(data)
return new Writable()
},
(err, data) => {
expectAssignable<Error | null>(err)
expectAssignable<Client.StreamData>(data)
}
))
// stream
expectAssignable<PromiseLike<Client.StreamData>>(pool.stream({ path: '', method: '' }, data => {
expectAssignable<Client.StreamFactoryData>(data)
return new Writable()
}))
expectAssignable<void>(pool.stream(
{ path: '', method: '' },
data => {
expectAssignable<Client.StreamFactoryData>(data)
return new Writable()
},
(err, data) => {
expectAssignable<Error | null>(err)
expectAssignable<Client.StreamData>(data)
}
))
// pipeline
expectAssignable<Duplex>(pool.pipeline({ path: '', method: '' }, data => {
expectAssignable<Client.PipelineHandlerData>(data)
return new Readable()
}))
// pipeline
expectAssignable<Duplex>(pool.pipeline({ path: '', method: '' }, data => {
expectAssignable<Client.PipelineHandlerData>(data)
return new Readable()
}))
// upgrade
expectAssignable<PromiseLike<Client.UpgradeData>>(pool.upgrade({ path: '' }))
expectAssignable<void>(pool.upgrade({ path: '' }, (err, data) => {
expectAssignable<Error | null>(err)
expectAssignable<Client.UpgradeData>(data)
}))
// upgrade
expectAssignable<PromiseLike<Client.UpgradeData>>(pool.upgrade({ path: '' }))
expectAssignable<void>(pool.upgrade({ path: '' }, (err, data) => {
expectAssignable<Error | null>(err)
expectAssignable<Client.UpgradeData>(data)
}))
// connect
expectAssignable<PromiseLike<Client.ConnectData>>(pool.connect({ path: '' }))
expectAssignable<void>(pool.connect({ path: '' }, (err, data) => {
expectAssignable<Error | null>(err)
expectAssignable<Client.ConnectData>(data)
}))
// connect
expectAssignable<PromiseLike<Client.ConnectData>>(pool.connect({ path: '' }))
expectAssignable<void>(pool.connect({ path: '' }, (err, data) => {
expectAssignable<Error | null>(err)
expectAssignable<Client.ConnectData>(data)
}))
// dispatch
expectAssignable<void>(pool.dispatch({ path: '', method: '' }, {}))
// dispatch
expectAssignable<void>(pool.dispatch({ path: '', method: '' }, {}))
// close
expectAssignable<PromiseLike<void>>(pool.close())
expectAssignable<void>(pool.close(() => {}))
// close
expectAssignable<PromiseLike<void>>(pool.close())
expectAssignable<void>(pool.close(() => {}))
// destroy
expectAssignable<PromiseLike<void>>(pool.destroy())
expectAssignable<PromiseLike<void>>(pool.destroy(new Error()))
expectAssignable<void>(pool.destroy(() => {}))
expectAssignable<void>(pool.destroy(new Error(), () => {}))
}
// destroy
expectAssignable<PromiseLike<void>>(pool.destroy())
expectAssignable<PromiseLike<void>>(pool.destroy(new Error()))
expectAssignable<void>(pool.destroy(() => {}))
expectAssignable<void>(pool.destroy(new Error(), () => {}))
}

@@ -5,5 +5,6 @@ import { URL } from 'url'

import { EventEmitter } from 'events'
import { AbortController } from 'abort-controller'
import { IncomingHttpHeaders } from 'http'
type AbortSignal = unknown;
export = Client

@@ -13,195 +14,178 @@

declare class Client extends EventEmitter {
constructor(url: string | URL, options?: Client.Options);
constructor(url: string | URL, options?: Client.Options);
/** Property to get and set the pipelining factor. */
pipelining: number;
/** Number of queued requests. */
pending: number;
/** Number of inflight requests. */
running: number;
/** Number of pending and running requests. */
size: number;
/** True if the client has an active connection. The client will lazily create a connection when it receives a request and will destroy it if there is no activity for the duration of the `timeout` value. */
connected: boolean;
/** True if pipeline is saturated or blocked. Indicates whether dispatching further requests is meaningful. */
busy: boolean;
/** True after `client.close()` has been called. */
closed: boolean;
/** True after `client.destroyed()` has been called or `client.close()` has been called and the client shutdown has completed. */
destroyed: boolean;
/** Property to get and set the pipelining factor. */
pipelining: number;
/** Number of queued requests. */
pending: number;
/** Number of inflight requests. */
running: number;
/** Number of pending and running requests. */
size: number;
/** True if the client has an active connection. The client will lazily create a connection when it receives a request and will destroy it if there is no activity for the duration of the `timeout` value. */
connected: boolean;
/** True if pipeline is saturated or blocked. Indicates whether dispatching further requests is meaningful. */
busy: boolean;
/** True after `client.close()` has been called. */
closed: boolean;
/** True after `client.destroyed()` has been called or `client.close()` has been called and the client shutdown has completed. */
destroyed: boolean;
/** Performs a HTTP request */
request(options: Client.RequestOptions): PromiseLike<Client.ResponseData>;
request(options: Client.RequestOptions, callback: (err: Error | null, data: Client.ResponseData) => void): void;
/** Performs a HTTP request */
request(options: Client.RequestOptions): PromiseLike<Client.ResponseData>;
request(options: Client.RequestOptions, callback: (err: Error | null, data: Client.ResponseData) => void): void;
/** A faster version of `Client.request` */
stream(options: Client.RequestOptions, factory: Client.StreamFactory): PromiseLike<Client.StreamData>;
stream(options: Client.RequestOptions, factory: Client.StreamFactory, callback: (err: Error | null, data: Client.StreamData) => void): void;
/** A faster version of `Client.request` */
stream(options: Client.RequestOptions, factory: Client.StreamFactory): PromiseLike<Client.StreamData>;
stream(options: Client.RequestOptions, factory: Client.StreamFactory, callback: (err: Error | null, data: Client.StreamData) => void): void;
/** For easy use with `stream.pipeline` */
pipeline(options: Client.PipelineOptions, handler: Client.PipelineHandler): Duplex
/** For easy use with `stream.pipeline` */
pipeline(options: Client.PipelineOptions, handler: Client.PipelineHandler): Duplex;
/** Upgrade to a different protocol */
upgrade(options: Client.UpgradeOptions): PromiseLike<Client.UpgradeData>;
upgrade(options: Client.UpgradeOptions, callback: (err: Error | null, data: Client.UpgradeData) => void): void;
/** Upgrade to a different protocol */
upgrade(options: Client.UpgradeOptions): PromiseLike<Client.UpgradeData>;
upgrade(options: Client.UpgradeOptions, callback: (err: Error | null, data: Client.UpgradeData) => void): void;
/** Starts two-way communications with the requested resource */
connect(options: Client.ConnectOptions): PromiseLike<Client.ConnectData>;
connect(options: Client.ConnectOptions, callback: (err: Error | null, data: Client.ConnectData) => void): void;
/** Starts two-way communications with the requested resource */
connect(options: Client.ConnectOptions): PromiseLike<Client.ConnectData>;
connect(options: Client.ConnectOptions, callback: (err: Error | null, data: Client.ConnectData) => void): void;
/** This is the low level API which all the preceding APIs are implemented on top of. This API is expected to evolve through semver-major versions and is less stable than the preceding higher level APIs. It is primarily intended for library developers who implement higher level APIs on top of this. */
dispatch(options: Client.DispatchOptions, handlers: Client.DispatchHandlers): void;
/** This is the low level API which all the preceding APIs are implemented on top of. This API is expected to evolve through semver-major versions and is less stable than the preceding higher level APIs. It is primarily intended for library developers who implement higher level APIs on top of this. */
dispatch(options: Client.DispatchOptions, handlers: Client.DispatchHandlers): void;
/** Closes the client and gracefully waits for enqueued requests to complete before invoking the callback (or returnning a promise if no callback is provided). */
close(): PromiseLike<void>;
close(callback: () => void): void;
/** Closes the client and gracefully waits for enqueued requests to complete before invoking the callback (or returnning a promise if no callback is provided). */
close(): PromiseLike<void>;
close(callback: () => void): void;
/** Destroy the client abruptly with the given err. All the pending and running requests will be asynchronously aborted and error. Waits until socket is closed before invoking the callback (or returnning a promise if no callback is provided). Since this operation is asynchronously dispatched there might still be some progress on dispatched requests. */
destroy(): PromiseLike<void>;
destroy(err: Error | null): PromiseLike<void>;
destroy(callback: () => void): void;
destroy(err: Error | null, callback: () => void): void;
/** Destroy the client abruptly with the given err. All the pending and running requests will be asynchronously aborted and error. Waits until socket is closed before invoking the callback (or returnning a promise if no callback is provided). Since this operation is asynchronously dispatched there might still be some progress on dispatched requests. */
destroy(): PromiseLike<void>;
destroy(err: Error | null): PromiseLike<void>;
destroy(callback: () => void): void;
destroy(err: Error | null, callback: () => void): void;
}
declare namespace Client {
export interface Options {
/** the timeout after which a socket with active requests will time out. Monitors time between activity on a connected socket. Use `0` to disable it entirely. Default: `30e3` milliseconds (30s). */
socketTimeout?: number;
/** an IPC endpoint, either Unix domain socket or Windows named pipe. Default: `null`. */
socketPath?: string | null;
/** the timeout after which a socket without active requests will time out. Monitors time between activity on a connected socket. This value may be overriden by *keep-alive* hints from the server. Default: `4e3` milliseconds (4s). */
idleTimeout?: number;
/** enable or disable keep alive connections. Default: `true`. */
keepAlive?: boolean;
/** the maximum allowed `idleTimeout` when overriden by *keep-alive* hints from the server. Default: `600e3` milliseconds (10min). */
keepAliveMaxTimeout?: number;
/** A number subtracted from server *keep-alive* hints when overriding `idleTimeout` to account for timing inaccuries caused by e.g. transport latency. Default: `1e3` milliseconds (1s). */
keepAliveTimeoutThreshold?: number;
/** The timeout after which a request will time out. Monitors time between request is dispatched on socket and receiving a response. Use `0` to disable it entirely. Default: `30e3` milliseconds (30s). */
requestTimeout?: 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). Default: `1`. */
pipelining?: number;
/** An options object which in the case of `https` will be passed to [`tls.connect`](https://nodejs.org/api/tls.html#tls_tls_connect_options_callback). Default: `null`. */
tls?: TlsOptions | null;
/** The maximum length of request headers in bytes. Default: `16384` (16KiB). */
maxHeaderSize?: number;
/** The amount of time the parser will wait to receive the complete HTTP headers (Node 14 and above only). Default: `30e3` milliseconds (30s). */
headersTimeout?: number;
}
export interface Options {
/** an IPC endpoint, either Unix domain socket or Windows named pipe. Default: `null`. */
socketPath?: string | null;
/** the timeout after which a socket without active requests will time out. Monitors time between activity on a connected socket. This value may be overriden by *keep-alive* hints from the server. Default: `4e3` milliseconds (4s). */
keepAliveTimeout?: number;
/** the maximum allowed `idleTimeout` when overriden by *keep-alive* hints from the server. Default: `600e3` milliseconds (10min). */
keepAliveMaxTimeout?: number;
/** A number subtracted from server *keep-alive* hints when overriding `idleTimeout` to account for timing inaccuries caused by e.g. transport latency. Default: `1e3` milliseconds (1s). */
keepAliveTimeoutThreshold?: 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). Default: `1`. */
pipelining?: number;
/** An options object which in the case of `https` will be passed to [`tls.connect`](https://nodejs.org/api/tls.html#tls_tls_connect_options_callback). Default: `null`. */
tls?: TlsOptions | null;
/** The maximum length of request headers in bytes. Default: `16384` (16KiB). */
maxHeaderSize?: number;
/** The amount of time the parser will wait to receive the complete HTTP headers (Node 14 and above only). Default: `30e3` milliseconds (30s). */
headersTimeout?: number;
}
export interface RequestOptions {
path: string;
method: string;
opaque?: unknown;
/** Default: `null` */
body?: string | Buffer | Uint8Array | Readable | null;
/** an object with header-value pairs or an array with header-value pairs bi-indexed (`['header1', 'value1', 'header2', 'value2']`). Default: `null`. */
headers?: IncomingHttpHeaders | string[] | null;
/** Default: `null` */
signal?: AbortController | EventEmitter | null;
/** The timeout after which a request will time out, in milliseconds. Monitors time between request being enqueued and receiving a response. Use `0` to disable it entirely. Default: `30e3` milliseconds (30s). */
requestTimeout?: number;
/** Whether the requests can be safely retried or not. If `false` the request won't be sent until all preceeding requests in the pipeline has completed. Default: `true` if `method` is `HEAD` or `GET`. */
idempotent?: boolean;
}
export interface DispatchOptions {
path: string;
method: string;
/** Default: `null` */
body?: string | Buffer | Uint8Array | Readable | null;
/** Default: `null` */
headers?: IncomingHttpHeaders | null;
/** The timeout after which a request will time out, in milliseconds. Monitors time between receiving a complete headers. Use 0 to disable it entirely. Default: `30e3` milliseconds (30s). */
headersTimeout?: number;
/** The timeout after which a request will time out, in milliseconds. Monitors time between receiving a body data. Use 0 to disable it entirely. Default: `30e3` milliseconds (30s). */
bodyTimeout?: number;
/** Whether the requests can be safely retried or not. If `false` the request won't be sent until all preceeding requests in the pipeline has completed. Default: `true` if `method` is `HEAD` or `GET`. */
idempotent?: boolean;
}
export interface PipelineOptions extends RequestOptions {
/** `true` if the `handler` will return an object stream. Default: `false` */
objectMode?: boolean
}
export interface RequestOptions extends DispatchOptions {
opaque?: unknown;
/** Default: `null` */
signal?: AbortSignal | EventEmitter | null;
}
export interface UpgradeOptions {
path: string;
opaque?: unknown;
/** Default: `'GET'` */
method?: string;
/** Default: `null` */
headers?: IncomingHttpHeaders | null;
/** Default: `null` */
signal?: AbortController | EventEmitter | null;
/** The timeout after which a request will time out, in milliseconds. Monitors time between request being enqueued and receiving a response. Use `0` to disable it entirely. Default: `30e3` milliseconds (30s). */
requestTimeout?: number;
/** A string of comma separated protocols, in descending preference order. Default: `'Websocket'` */
protocol?: string;
}
export interface PipelineOptions extends RequestOptions {
/** `true` if the `handler` will return an object stream. Default: `false` */
objectMode?: boolean;
}
export interface ConnectOptions {
path: string;
opaque?: unknown;
/** Default: `null` */
headers?: IncomingHttpHeaders | null;
/** Default: `null` */
signal?: AbortController | EventEmitter | null;
/** The timeout after which a request will time out, in milliseconds. Monitors time between request being enqueued and receiving a response. Use `0` to disable it entirely. Default: `30e3` milliseconds (30s). */
requestTimeout?: number;
}
export interface UpgradeOptions {
path: string;
method?: string;
/** Default: `null` */
headers?: IncomingHttpHeaders | null;
/** The timeout after which a request will time out, in milliseconds. Monitors time between receiving a complete headers. Use 0 to disable it entirely. Default: `30e3` milliseconds (30s). */
headersTimeout?: number;
/** A string of comma separated protocols, in descending preference order. Default: `'Websocket'` */
protocol?: string;
/** Default: `null` */
signal?: AbortSignal | EventEmitter | null;
}
export interface DispatchOptions {
path: string;
method: string;
/** Default: `null` */
body?: string | Buffer | Uint8Array | Readable | null;
/** Default: `null` */
headers?: IncomingHttpHeaders | null;
/** The timeout after which a request will time out, in milliseconds. Monitors time between request being enqueued and receiving a response. Use `0` to disable it entirely. Default: `30e3` milliseconds (30s). */
requestTimeout?: number;
/** Whether the requests can be safely retried or not. If `false` the request won't be sent until all preceeding requests in the pipeline has completed. Default: `true` if `method` is `HEAD` or `GET`. */
idempotent?: boolean;
}
export interface ConnectOptions {
path: string;
/** Default: `null` */
headers?: IncomingHttpHeaders | null;
/** The timeout after which a request will time out, in milliseconds. Monitors time between receiving a complete headers. Use 0 to disable it entirely. Default: `30e3` milliseconds (30s). */
headersTimeout?: number;
/** Default: `null` */
signal?: AbortSignal | EventEmitter | null;
}
export interface ResponseData {
statusCode: number;
headers: IncomingHttpHeaders;
body: Readable;
opaque?: unknown;
}
export interface ResponseData {
statusCode: number;
headers: IncomingHttpHeaders;
body: Readable;
opaque?: unknown;
}
export interface StreamData {
opaque: unknown;
trailers: Record<string, unknown>;
}
export interface StreamData {
opaque: unknown;
trailers: Record<string, unknown>;
}
export interface UpgradeData {
headers: IncomingHttpHeaders;
socket: Duplex;
opaque: unknown;
}
export interface UpgradeData {
headers: IncomingHttpHeaders;
socket: Duplex;
opaque: unknown;
}
export interface ConnectData {
statusCode: number;
headers: IncomingHttpHeaders;
socket: Duplex;
opaque: unknown;
}
export interface ConnectData {
statusCode: number;
headers: IncomingHttpHeaders;
socket: Duplex;
opaque: unknown;
}
export interface StreamFactoryData {
statusCode: number;
headers: IncomingHttpHeaders;
opaque: unknown
}
export type StreamFactory = (data: StreamFactoryData) => Writable
export interface StreamFactoryData {
statusCode: number;
headers: IncomingHttpHeaders;
opaque: unknown;
}
export type StreamFactory = (data: StreamFactoryData) => Writable
export interface PipelineHandlerData {
statusCode: number;
headers: IncomingHttpHeaders;
opaque: unknown;
body: Readable
}
export interface PipelineHandlerData {
statusCode: number;
headers: IncomingHttpHeaders;
opaque: unknown;
body: Readable;
}
export type PipelineHandler = (data: PipelineHandlerData) => Readable
export type PipelineHandler = (data: PipelineHandlerData) => Readable
export interface DispatchHandlers {
/** Invoked before request is dispatched on socket. May be invoked multiple times when a request is retried when the request at the head of the pipeline fails. */
onConnect?(abort: AbortController): void;
/** Invoked when request is upgraded either due to a `Upgrade` header or `CONNECT` method */
onUpgrade?(statusCode: number, headers: string[] | null, socket: Duplex): void;
/** Invoked when statusCode and headers have been received. May be invoked multiple times due to 1xx informational headers. */
onHeaders?(statusCode: number, headers: string[] | null, resume: () => void): boolean;
/** Invoked when response payload data is received */
onData?(chunk: Buffer): boolean;
/** Invoked when response payload and trailers have been received and the request has completed. */
onComplete?(trailers: string[] | null): void;
/** Invoked when an error has occurred. */
onError?(err: Error): void;
}
}
export interface DispatchHandlers {
/** Invoked before request is dispatched on socket. May be invoked multiple times when a request is retried when the request at the head of the pipeline fails. */
onConnect?(abort: () => void): void;
/** Invoked when request is upgraded either due to a `Upgrade` header or `CONNECT` method */
onUpgrade?(statusCode: number, headers: string[] | null, socket: Duplex): void;
/** Invoked when statusCode and headers have been received. May be invoked multiple times due to 1xx informational headers. */
onHeaders?(statusCode: number, headers: string[] | null, resume: () => void): boolean;
/** Invoked when response payload data is received */
onData?(chunk: Buffer): boolean;
/** Invoked when response payload and trailers have been received and the request has completed. */
onComplete?(trailers: string[] | null): void;
/** Invoked when an error has occurred. */
onError?(err: Error): void;
}
}
export = Errors
declare namespace Errors {
export class UndiciError extends Error { }
export class UndiciError extends Error { }
/** A header exceeds the `headersTimeout` option. */
export class HeadersTimeoutError extends UndiciError {
name: 'HeadersTimeoutError';
code: 'UND_ERR_HEADERS_TIMEOUT';
}
/** A header exceeds the `headersTimeout` option. */
export class HeadersTimeoutError extends UndiciError {
name: 'HeadersTimeoutError';
code: 'UND_ERR_HEADERS_TIMEOUT';
}
/** A socket exceeds the `socketTimeout` option. */
export class SocketTimeoutError extends UndiciError {
name: 'SocketTimeoutError';
code: 'UND_ERR_SOCKET_TIMEOUT';
}
/** A socket exceeds the `socketTimeout` option. */
export class SocketTimeoutError extends UndiciError {
name: 'SocketTimeoutError';
code: 'UND_ERR_SOCKET_TIMEOUT';
}
/** A request exceeds the `requestTimeout` option. */
export class RequestTimeoutError extends UndiciError {
name: 'RequestTimeoutError';
code: 'UND_ERR_REQUEST_TIMEOUT';
}
/** Passed an invalid argument. */
export class InvalidArgumentError extends UndiciError {
name: 'InvalidArgumentError';
code: 'UND_ERR_INVALID_ARG';
}
/** Passed an invalid argument. */
export class InvalidArgumentError extends UndiciError {
name: 'InvalidArgumentError';
code: 'UND_ERR_INVALID_ARG';
}
/** Returned an invalid value. */
export class InvalidReturnError extends UndiciError {
name: 'InvalidReturnError';
code: 'UND_ERR_INVALID_RETURN_VALUE';
}
/** Returned an invalid value. */
export class InvalidReturnError extends UndiciError {
name: 'InvalidReturnError';
code: 'UND_ERR_INVALID_RETURN_VALUE';
}
/** The request has been aborted by the user. */
export class RequestAbortedError extends UndiciError {
name: 'RequestAbortedError';
code: 'UND_ERR_ABORTED';
}
/** The request has been aborted by the user. */
export class RequestAbortedError extends UndiciError {
name: 'RequestAbortedError';
code: 'UND_ERR_ABORTED';
}
/** Expected error with reason. */
export class InformationalError extends UndiciError {
name: 'InformationalError';
code: 'UND_ERR_INFO';
}
/** Expected error with reason. */
export class InformationalError extends UndiciError {
name: 'InformationalError';
code: 'UND_ERR_INFO';
}
/** Body does not match content-length header. */
export class ContentLengthMismatchError extends UndiciError {
name: 'ContentLengthMismatchError';
code: 'UND_ERR_CONTENT_LENGTH_MISMATCH';
}
/** Body does not match content-length header. */
export class ContentLengthMismatchError extends UndiciError {
name: 'ContentLengthMismatchError';
code: 'UND_ERR_CONTENT_LENGTH_MISMATCH';
}
/** Trying to use a destroyed client. */
export class ClientDestroyedError extends UndiciError {
name: 'ClientDestroyedError';
code: 'UND_ERR_DESTROYED';
}
/** Trying to use a destroyed client. */
export class ClientDestroyedError extends UndiciError {
name: 'ClientDestroyedError';
code: 'UND_ERR_DESTROYED';
}
/** Trying to use a closed client. */
export class ClientClosedError extends UndiciError {
name: 'ClientClosedError';
code: 'UND_ERR_CLOSED';
}
/** Trying to use a closed client. */
export class ClientClosedError extends UndiciError {
name: 'ClientClosedError';
code: 'UND_ERR_CLOSED';
}
/** There is an error with the socket. */
export class SocketError extends UndiciError {
name: 'SocketError';
code: 'UND_ERR_SOCKET';
}
/** There is an error with the socket. */
export class SocketError extends UndiciError {
name: 'SocketError';
code: 'UND_ERR_SOCKET';
}
/** Encountered unsupported functionality. */
export class NotSupportedError extends UndiciError {
name: 'NotSupportedError';
code: 'UND_ERR_NOT_SUPPORTED';
}
}
/** Encountered unsupported functionality. */
export class NotSupportedError extends UndiciError {
name: 'NotSupportedError';
code: 'UND_ERR_NOT_SUPPORTED';
}
}

@@ -6,10 +6,10 @@ import Client from './client'

declare class Pool extends Client {
constructor(url: string, options?: Pool.Options)
constructor(url: string, options?: Pool.Options)
}
declare namespace Pool {
export interface Options extends Client.Options {
/** The number of clients to create. Default `100`. */
connections?: number
}
}
export interface Options extends Client.Options {
/** The number of clients to create. Default `100`. */
connections?: number
}
}

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

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

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc