Comparing version 3.1.0 to 3.2.0
import Pool from './types/pool' | ||
import Client from './types/client' | ||
import errors from './types/errors' | ||
import { Agent, setGlobalAgent, request, stream, pipeline } from './types/agent' | ||
export { Pool, Client, errors } | ||
export { Pool, Client, errors, Agent, setGlobalAgent, request, stream, pipeline } | ||
export default Undici | ||
@@ -14,2 +15,7 @@ | ||
var errors: typeof import('./types/errors'); | ||
var Agent: typeof import('./types/agent').Agent; | ||
var setGlobalAgent: typeof import('./types/agent').setGlobalAgent; | ||
var request: typeof import('./types/agent').request; | ||
var stream: typeof import('./types/agent').stream; | ||
var pipeline: typeof import('./types/agent').pipeline; | ||
} |
13
index.js
@@ -6,2 +6,3 @@ 'use strict' | ||
const Pool = require('./lib/pool') | ||
const { Agent, request, stream, pipeline, setGlobalAgent } = require('./lib/agent') | ||
@@ -14,8 +15,2 @@ Client.prototype.request = require('./lib/client-request') | ||
Pool.prototype.request = require('./lib/client-request') | ||
Pool.prototype.stream = require('./lib/client-stream') | ||
Pool.prototype.pipeline = require('./lib/client-pipeline') | ||
Pool.prototype.upgrade = require('./lib/client-upgrade') | ||
Pool.prototype.connect = require('./lib/client-connect') | ||
function undici (url, opts) { | ||
@@ -29,2 +24,8 @@ return new Pool(url, opts) | ||
undici.Agent = Agent | ||
undici.request = request | ||
undici.stream = stream | ||
undici.pipeline = pipeline | ||
undici.setGlobalAgent = setGlobalAgent | ||
module.exports = undici |
'use strict' | ||
/* global WeakRef, FinalizationRegistry */ | ||
@@ -8,2 +9,3 @@ const assert = require('assert') | ||
const net = require('net') | ||
const { NotSupportedError } = require('./errors') | ||
@@ -81,5 +83,5 @@ function nop () {} | ||
function parseHeaders (headers, obj = {}) { | ||
for (var i = 0; i < headers.length; i += 2) { | ||
var key = headers[i].toLowerCase() | ||
var val = obj[key] | ||
for (let i = 0; i < headers.length; i += 2) { | ||
const key = headers[i].toLowerCase() | ||
let val = obj[key] | ||
if (!val) { | ||
@@ -114,2 +116,41 @@ obj[key] = headers[i + 1] | ||
/* istanbul ignore next: https://github.com/tc39/proposal-weakrefs */ | ||
function weakCache (fn) { | ||
/* istanbul ignore next: */ | ||
if (typeof WeakRef === 'undefined' || typeof FinalizationRegistry === 'undefined') { | ||
throw new NotSupportedError('In order to use this feature, `WeakRef` and `FinalizationRegistry` must be defined as global objects. Check your Node.js version to be sure it is v14.6.0 or greater.') | ||
} | ||
const cache = new Map() | ||
const cleanup = new FinalizationRegistry(key => { | ||
// get the WeakRef from the cache | ||
const ref = cache.get(key) | ||
// if the WeakRef exists and the object has been reclaimed | ||
if (ref !== undefined && ref.deref() === undefined) { | ||
// remove the WeakRef from the cache | ||
cache.delete(key) | ||
} | ||
}) | ||
return key => { | ||
// check the cache for an existing WeakRef | ||
const ref = cache.get(key) | ||
// if one exists in the cache try to return the WeakRef | ||
if (ref !== undefined) { | ||
const cached = ref.deref() | ||
if (cached !== undefined) { | ||
return cached | ||
} | ||
} | ||
// otherwise, if it isn't in the cache or the reference has been cleaned up, create a new one! | ||
const value = fn(key) | ||
// add a WeakRef of the value to the cache | ||
cache.set(key, new WeakRef(value)) | ||
// add the value to the finalization registry | ||
cleanup.register(value, key) | ||
return value | ||
} | ||
} | ||
module.exports = { | ||
@@ -125,3 +166,4 @@ nop, | ||
bodyLength, | ||
isBuffer | ||
isBuffer, | ||
weakCache | ||
} |
@@ -17,14 +17,20 @@ 'use strict' | ||
const kClosedResolve = Symbol('closed resolve') | ||
const kOptions = Symbol('options') | ||
const kUrl = Symbol('url') | ||
const kOnDrain = Symbol('onDrain') | ||
const kOnConnect = Symbol('onConnect') | ||
const kOnDisconnect = Symbol('onDisconnect') | ||
class Pool extends EventEmitter { | ||
constructor (url, { | ||
connections, | ||
...options | ||
} = {}) { | ||
constructor (url, options = {}) { | ||
super() | ||
if (connections != null && (!Number.isFinite(connections) || connections <= 0)) { | ||
const { connections } = options | ||
if (connections != null && (!Number.isFinite(connections) || connections < 0)) { | ||
throw new InvalidArgumentError('invalid connections') | ||
} | ||
this[kUrl] = url | ||
this[kOptions] = JSON.parse(JSON.stringify(options)) | ||
this[kQueue] = new FixedQueue() | ||
@@ -34,8 +40,7 @@ this[kClosedPromise] = null | ||
this[kDestroyed] = false | ||
this[kClients] = Array.from({ | ||
length: connections || 10 | ||
}, () => new Client(url, options)) | ||
this[kClients] = [] | ||
const pool = this | ||
function onDrain () { | ||
this[kOnDrain] = function onDrain () { | ||
const queue = pool[kQueue] | ||
@@ -58,15 +63,9 @@ | ||
function onConnect () { | ||
this[kOnConnect] = function onConnect () { | ||
pool.emit('connect', this) | ||
} | ||
function onDisconnect () { | ||
this[kOnDisconnect] = function onDisconnect () { | ||
pool.emit('disconnect', this) | ||
} | ||
for (const client of this[kClients]) { | ||
client.on('drain', onDrain) | ||
client.on('connect', onConnect) | ||
client.on('disconnect', onDisconnect) | ||
} | ||
} | ||
@@ -84,4 +83,17 @@ | ||
const client = this[kClients].find(client => !client.busy) | ||
let client = this[kClients].find(client => !client.busy) | ||
if (!client) { | ||
const { connections, ...options } = this[kOptions] | ||
if (!connections || this[kClients].length < connections) { | ||
client = new Client(this[kUrl], options) | ||
.on('drain', this[kOnDrain]) | ||
.on('connect', this[kOnConnect]) | ||
.on('disconnect', this[kOnDisconnect]) | ||
this[kClients].push(client) | ||
} | ||
} | ||
if (!client) { | ||
this[kQueue].push({ opts, handler }) | ||
@@ -159,2 +171,8 @@ } else { | ||
Pool.prototype.request = require('./client-request') | ||
Pool.prototype.stream = require('./client-stream') | ||
Pool.prototype.pipeline = require('./client-pipeline') | ||
Pool.prototype.upgrade = require('./client-upgrade') | ||
Pool.prototype.connect = require('./client-connect') | ||
module.exports = Pool |
{ | ||
"name": "undici", | ||
"version": "3.1.0", | ||
"version": "3.2.0", | ||
"description": "An HTTP/1.1 client, written from scratch for Node.js", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
108
README.md
@@ -36,2 +36,24 @@ # undici | ||
## Quick Start | ||
```js | ||
import { request } from 'undici' | ||
const { | ||
statusCode, | ||
headers, | ||
trailers, | ||
body | ||
} = await request('http://localhost:3000/foo') | ||
console.log('response received', statusCode) | ||
console.log('headers', headers) | ||
for await (const data of body) { | ||
console.log('data', chunk) | ||
} | ||
console.log('trailers', trailers) | ||
``` | ||
## API | ||
@@ -615,2 +637,88 @@ | ||
### `new undici.Agent(opts)` | ||
* opts `undici.Pool.options` - options passed through to Pool constructor | ||
Returns: `Agent` | ||
Requires: Node.js v14+ | ||
Returns a new Agent instance for use with pool based requests or the following top-level methods `request`, `pipeline`, and `stream`. | ||
#### `agent.get(origin): Pool` | ||
* origin `string` - A pool origin to be retrieved from the Agent. | ||
Requires: Node.js v14+ | ||
This method retrieves Pool instances from the Agent. If the pool does not exist it is automatically added. You do not need to manually close these pools as they are automatically removed using a WeakCache based on WeakRef and FinalizationRegistry. | ||
The following methods `request`, `pipeline`, and `stream` utilize this feature. | ||
### `undici.setGlobalAgent(agent)` | ||
* agent `Agent` | ||
Sets the global agent used by `request`, `pipeline`, and `stream` methods. | ||
The default global agent creates `undici.Pool`s with no max number of | ||
connections. | ||
Requires: Node.js v14+ | ||
The agent must only **implement** the `Agent` API; not necessary extend from it. | ||
### `undici.request(url[, opts]): Promise` | ||
* url `string | URL | object` | ||
* opts `{ agent: Agent } & client.request.opts` | ||
`url` may contain path. `opts` may not contain path. `opts.method` is `GET` by default. | ||
Calls `pool.request(opts)` on the pool returned from either the globalAgent (see [setGlobalAgent](#undicisetglobalagentagent)) or the agent passed to the `opts` argument. | ||
Returns a promise with the result of the `request` method. | ||
### `undici.stream(url, opts, factory): Promise` | ||
* url `string | URL | object` | ||
* opts `{ agent: Agent } & client.stream.opts` | ||
* factory `client.stream.factory` | ||
`url` may contain path. `opts` may not contain path. | ||
See [client.stream](#clientstreamopts-factorydata-callbackerr-promisevoid) for details on the `opts` and `factory` arguments. | ||
Calls `pool.stream(opts, factory)` on the pool returned from either the globalAgent (see [setGlobalAgent](#undicisetglobalagentagent)) or the agent passed to the `opts` argument. | ||
Result is returned in the factory function. See [client.stream](#clientstreamopts-factorydata-callbackerr-promisevoid) for more details. | ||
### `undici.pipeline(url, opts, handler): Duplex` | ||
* url `string | URL | object` | ||
* opts `{ agent: Agent } & client.pipeline.opts` | ||
* handler `client.pipeline.handler` | ||
`url` may contain path. `opts` may not contain path. | ||
See [client.pipeline](#clientpipelineopts-handlerdata-duplex) for details on the `opts` and `handler` arguments. | ||
Calls `pool.pipeline(opts, factory)` on the pool returned from either the globalAgent (see [setGlobalAgent](#undicisetglobalagentagent)) or the agent passed to the `opts` argument. | ||
See [client.pipeline](#clientpipelineopts-handlerdata-duplex) for more details. | ||
### `client.upgrade(opts[, callback(err, data)]): Promise|Void` | ||
* url `string | URL | object` | ||
* opts `{ agent: Agent } & client.upgrade.opts` | ||
`url` may contain path. `opts` may not contain path. | ||
### `client.connect(opts[, callback(err, data)]): Promise|Void` | ||
* url `string | URL | object` | ||
* opts `{ agent: Agent } & client.connect.opts` | ||
`url` may contain path. `opts` may not contain path. | ||
## Specification Compliance | ||
@@ -617,0 +725,0 @@ |
@@ -287,4 +287,2 @@ 'use strict' | ||
t.strictEqual(total, 10) | ||
pool.dispatch({}, noop) | ||
@@ -328,2 +326,4 @@ pool.dispatch({}, noop) | ||
t.strictEqual(total, 2) | ||
t.end() | ||
@@ -370,12 +370,5 @@ }) | ||
test('invalid options throws', (t) => { | ||
t.plan(6) | ||
t.plan(4) | ||
try { | ||
new Pool(null, { connections: 0 }) // eslint-disable-line | ||
} catch (err) { | ||
t.ok(err instanceof errors.InvalidArgumentError) | ||
t.strictEqual(err.message, 'invalid connections') | ||
} | ||
try { | ||
new Pool(null, { connections: -1 }) // eslint-disable-line | ||
@@ -382,0 +375,0 @@ } catch (err) { |
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
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
422029
87
13414
767
58