summaryrefslogtreecommitdiff
path: root/node_modules/http2-wrapper/source/agent.js
diff options
context:
space:
mode:
authorLinuxWizard42 <computerwizard@linuxmail.org>2022-10-12 23:08:57 +0300
committerLinuxWizard42 <computerwizard@linuxmail.org>2022-10-12 23:08:57 +0300
commit726b81b19251674e149ccfbb1abacbd837fc6db0 (patch)
treefbdbb227dc01357eb76e8222d76185bc124c5ca6 /node_modules/http2-wrapper/source/agent.js
parent34f0890e175698940d49238097579f44e4d78c89 (diff)
downloadFlashRunner-726b81b19251674e149ccfbb1abacbd837fc6db0.tar.gz
FlashRunner-726b81b19251674e149ccfbb1abacbd837fc6db0.tar.zst
Removed files that should not have been included in git
Diffstat (limited to 'node_modules/http2-wrapper/source/agent.js')
-rw-r--r--node_modules/http2-wrapper/source/agent.js670
1 files changed, 0 insertions, 670 deletions
diff --git a/node_modules/http2-wrapper/source/agent.js b/node_modules/http2-wrapper/source/agent.js
deleted file mode 100644
index b2b1cff..0000000
--- a/node_modules/http2-wrapper/source/agent.js
+++ /dev/null
@@ -1,670 +0,0 @@
-'use strict';
-const EventEmitter = require('events');
-const tls = require('tls');
-const http2 = require('http2');
-const QuickLRU = require('quick-lru');
-
-const kCurrentStreamsCount = Symbol('currentStreamsCount');
-const kRequest = Symbol('request');
-const kOriginSet = Symbol('cachedOriginSet');
-const kGracefullyClosing = Symbol('gracefullyClosing');
-
-const nameKeys = [
- // `http2.connect()` options
- 'maxDeflateDynamicTableSize',
- 'maxSessionMemory',
- 'maxHeaderListPairs',
- 'maxOutstandingPings',
- 'maxReservedRemoteStreams',
- 'maxSendHeaderBlockLength',
- 'paddingStrategy',
-
- // `tls.connect()` options
- 'localAddress',
- 'path',
- 'rejectUnauthorized',
- 'minDHSize',
-
- // `tls.createSecureContext()` options
- 'ca',
- 'cert',
- 'clientCertEngine',
- 'ciphers',
- 'key',
- 'pfx',
- 'servername',
- 'minVersion',
- 'maxVersion',
- 'secureProtocol',
- 'crl',
- 'honorCipherOrder',
- 'ecdhCurve',
- 'dhparam',
- 'secureOptions',
- 'sessionIdContext'
-];
-
-const getSortedIndex = (array, value, compare) => {
- let low = 0;
- let high = array.length;
-
- while (low < high) {
- const mid = (low + high) >>> 1;
-
- /* 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 low;
-};
-
-const compareSessions = (a, b) => {
- return a.remoteSettings.maxConcurrentStreams > b.remoteSettings.maxConcurrentStreams;
-};
-
-// See https://tools.ietf.org/html/rfc8336
-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) {
- if (
- // The set is a proper subset when its length is less than the other set.
- coveredSession[kOriginSet].length < session[kOriginSet].length &&
-
- // And the other set includes all elements of the subset.
- coveredSession[kOriginSet].every(origin => session[kOriginSet].includes(origin)) &&
-
- // Makes sure that the session can handle all requests from the covered session.
- coveredSession[kCurrentStreamsCount] + session[kCurrentStreamsCount] <= session.remoteSettings.maxConcurrentStreams
- ) {
- // This allows pending requests to finish and prevents making new requests.
- gracefullyClose(coveredSession);
- }
- }
-};
-
-// This is basically inverted `closeCoveredSessions(...)`.
-const closeSessionIfCovered = (where, coveredSession) => {
- for (const session of where) {
- if (
- coveredSession[kOriginSet].length < session[kOriginSet].length &&
- coveredSession[kOriginSet].every(origin => session[kOriginSet].includes(origin)) &&
- coveredSession[kCurrentStreamsCount] + session[kCurrentStreamsCount] <= session.remoteSettings.maxConcurrentStreams
- ) {
- gracefullyClose(coveredSession);
- }
- }
-};
-
-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 = 10, maxCachedTlsSessions = 100} = {}) {
- super();
-
- // A session is considered busy when its current streams count
- // is equal to or greater than the `maxConcurrentStreams` value.
-
- // A session is considered free when its current streams count
- // is less than the `maxConcurrentStreams` value.
-
- // SESSIONS[NORMALIZED_OPTIONS] = [];
- this.sessions = {};
-
- // The queue for creating new sessions. It looks like this:
- // QUEUE[NORMALIZED_OPTIONS][NORMALIZED_ORIGIN] = ENTRY_FUNCTION
- //
- // The entry function has `listeners`, `completed` and `destroyed` properties.
- // `listeners` is an array of objects containing `resolve` and `reject` functions.
- // `completed` is a boolean. It's set to true after ENTRY_FUNCTION is executed.
- // `destroyed` is a boolean. If it's set to true, the session will be destroyed if hasn't connected yet.
- this.queue = {};
-
- // Each session will use this timeout value.
- this.timeout = timeout;
-
- // Max sessions in total
- this.maxSessions = maxSessions;
-
- // Max free sessions in total
- // TODO: decreasing `maxFreeSessions` should close some sessions
- this.maxFreeSessions = maxFreeSessions;
-
- this._freeSessionsCount = 0;
- this._sessionsCount = 0;
-
- // We don't support push streams by default.
- this.settings = {
- enablePush: false
- };
-
- // Reusing TLS sessions increases performance.
- this.tlsSessionCache = new QuickLRU({maxSize: maxCachedTlsSessions});
- }
-
- static normalizeOrigin(url, servername) {
- if (typeof url === 'string') {
- url = new URL(url);
- }
-
- if (servername && url.hostname !== servername) {
- url.hostname = servername;
- }
-
- return url.origin;
- }
-
- normalizeOptions(options) {
- let normalized = '';
-
- if (options) {
- for (const key of nameKeys) {
- if (options[key]) {
- normalized += `:${options[key]}`;
- }
- }
- }
-
- return normalized;
- }
-
- _tryToCreateNewSession(normalizedOptions, normalizedOrigin) {
- if (!(normalizedOptions in this.queue) || !(normalizedOrigin in this.queue[normalizedOptions])) {
- return;
- }
-
- const item = this.queue[normalizedOptions][normalizedOrigin];
-
- // The entry function can be run only once.
- // 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;
-
- item();
- }
- }
-
- getSession(origin, options, listeners) {
- return new Promise((resolve, reject) => {
- if (Array.isArray(listeners)) {
- listeners = [...listeners];
-
- // Resolve the current promise ASAP, we're just moving the listeners.
- // They will be executed at a different time.
- resolve();
- } else {
- listeners = [{resolve, reject}];
- }
-
- const normalizedOptions = this.normalizeOptions(options);
- const normalizedOrigin = Agent.normalizeOrigin(origin, options && options.servername);
-
- if (normalizedOrigin === undefined) {
- for (const {reject} of listeners) {
- reject(new TypeError('The `origin` argument needs to be a string or an URL object'));
- }
-
- return;
- }
-
- if (normalizedOptions in this.sessions) {
- const sessions = this.sessions[normalizedOptions];
-
- 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 (
- sessionCurrentStreamsCount >= sessionMaxConcurrentStreams ||
- session[kGracefullyClosing] ||
- // Unfortunately the `close` event isn't called immediately,
- // so `session.destroyed` is `true`, but `session.closed` is `false`.
- session.destroyed
- ) {
- continue;
- }
-
- // We only need set this once.
- if (!optimalSession) {
- maxConcurrentStreams = sessionMaxConcurrentStreams;
- }
-
- // 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;
- }
- }
-
- if (normalizedOptions in this.queue) {
- if (normalizedOrigin in this.queue[normalizedOptions]) {
- // There's already an item in the queue, just attach ourselves to it.
- this.queue[normalizedOptions][normalizedOrigin].listeners.push(...listeners);
-
- // This shouldn't be executed here.
- // See the comment inside _tryToCreateNewSession.
- this._tryToCreateNewSession(normalizedOptions, normalizedOrigin);
- return;
- }
- } else {
- this.queue[normalizedOptions] = {};
- }
-
- // The entry must be removed from the queue IMMEDIATELY when:
- // 1. the session connects successfully,
- // 2. an error occurs.
- const removeFromQueue = () => {
- // Our entry can be replaced. We cannot remove the new one.
- if (normalizedOptions in this.queue && this.queue[normalizedOptions][normalizedOrigin] === entry) {
- delete this.queue[normalizedOptions][normalizedOrigin];
-
- if (Object.keys(this.queue[normalizedOptions]).length === 0) {
- delete this.queue[normalizedOptions];
- }
- }
- };
-
- // The main logic is here
- const entry = () => {
- const name = `${normalizedOrigin}:${normalizedOptions}`;
- let receivedSettings = false;
-
- try {
- const session = http2.connect(origin, {
- createConnection: this.createConnection,
- settings: this.settings,
- session: this.tlsSessionCache.get(name),
- ...options
- });
- session[kCurrentStreamsCount] = 0;
- session[kGracefullyClosing] = false;
-
- const isFree = () => session[kCurrentStreamsCount] < session.remoteSettings.maxConcurrentStreams;
- let wasFree = true;
-
- session.socket.once('session', tlsSession => {
- this.tlsSessionCache.set(name, tlsSession);
- });
-
- session.once('error', error => {
- // Listeners are empty when the session successfully connected.
- for (const {reject} of listeners) {
- reject(error);
- }
-
- // The connection got broken, purge the cache.
- this.tlsSessionCache.delete(name);
- });
-
- session.setTimeout(this.timeout, () => {
- // Terminates all streams owned by this session.
- // TODO: Maybe the streams should have a "Session timed out" error?
- session.destroy();
- });
-
- session.once('close', () => {
- 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';
-
- for (const {reject} of listeners) {
- reject(error);
- }
-
- removeFromQueue();
- }
-
- // There may be another session awaiting.
- this._tryToCreateNewSession(normalizedOptions, normalizedOrigin);
- });
-
- // Iterates over the queue and processes listeners.
- const processListeners = () => {
- if (!(normalizedOptions in this.queue) || !isFree()) {
- return;
- }
-
- for (const origin of session[kOriginSet]) {
- if (origin in this.queue[normalizedOptions]) {
- const {listeners} = this.queue[normalizedOptions][origin];
-
- // Prevents session overloading.
- while (listeners.length !== 0 && isFree()) {
- // We assume `resolve(...)` calls `request(...)` *directly*,
- // otherwise the session will get overloaded.
- listeners.shift().resolve(session);
- }
-
- const where = this.queue[normalizedOptions];
- if (where[origin].listeners.length === 0) {
- delete where[origin];
-
- if (Object.keys(where).length === 0) {
- delete this.queue[normalizedOptions];
- break;
- }
- }
-
- // We're no longer free, no point in continuing.
- if (!isFree()) {
- break;
- }
- }
- }
- };
-
- // The Origin Set cannot shrink. No need to check if it suddenly became covered by another one.
- session.on('origin', () => {
- session[kOriginSet] = session.originSet;
-
- if (!isFree()) {
- // The session is full.
- return;
- }
-
- processListeners();
-
- // 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.
- if (entry.destroyed) {
- const error = new Error('Agent has been destroyed');
-
- for (const listener of listeners) {
- listener.reject(error);
- }
-
- session.destroy();
- return;
- }
-
- 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);
-
- processListeners();
- removeFromQueue();
-
- // TODO: Close last recently used (or least used?) session
- if (session[kCurrentStreamsCount] === 0 && this._freeSessionsCount > this.maxFreeSessions) {
- session.close();
- }
-
- // Check if we haven't managed to execute all listeners.
- if (listeners.length !== 0) {
- // Request for a new session with predefined listeners.
- this.getSession(normalizedOrigin, options, listeners);
- listeners.length = 0;
- }
-
- // `session.remoteSettings.maxConcurrentStreams` might get increased
- session.on('remoteSettings', () => {
- processListeners();
-
- // In case the Origin Set changes
- closeCoveredSessions(this.sessions[normalizedOptions], session);
- });
- });
-
- // Shim `session.request()` in order to catch all streams
- session[kRequest] = session.request;
- 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 or all requests are gone.
- session.ref();
-
- ++session[kCurrentStreamsCount];
-
- if (session[kCurrentStreamsCount] === session.remoteSettings.maxConcurrentStreams) {
- this._freeSessionsCount--;
- }
-
- stream.once('close', () => {
- wasFree = isFree();
-
- --session[kCurrentStreamsCount];
-
- if (!session.destroyed && !session.closed) {
- closeSessionIfCovered(this.sessions[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();
- }
- }
- }
- });
-
- return stream;
- };
- } catch (error) {
- for (const listener of listeners) {
- listener.reject(error);
- }
-
- removeFromQueue();
- }
- };
-
- entry.listeners = listeners;
- entry.completed = false;
- entry.destroyed = false;
-
- this.queue[normalizedOptions][normalizedOrigin] = entry;
- this._tryToCreateNewSession(normalizedOptions, normalizedOrigin);
- });
- }
-
- request(origin, options, headers, streamOptions) {
- return new Promise((resolve, reject) => {
- this.getSession(origin, options, [{
- reject,
- resolve: session => {
- try {
- resolve(session.request(headers, streamOptions));
- } catch (error) {
- reject(error);
- }
- }
- }]);
- });
- }
-
- createConnection(origin, options) {
- return Agent.connect(origin, options);
- }
-
- static connect(origin, options) {
- options.ALPNProtocols = ['h2'];
-
- const port = origin.port || 443;
- const host = origin.hostname || origin.host;
-
- if (typeof options.servername === 'undefined') {
- options.servername = host;
- }
-
- return tls.connect(port, host, options);
- }
-
- closeFreeSessions() {
- for (const sessions of Object.values(this.sessions)) {
- for (const session of sessions) {
- if (session[kCurrentStreamsCount] === 0) {
- session.close();
- }
- }
- }
- }
-
- destroy(reason) {
- for (const sessions of Object.values(this.sessions)) {
- for (const session of sessions) {
- session.destroy(reason);
- }
- }
-
- for (const entriesOfAuthority of Object.values(this.queue)) {
- for (const entry of Object.values(entriesOfAuthority)) {
- entry.destroyed = true;
- }
- }
-
- // New requests should NOT attach to destroyed sessions
- this.queue = {};
- }
-
- get freeSessions() {
- return getSessions({agent: this, isFree: true});
- }
-
- get busySessions() {
- return getSessions({agent: this, isFree: false});
- }
-}
-
-Agent.kCurrentStreamsCount = kCurrentStreamsCount;
-Agent.kGracefullyClosing = kGracefullyClosing;
-
-module.exports = {
- Agent,
- globalAgent: new Agent()
-};