http2-wrapper
Advanced tools
Comparing version 1.0.0-beta.4.8 to 1.0.0-beta.5.0
{ | ||
"name": "http2-wrapper", | ||
"version": "1.0.0-beta.4.8", | ||
"version": "1.0.0-beta.5.0", | ||
"description": "HTTP2 client, just with the familiar `https` API", | ||
@@ -10,3 +10,3 @@ "main": "source", | ||
"scripts": { | ||
"test": "xo && nyc --reporter=lcovonly --reporter=text ava" | ||
"test": "xo && nyc --reporter=lcovonly --reporter=text --reporter=html ava" | ||
}, | ||
@@ -42,3 +42,3 @@ "files": [ | ||
"get-stream": "^5.1.0", | ||
"got": "^11.0.2", | ||
"got": "^11.4.0", | ||
"lolex": "^6.0.0", | ||
@@ -45,0 +45,0 @@ "many-keys-map": "^1.0.2", |
@@ -5,3 +5,3 @@ # http2-wrapper | ||
[![Node CI](https://github.com/szmarczak/http2-wrapper/workflows/Node%20CI/badge.svg)](https://github.com/szmarczak/http2-wrapper/actions) | ||
[![Coverage Status](https://coveralls.io/repos/github/szmarczak/http2-wrapper/badge.svg?branch=master)](https://coveralls.io/github/szmarczak/http2-wrapper?branch=master) | ||
[![codecov](https://codecov.io/gh/szmarczak/http2-wrapper/branch/master/graph/badge.svg)](https://codecov.io/gh/szmarczak/http2-wrapper) | ||
[![npm](https://img.shields.io/npm/dm/http2-wrapper.svg)](https://www.npmjs.com/package/http2-wrapper) | ||
@@ -225,3 +225,3 @@ [![install size](https://packagephobia.now.sh/badge?p=http2-wrapper)](https://packagephobia.now.sh/result?p=http2-wrapper) | ||
The maximum amount of sessions per origin. | ||
The maximum amount of sessions in total. | ||
@@ -231,6 +231,8 @@ ##### maxFreeSessions | ||
Type: `number`<br> | ||
Default: `1` | ||
Default: `10` | ||
The maximum amount of free sessions per origin. | ||
The maximum amount of free sessions in total. This only applies to sessions with no pending requests. | ||
**Note:** It is possible that the amount will be exceeded when sessions have at least 1 pending request. | ||
##### maxCachedTlsSessions | ||
@@ -328,3 +330,4 @@ | ||
Server: H2O v2.2.5 [`h2o.conf`](h2o.conf)<br> | ||
Node: v13.8.0 | ||
Node: v14.5.0 | ||
Linux: 5.6.18-156.current | ||
@@ -334,10 +337,10 @@ `auto` means `http2wrapper.auto`. | ||
``` | ||
http2-wrapper x 12,417 ops/sec ±3.72% (83 runs sampled) | ||
http2-wrapper - preconfigured session x 14,517 ops/sec ±1.39% (83 runs sampled) | ||
http2-wrapper - auto x 11,373 ops/sec ±3.17% (84 runs sampled) | ||
http2 x 16,172 ops/sec ±1.21% (85 runs sampled) | ||
https - auto - keepalive x 13,251 ops/sec ±3.84% (79 runs sampled) | ||
https - keepalive x 13,158 ops/sec ±2.88% (78 runs sampled) | ||
https x 1,618 ops/sec ±2.07% (82 runs sampled) | ||
http x 5,922 ops/sec ±2.87% (79 runs sampled) | ||
http2-wrapper x 12,181 ops/sec ±3.39% (75 runs sampled) | ||
http2-wrapper - preconfigured session x 13,140 ops/sec ±2.51% (79 runs sampled) | ||
http2-wrapper - auto x 11,412 ops/sec ±2.55% (78 runs sampled) | ||
http2 x 16,050 ops/sec ±1.39% (86 runs sampled) | ||
https - auto - keepalive x 12,288 ops/sec ±2.69% (79 runs sampled) | ||
https - keepalive x 12,155 ops/sec ±3.32% (78 runs sampled) | ||
https x 1,604 ops/sec ±2.03% (77 runs sampled) | ||
http x 6,041 ops/sec ±3.82% (76 runs sampled) | ||
Fastest is http2 | ||
@@ -347,20 +350,20 @@ ``` | ||
`http2-wrapper`: | ||
- 23% less performant than `http2` | ||
- 6% less performant than `https - keepalive` | ||
- 110% more performant than `http` | ||
- 32% **less** performant than `http2` | ||
- as performant as `https - keepalive` | ||
- 100% **more** performant than `http` | ||
`http2-wrapper - preconfigured session`: | ||
- 10% less performant than `http2` | ||
- 10% more performant than `https - keepalive` | ||
- 145% more performant than `http` | ||
- 22% **less** performant than `http2` | ||
- 8% **more** performant than `https - keepalive` | ||
- 118% **more** performant than `http` | ||
`http2-wrapper - auto`: | ||
- 30% less performant than `http2` | ||
- 14% less performant than `https - keepalive` | ||
- 92% more performant than `http` | ||
- 41% **less** performant than `http2` | ||
- 8% **less** performant than `https - keepalive` | ||
- 89% **more** performant than `http` | ||
`https - auto - keepalive`: | ||
- 18% less performant than `http2` | ||
- 31% **less** performant than `http2` | ||
- as performant as `https - keepalive` | ||
- 124% more performant than `http` | ||
- 103% **more** performant than `http` | ||
@@ -367,0 +370,0 @@ ## Related |
@@ -10,2 +10,3 @@ 'use strict'; | ||
const kOriginSet = Symbol('cachedOriginSet'); | ||
const kGracefullyClosing = Symbol('gracefullyClosing'); | ||
@@ -47,48 +48,31 @@ const nameKeys = [ | ||
const removeSession = (where, name, session) => { | ||
if (name in where) { | ||
const index = where[name].indexOf(session); | ||
const getSortedIndex = (array, value, compare) => { | ||
let low = 0; | ||
let high = array.length; | ||
if (index !== -1) { | ||
where[name].splice(index, 1); | ||
while (low < high) { | ||
const mid = (low + high) >>> 1; | ||
if (where[name].length === 0) { | ||
delete where[name]; | ||
} | ||
return true; | ||
/* istanbul ignore next */ | ||
if (compare(array[mid], value)) { | ||
// This never gets called because we use descending sort. Better to have this anyway. | ||
low = mid + 1; | ||
} else { | ||
high = mid; | ||
} | ||
} | ||
return false; | ||
return low; | ||
}; | ||
const addSession = (where, name, session) => { | ||
if (name in where) { | ||
where[name].push(session); | ||
} else { | ||
where[name] = [session]; | ||
} | ||
const compareSessions = (a, b) => { | ||
return a.remoteSettings.maxConcurrentStreams > b.remoteSettings.maxConcurrentStreams; | ||
}; | ||
const getSessions = (where, name, normalizedOrigin) => { | ||
if (!(name in where)) { | ||
return []; | ||
} | ||
return where[name].filter(session => { | ||
return !session.closed && !session.destroyed && session[kOriginSet].includes(normalizedOrigin); | ||
}); | ||
}; | ||
// See https://tools.ietf.org/html/rfc8336 | ||
const closeCoveredSessions = (where, name, session) => { | ||
if (!(name in where)) { | ||
return; | ||
} | ||
const closeCoveredSessions = (where, session) => { | ||
// Clients SHOULD NOT emit new requests on any connection whose Origin | ||
// Set is a proper subset of another connection's Origin Set, and they | ||
// SHOULD close it once all outstanding requests are satisfied. | ||
for (const coveredSession of where[name]) { | ||
for (const coveredSession of where) { | ||
if ( | ||
@@ -102,7 +86,6 @@ // The set is a proper subset when its length is less than the other set. | ||
// Makes sure that the session can handle all requests from the covered session. | ||
// TODO: can the session become uncovered when a stream is closed after checking this condition? | ||
coveredSession[kCurrentStreamsCount] + session[kCurrentStreamsCount] <= session.remoteSettings.maxConcurrentStreams | ||
) { | ||
// This allows pending requests to finish and prevents making new requests. | ||
coveredSession.close(); | ||
gracefullyClose(coveredSession); | ||
} | ||
@@ -113,8 +96,4 @@ } | ||
// This is basically inverted `closeCoveredSessions(...)`. | ||
const closeSessionIfCovered = (where, name, coveredSession) => { | ||
if (!(name in where)) { | ||
return; | ||
} | ||
for (const session of where[name]) { | ||
const closeSessionIfCovered = (where, coveredSession) => { | ||
for (const session of where) { | ||
if ( | ||
@@ -125,3 +104,3 @@ coveredSession[kOriginSet].length < session[kOriginSet].length && | ||
) { | ||
coveredSession.close(); | ||
gracefullyClose(coveredSession); | ||
} | ||
@@ -131,4 +110,33 @@ } | ||
const getSessions = ({agent, isFree}) => { | ||
const result = {}; | ||
// eslint-disable-next-line guard-for-in | ||
for (const normalizedOptions in agent.sessions) { | ||
const sessions = agent.sessions[normalizedOptions]; | ||
const filtered = sessions.filter(session => { | ||
const result = session[Agent.kCurrentStreamsCount] < session.remoteSettings.maxConcurrentStreams; | ||
return isFree ? result : !result; | ||
}); | ||
if (filtered.length !== 0) { | ||
result[normalizedOptions] = filtered; | ||
} | ||
} | ||
return result; | ||
}; | ||
const gracefullyClose = session => { | ||
session[kGracefullyClosing] = true; | ||
if (session[kCurrentStreamsCount] === 0) { | ||
session.close(); | ||
} | ||
}; | ||
class Agent extends EventEmitter { | ||
constructor({timeout = 60000, maxSessions = Infinity, maxFreeSessions = 1, maxCachedTlsSessions = 100} = {}) { | ||
constructor({timeout = 60000, maxSessions = Infinity, maxFreeSessions = 10, maxCachedTlsSessions = 100} = {}) { | ||
super(); | ||
@@ -138,8 +146,9 @@ | ||
// is equal to or greater than the `maxConcurrentStreams` value. | ||
this.busySessions = {}; | ||
// A session is considered free when its current streams count | ||
// is less than the `maxConcurrentStreams` value. | ||
this.freeSessions = {}; | ||
// SESSIONS[NORMALIZED_OPTIONS] = []; | ||
this.sessions = {}; | ||
// The queue for creating new sessions. It looks like this: | ||
@@ -157,10 +166,12 @@ // QUEUE[NORMALIZED_OPTIONS][NORMALIZED_ORIGIN] = ENTRY_FUNCTION | ||
// Max sessions per origin. | ||
// Max sessions in total | ||
this.maxSessions = maxSessions; | ||
// Max free sessions per origin. | ||
// Max free sessions in total | ||
// TODO: decreasing `maxFreeSessions` should close some sessions | ||
// TODO: should `maxFreeSessions` be related only to sessions with 0 pending streams? | ||
this.maxFreeSessions = maxFreeSessions; | ||
this._freeSessionsCount = 0; | ||
this._sessionsCount = 0; | ||
// We don't support push streams by default. | ||
@@ -206,8 +217,9 @@ this.settings = { | ||
// We need the busy sessions length to check if a session can be created. | ||
const busyLength = getSessions(this.busySessions, normalizedOptions, normalizedOrigin).length; | ||
const item = this.queue[normalizedOptions][normalizedOrigin]; | ||
// The entry function can be run only once. | ||
if (busyLength < this.maxSessions && !item.completed) { | ||
// BUG: The session may be never created when: | ||
// - the first condition is false AND | ||
// - this function is never called with the same arguments in the future. | ||
if (this._sessionsCount < this.maxSessions && !item.completed) { | ||
item.completed = true; | ||
@@ -219,7 +231,2 @@ | ||
_closeCoveredSessions(normalizedOptions, session) { | ||
closeCoveredSessions(this.freeSessions, normalizedOptions, session); | ||
closeCoveredSessions(this.busySessions, normalizedOptions, session); | ||
} | ||
getSession(origin, options, listeners) { | ||
@@ -248,24 +255,61 @@ return new Promise((resolve, reject) => { | ||
if (normalizedOptions in this.freeSessions) { | ||
// Look for all available free sessions. | ||
const freeSessions = getSessions(this.freeSessions, normalizedOptions, normalizedOrigin); | ||
if (normalizedOptions in this.sessions) { | ||
const sessions = this.sessions[normalizedOptions]; | ||
if (freeSessions.length !== 0) { | ||
// Use session which has the biggest stream capacity in order to use the smallest number of sessions possible. | ||
const session = freeSessions.reduce((previousSession, nextSession) => { | ||
let maxConcurrentStreams = -1; | ||
let currentStreamsCount = -1; | ||
let optimalSession; | ||
// We could just do this.sessions[normalizedOptions].find(...) but that isn't optimal. | ||
// Additionally, we are looking for session which has biggest current pending streams count. | ||
for (const session of sessions) { | ||
const sessionMaxConcurrentStreams = session.remoteSettings.maxConcurrentStreams; | ||
if (sessionMaxConcurrentStreams < maxConcurrentStreams) { | ||
break; | ||
} | ||
if (session[kOriginSet].includes(normalizedOrigin)) { | ||
const sessionCurrentStreamsCount = session[kCurrentStreamsCount]; | ||
if ( | ||
nextSession.remoteSettings.maxConcurrentStreams >= previousSession.remoteSettings.maxConcurrentStreams && | ||
nextSession[kCurrentStreamsCount] > previousSession[kCurrentStreamsCount] | ||
sessionCurrentStreamsCount >= sessionMaxConcurrentStreams || | ||
session[kGracefullyClosing] || | ||
// Unfortunately the `close` event isn't called immediately, | ||
// so `session.destroyed` is `true`, but `session.closed` is `false`. | ||
session.destroyed | ||
) { | ||
return nextSession; | ||
continue; | ||
} | ||
return previousSession; | ||
}); | ||
// We only need set this once. | ||
if (!optimalSession) { | ||
maxConcurrentStreams = sessionMaxConcurrentStreams; | ||
} | ||
for (const {resolve} of listeners) { | ||
// TODO: The session can get busy here | ||
resolve(session); | ||
// We're looking for the session which has biggest current pending stream count, | ||
// in order to minimalize the amount of active sessions. | ||
if (sessionCurrentStreamsCount > currentStreamsCount) { | ||
optimalSession = session; | ||
currentStreamsCount = sessionCurrentStreamsCount; | ||
} | ||
} | ||
} | ||
if (optimalSession) { | ||
/* istanbul ignore next: safety check */ | ||
if (listeners.length !== 1) { | ||
for (const {reject} of listeners) { | ||
const error = new Error( | ||
`Expected the length of listeners to be 1, got ${listeners.length}.\n` + | ||
'Please report this to https://github.com/szmarczak/http2-wrapper/' | ||
); | ||
reject(error); | ||
} | ||
return; | ||
} | ||
listeners[0].resolve(optimalSession); | ||
return; | ||
@@ -280,2 +324,5 @@ } | ||
// This shouldn't be executed here. | ||
// See the comment inside _tryToCreateNewSession. | ||
this._tryToCreateNewSession(normalizedOptions, normalizedOrigin); | ||
return; | ||
@@ -305,60 +352,24 @@ } | ||
let receivedSettings = false; | ||
let servername; | ||
try { | ||
const tlsSessionCache = this.tlsSessionCache.get(name); | ||
const session = http2.connect(origin, { | ||
createConnection: this.createConnection, | ||
settings: this.settings, | ||
session: tlsSessionCache ? tlsSessionCache.session : undefined, | ||
session: this.tlsSessionCache.get(name), | ||
...options | ||
}); | ||
session[kCurrentStreamsCount] = 0; | ||
session[kGracefullyClosing] = false; | ||
// Tries to free the session. | ||
const freeSession = () => { | ||
// Fetch the smallest amount of free sessions of any origin we have. | ||
const freeSessionsCount = session[kOriginSet].reduce((accumulator, origin) => { | ||
return Math.min(accumulator, getSessions(this.freeSessions, normalizedOptions, origin).length); | ||
}, Infinity); | ||
// Check the limit. | ||
if (freeSessionsCount < this.maxFreeSessions) { | ||
addSession(this.freeSessions, normalizedOptions, session); | ||
return true; | ||
} | ||
return false; | ||
}; | ||
const isFree = () => session[kCurrentStreamsCount] < session.remoteSettings.maxConcurrentStreams; | ||
let wasFree = true; | ||
session.socket.once('session', tlsSession => { | ||
// We need to cache the servername due to a bug in OpenSSL. | ||
setImmediate(() => { | ||
this.tlsSessionCache.set(name, { | ||
session: tlsSession, | ||
servername | ||
}); | ||
}); | ||
this.tlsSessionCache.set(name, tlsSession); | ||
}); | ||
// OpenSSL bug workaround. | ||
// See https://github.com/nodejs/node/issues/28985 | ||
session.socket.once('secureConnect', () => { | ||
servername = session.socket.servername; | ||
if (servername === false && typeof tlsSessionCache !== 'undefined' && typeof tlsSessionCache.servername !== 'undefined') { | ||
session.socket.servername = tlsSessionCache.servername; | ||
} | ||
}); | ||
session.once('error', error => { | ||
// `receivedSettings` is true when the session has successfully connected. | ||
if (!receivedSettings) { | ||
for (const {reject} of listeners) { | ||
reject(error); | ||
} | ||
// Listeners are empty when the session successfully connected. | ||
for (const {reject} of listeners) { | ||
reject(error); | ||
} | ||
@@ -372,2 +383,3 @@ | ||
// Terminates all streams owned by this session. | ||
// TODO: Maybe the streams should have a "Session timed out" error? | ||
session.destroy(); | ||
@@ -377,5 +389,25 @@ }); | ||
session.once('close', () => { | ||
if (!receivedSettings) { | ||
if (receivedSettings) { | ||
// 1. If it wasn't free then no need to decrease because | ||
// it has been decreased already in session.request(). | ||
// 2. `stream.once('close')` won't increment the count | ||
// because the session is already closed. | ||
if (wasFree) { | ||
this._freeSessionsCount--; | ||
} | ||
this._sessionsCount--; | ||
// This cannot be moved to the stream logic, | ||
// because there may be a session that hadn't made a single request. | ||
const where = this.sessions[normalizedOptions]; | ||
where.splice(where.indexOf(session), 1); | ||
if (where.length === 0) { | ||
delete this.sessions[normalizedOptions]; | ||
} | ||
} else { | ||
// Broken connection | ||
const error = new Error('Session closed without receiving a SETTINGS frame'); | ||
error.code = 'HTTP2WRAPPER_NOSETTINGS'; | ||
@@ -385,10 +417,6 @@ for (const {reject} of listeners) { | ||
} | ||
removeFromQueue(); | ||
} | ||
removeFromQueue(); | ||
// This cannot be moved to the stream logic, | ||
// because there may be a session that hadn't made a single request. | ||
removeSession(this.freeSessions, normalizedOptions, session); | ||
// There may be another session awaiting. | ||
@@ -400,3 +428,3 @@ this._tryToCreateNewSession(normalizedOptions, normalizedOrigin); | ||
const processListeners = () => { | ||
if (!(normalizedOptions in this.queue)) { | ||
if (!(normalizedOptions in this.queue) || !isFree()) { | ||
return; | ||
@@ -416,6 +444,7 @@ } | ||
if (this.queue[normalizedOptions][origin].listeners.length === 0) { | ||
delete this.queue[normalizedOptions][origin]; | ||
const where = this.queue[normalizedOptions]; | ||
if (where[origin].listeners.length === 0) { | ||
delete where[origin]; | ||
if (Object.keys(this.queue[normalizedOptions]).length === 0) { | ||
if (Object.keys(where).length === 0) { | ||
delete this.queue[normalizedOptions]; | ||
@@ -435,3 +464,3 @@ break; | ||
// The Origin Set cannot shrink. No need to check if it suddenly became covered by another one. | ||
session.once('origin', () => { | ||
session.on('origin', () => { | ||
session[kOriginSet] = session.originSet; | ||
@@ -444,14 +473,15 @@ | ||
// Close covered sessions (if possible). | ||
this._closeCoveredSessions(normalizedOptions, session); | ||
processListeners(); | ||
// `session.remoteSettings.maxConcurrentStreams` might get increased | ||
session.on('remoteSettings', () => { | ||
this._closeCoveredSessions(normalizedOptions, session); | ||
}); | ||
// Close covered sessions (if possible). | ||
closeCoveredSessions(this.sessions[normalizedOptions], session); | ||
}); | ||
session.once('remoteSettings', () => { | ||
// Fix Node.js bug preventing the process from exiting | ||
session.ref(); | ||
session.unref(); | ||
this._sessionsCount++; | ||
// The Agent could have been destroyed already. | ||
@@ -470,21 +500,27 @@ if (entry.destroyed) { | ||
session[kOriginSet] = session.originSet; | ||
{ | ||
const where = this.sessions; | ||
if (normalizedOptions in where) { | ||
const sessions = where[normalizedOptions]; | ||
sessions.splice(getSortedIndex(sessions, session, compareSessions), 0, session); | ||
} else { | ||
where[normalizedOptions] = [session]; | ||
} | ||
} | ||
this._freeSessionsCount += 1; | ||
receivedSettings = true; | ||
this.emit('session', session); | ||
if (freeSession()) { | ||
// Process listeners, we're free. | ||
processListeners(); | ||
} else if (this.maxFreeSessions === 0) { | ||
processListeners(); | ||
processListeners(); | ||
removeFromQueue(); | ||
// We're closing ASAP, when all possible requests have been made for this event loop tick. | ||
setImmediate(() => { | ||
session.close(); | ||
}); | ||
} else { | ||
// Too late, another free session took these listeners. | ||
// TODO: Close last recently used (or least used?) session | ||
if (session[kCurrentStreamsCount] === 0 && this._freeSessionsCount > this.maxFreeSessions) { | ||
session.close(); | ||
} | ||
removeFromQueue(); | ||
// Check if we haven't managed to execute all listeners. | ||
@@ -497,16 +533,8 @@ if (listeners.length !== 0) { | ||
receivedSettings = true; | ||
// `session.remoteSettings.maxConcurrentStreams` might get increased | ||
session.on('remoteSettings', () => { | ||
// Check if we're eligible to become a free session | ||
if (isFree() && removeSession(this.busySessions, normalizedOptions, session)) { | ||
// Check for free seats | ||
if (freeSession()) { | ||
processListeners(); | ||
} else { | ||
// Assume it's still a busy session | ||
addSession(this.busySessions, normalizedOptions, session); | ||
} | ||
} | ||
processListeners(); | ||
// In case the Origin Set changes | ||
closeCoveredSessions(this.sessions[normalizedOptions], session); | ||
}); | ||
@@ -518,5 +546,9 @@ }); | ||
session.request = (headers, streamOptions) => { | ||
if (session[kGracefullyClosing]) { | ||
throw new Error('The session is gracefully closing. No new streams are allowed.'); | ||
} | ||
const stream = session[kRequest](headers, streamOptions); | ||
// The process won't exit until the session is closed. | ||
// The process won't exit until the session is closed or all requests are gone. | ||
session.ref(); | ||
@@ -526,31 +558,41 @@ | ||
// Check if we became busy | ||
if (!isFree() && removeSession(this.freeSessions, normalizedOptions, session)) { | ||
addSession(this.busySessions, normalizedOptions, session); | ||
if (session[kCurrentStreamsCount] === session.remoteSettings.maxConcurrentStreams) { | ||
this._freeSessionsCount--; | ||
} | ||
stream.once('close', () => { | ||
wasFree = isFree(); | ||
--session[kCurrentStreamsCount]; | ||
if (isFree()) { | ||
if (session[kCurrentStreamsCount] === 0) { | ||
// All requests are finished, the process may exit now. | ||
session.unref(); | ||
} | ||
if (!session.destroyed && !session.closed) { | ||
closeSessionIfCovered(this.sessions[normalizedOptions], session); | ||
// Check if we are no longer busy and the session is not broken. | ||
if (removeSession(this.busySessions, normalizedOptions, session) && !session.destroyed && !session.closed) { | ||
// Check the sessions count of this authority and compare it to `maxSessionsCount`. | ||
if (freeSession()) { | ||
this._closeCoveredSessions(normalizedOptions, session); | ||
if (isFree() && !session.closed) { | ||
if (!wasFree) { | ||
this._freeSessionsCount++; | ||
wasFree = true; | ||
} | ||
const isEmpty = session[kCurrentStreamsCount] === 0; | ||
if (isEmpty) { | ||
session.unref(); | ||
} | ||
if ( | ||
isEmpty && | ||
( | ||
this._freeSessionsCount > this.maxFreeSessions || | ||
session[kGracefullyClosing] | ||
) | ||
) { | ||
session.close(); | ||
} else { | ||
closeCoveredSessions(this.sessions[normalizedOptions], session); | ||
processListeners(); | ||
} else { | ||
session.close(); | ||
} | ||
} | ||
} | ||
if (!session.destroyed && !session.closed) { | ||
closeSessionIfCovered(this.freeSessions, normalizedOptions, session); | ||
} | ||
}); | ||
@@ -583,3 +625,7 @@ | ||
resolve: session => { | ||
resolve(session.request(headers, streamOptions)); | ||
try { | ||
resolve(session.request(headers, streamOptions)); | ||
} catch (error) { | ||
reject(error); | ||
} | ||
} | ||
@@ -608,4 +654,4 @@ }]); | ||
closeFreeSessions() { | ||
for (const freeSessions of Object.values(this.freeSessions)) { | ||
for (const session of freeSessions) { | ||
for (const sessions of Object.values(this.sessions)) { | ||
for (const session of sessions) { | ||
if (session[kCurrentStreamsCount] === 0) { | ||
@@ -619,4 +665,4 @@ session.close(); | ||
destroy(reason) { | ||
for (const busySessions of Object.values(this.busySessions)) { | ||
for (const session of busySessions) { | ||
for (const sessions of Object.values(this.sessions)) { | ||
for (const session of sessions) { | ||
session.destroy(reason); | ||
@@ -626,8 +672,2 @@ } | ||
for (const freeSessions of Object.values(this.freeSessions)) { | ||
for (const session of freeSessions) { | ||
session.destroy(reason); | ||
} | ||
} | ||
for (const entriesOfAuthority of Object.values(this.queue)) { | ||
@@ -642,4 +682,15 @@ for (const entry of Object.values(entriesOfAuthority)) { | ||
} | ||
get freeSessions() { | ||
return getSessions({agent: this, isFree: true}); | ||
} | ||
get busySessions() { | ||
return getSessions({agent: this, isFree: false}); | ||
} | ||
} | ||
Agent.kCurrentStreamsCount = kCurrentStreamsCount; | ||
Agent.kGracefullyClosing = kGracefullyClosing; | ||
module.exports = { | ||
@@ -646,0 +697,0 @@ Agent, |
@@ -171,2 +171,3 @@ 'use strict'; | ||
callback(new Error('The GET, HEAD and DELETE methods must NOT have a body')); | ||
/* istanbul ignore next: Node.js 12 throws directly */ | ||
return; | ||
@@ -173,0 +174,0 @@ } |
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
50040
1156
371