Comparing version 2.2.1 to 3.0.0
@@ -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) |
@@ -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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 5 instances in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
395798
12774
56
82
651
20