init
This commit is contained in:
670
mc_test/node_modules/http2-wrapper/source/agent.js
generated
vendored
Executable file
670
mc_test/node_modules/http2-wrapper/source/agent.js
generated
vendored
Executable file
@ -0,0 +1,670 @@
|
||||
'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()
|
||||
};
|
||||
149
mc_test/node_modules/http2-wrapper/source/auto.js
generated
vendored
Executable file
149
mc_test/node_modules/http2-wrapper/source/auto.js
generated
vendored
Executable file
@ -0,0 +1,149 @@
|
||||
'use strict';
|
||||
const http = require('http');
|
||||
const https = require('https');
|
||||
const resolveALPN = require('resolve-alpn');
|
||||
const QuickLRU = require('quick-lru');
|
||||
const Http2ClientRequest = require('./client-request');
|
||||
const calculateServerName = require('./utils/calculate-server-name');
|
||||
const urlToOptions = require('./utils/url-to-options');
|
||||
|
||||
const cache = new QuickLRU({maxSize: 100});
|
||||
const queue = new Map();
|
||||
|
||||
const installSocket = (agent, socket, options) => {
|
||||
socket._httpMessage = {shouldKeepAlive: true};
|
||||
|
||||
const onFree = () => {
|
||||
agent.emit('free', socket, options);
|
||||
};
|
||||
|
||||
socket.on('free', onFree);
|
||||
|
||||
const onClose = () => {
|
||||
agent.removeSocket(socket, options);
|
||||
};
|
||||
|
||||
socket.on('close', onClose);
|
||||
|
||||
const onRemove = () => {
|
||||
agent.removeSocket(socket, options);
|
||||
socket.off('close', onClose);
|
||||
socket.off('free', onFree);
|
||||
socket.off('agentRemove', onRemove);
|
||||
};
|
||||
|
||||
socket.on('agentRemove', onRemove);
|
||||
|
||||
agent.emit('free', socket, options);
|
||||
};
|
||||
|
||||
const resolveProtocol = async options => {
|
||||
const name = `${options.host}:${options.port}:${options.ALPNProtocols.sort()}`;
|
||||
|
||||
if (!cache.has(name)) {
|
||||
if (queue.has(name)) {
|
||||
const result = await queue.get(name);
|
||||
return result.alpnProtocol;
|
||||
}
|
||||
|
||||
const {path, agent} = options;
|
||||
options.path = options.socketPath;
|
||||
|
||||
const resultPromise = resolveALPN(options);
|
||||
queue.set(name, resultPromise);
|
||||
|
||||
try {
|
||||
const {socket, alpnProtocol} = await resultPromise;
|
||||
cache.set(name, alpnProtocol);
|
||||
|
||||
options.path = path;
|
||||
|
||||
if (alpnProtocol === 'h2') {
|
||||
// https://github.com/nodejs/node/issues/33343
|
||||
socket.destroy();
|
||||
} else {
|
||||
const {globalAgent} = https;
|
||||
const defaultCreateConnection = https.Agent.prototype.createConnection;
|
||||
|
||||
if (agent) {
|
||||
if (agent.createConnection === defaultCreateConnection) {
|
||||
installSocket(agent, socket, options);
|
||||
} else {
|
||||
socket.destroy();
|
||||
}
|
||||
} else if (globalAgent.createConnection === defaultCreateConnection) {
|
||||
installSocket(globalAgent, socket, options);
|
||||
} else {
|
||||
socket.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
queue.delete(name);
|
||||
|
||||
return alpnProtocol;
|
||||
} catch (error) {
|
||||
queue.delete(name);
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return cache.get(name);
|
||||
};
|
||||
|
||||
module.exports = async (input, options, callback) => {
|
||||
if (typeof input === 'string' || input instanceof URL) {
|
||||
input = urlToOptions(new URL(input));
|
||||
}
|
||||
|
||||
if (typeof options === 'function') {
|
||||
callback = options;
|
||||
options = undefined;
|
||||
}
|
||||
|
||||
options = {
|
||||
ALPNProtocols: ['h2', 'http/1.1'],
|
||||
...input,
|
||||
...options,
|
||||
resolveSocket: true
|
||||
};
|
||||
|
||||
if (!Array.isArray(options.ALPNProtocols) || options.ALPNProtocols.length === 0) {
|
||||
throw new Error('The `ALPNProtocols` option must be an Array with at least one entry');
|
||||
}
|
||||
|
||||
options.protocol = options.protocol || 'https:';
|
||||
const isHttps = options.protocol === 'https:';
|
||||
|
||||
options.host = options.hostname || options.host || 'localhost';
|
||||
options.session = options.tlsSession;
|
||||
options.servername = options.servername || calculateServerName(options);
|
||||
options.port = options.port || (isHttps ? 443 : 80);
|
||||
options._defaultAgent = isHttps ? https.globalAgent : http.globalAgent;
|
||||
|
||||
const agents = options.agent;
|
||||
|
||||
if (agents) {
|
||||
if (agents.addRequest) {
|
||||
throw new Error('The `options.agent` object can contain only `http`, `https` or `http2` properties');
|
||||
}
|
||||
|
||||
options.agent = agents[isHttps ? 'https' : 'http'];
|
||||
}
|
||||
|
||||
if (isHttps) {
|
||||
const protocol = await resolveProtocol(options);
|
||||
|
||||
if (protocol === 'h2') {
|
||||
if (agents) {
|
||||
options.agent = agents.http2;
|
||||
}
|
||||
|
||||
return new Http2ClientRequest(options, callback);
|
||||
}
|
||||
}
|
||||
|
||||
return http.request(options, callback);
|
||||
};
|
||||
|
||||
module.exports.protocolCache = cache;
|
||||
445
mc_test/node_modules/http2-wrapper/source/client-request.js
generated
vendored
Executable file
445
mc_test/node_modules/http2-wrapper/source/client-request.js
generated
vendored
Executable file
@ -0,0 +1,445 @@
|
||||
'use strict';
|
||||
const http2 = require('http2');
|
||||
const {Writable} = require('stream');
|
||||
const {Agent, globalAgent} = require('./agent');
|
||||
const IncomingMessage = require('./incoming-message');
|
||||
const urlToOptions = require('./utils/url-to-options');
|
||||
const proxyEvents = require('./utils/proxy-events');
|
||||
const isRequestPseudoHeader = require('./utils/is-request-pseudo-header');
|
||||
const {
|
||||
ERR_INVALID_ARG_TYPE,
|
||||
ERR_INVALID_PROTOCOL,
|
||||
ERR_HTTP_HEADERS_SENT,
|
||||
ERR_INVALID_HTTP_TOKEN,
|
||||
ERR_HTTP_INVALID_HEADER_VALUE,
|
||||
ERR_INVALID_CHAR
|
||||
} = require('./utils/errors');
|
||||
|
||||
const {
|
||||
HTTP2_HEADER_STATUS,
|
||||
HTTP2_HEADER_METHOD,
|
||||
HTTP2_HEADER_PATH,
|
||||
HTTP2_METHOD_CONNECT
|
||||
} = http2.constants;
|
||||
|
||||
const kHeaders = Symbol('headers');
|
||||
const kOrigin = Symbol('origin');
|
||||
const kSession = Symbol('session');
|
||||
const kOptions = Symbol('options');
|
||||
const kFlushedHeaders = Symbol('flushedHeaders');
|
||||
const kJobs = Symbol('jobs');
|
||||
|
||||
const isValidHttpToken = /^[\^`\-\w!#$%&*+.|~]+$/;
|
||||
const isInvalidHeaderValue = /[^\t\u0020-\u007E\u0080-\u00FF]/;
|
||||
|
||||
class ClientRequest extends Writable {
|
||||
constructor(input, options, callback) {
|
||||
super({
|
||||
autoDestroy: false
|
||||
});
|
||||
|
||||
const hasInput = typeof input === 'string' || input instanceof URL;
|
||||
if (hasInput) {
|
||||
input = urlToOptions(input instanceof URL ? input : new URL(input));
|
||||
}
|
||||
|
||||
if (typeof options === 'function' || options === undefined) {
|
||||
// (options, callback)
|
||||
callback = options;
|
||||
options = hasInput ? input : {...input};
|
||||
} else {
|
||||
// (input, options, callback)
|
||||
options = {...input, ...options};
|
||||
}
|
||||
|
||||
if (options.h2session) {
|
||||
this[kSession] = options.h2session;
|
||||
} else if (options.agent === false) {
|
||||
this.agent = new Agent({maxFreeSessions: 0});
|
||||
} else if (typeof options.agent === 'undefined' || options.agent === null) {
|
||||
if (typeof options.createConnection === 'function') {
|
||||
// This is a workaround - we don't have to create the session on our own.
|
||||
this.agent = new Agent({maxFreeSessions: 0});
|
||||
this.agent.createConnection = options.createConnection;
|
||||
} else {
|
||||
this.agent = globalAgent;
|
||||
}
|
||||
} else if (typeof options.agent.request === 'function') {
|
||||
this.agent = options.agent;
|
||||
} else {
|
||||
throw new ERR_INVALID_ARG_TYPE('options.agent', ['Agent-like Object', 'undefined', 'false'], options.agent);
|
||||
}
|
||||
|
||||
if (options.protocol && options.protocol !== 'https:') {
|
||||
throw new ERR_INVALID_PROTOCOL(options.protocol, 'https:');
|
||||
}
|
||||
|
||||
const port = options.port || options.defaultPort || (this.agent && this.agent.defaultPort) || 443;
|
||||
const host = options.hostname || options.host || 'localhost';
|
||||
|
||||
// Don't enforce the origin via options. It may be changed in an Agent.
|
||||
delete options.hostname;
|
||||
delete options.host;
|
||||
delete options.port;
|
||||
|
||||
const {timeout} = options;
|
||||
options.timeout = undefined;
|
||||
|
||||
this[kHeaders] = Object.create(null);
|
||||
this[kJobs] = [];
|
||||
|
||||
this.socket = null;
|
||||
this.connection = null;
|
||||
|
||||
this.method = options.method || 'GET';
|
||||
this.path = options.path;
|
||||
|
||||
this.res = null;
|
||||
this.aborted = false;
|
||||
this.reusedSocket = false;
|
||||
|
||||
if (options.headers) {
|
||||
for (const [header, value] of Object.entries(options.headers)) {
|
||||
this.setHeader(header, value);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.auth && !('authorization' in this[kHeaders])) {
|
||||
this[kHeaders].authorization = 'Basic ' + Buffer.from(options.auth).toString('base64');
|
||||
}
|
||||
|
||||
options.session = options.tlsSession;
|
||||
options.path = options.socketPath;
|
||||
|
||||
this[kOptions] = options;
|
||||
|
||||
// Clients that generate HTTP/2 requests directly SHOULD use the :authority pseudo-header field instead of the Host header field.
|
||||
if (port === 443) {
|
||||
this[kOrigin] = `https://${host}`;
|
||||
|
||||
if (!(':authority' in this[kHeaders])) {
|
||||
this[kHeaders][':authority'] = host;
|
||||
}
|
||||
} else {
|
||||
this[kOrigin] = `https://${host}:${port}`;
|
||||
|
||||
if (!(':authority' in this[kHeaders])) {
|
||||
this[kHeaders][':authority'] = `${host}:${port}`;
|
||||
}
|
||||
}
|
||||
|
||||
if (timeout) {
|
||||
this.setTimeout(timeout);
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
this.once('response', callback);
|
||||
}
|
||||
|
||||
this[kFlushedHeaders] = false;
|
||||
}
|
||||
|
||||
get method() {
|
||||
return this[kHeaders][HTTP2_HEADER_METHOD];
|
||||
}
|
||||
|
||||
set method(value) {
|
||||
if (value) {
|
||||
this[kHeaders][HTTP2_HEADER_METHOD] = value.toUpperCase();
|
||||
}
|
||||
}
|
||||
|
||||
get path() {
|
||||
return this[kHeaders][HTTP2_HEADER_PATH];
|
||||
}
|
||||
|
||||
set path(value) {
|
||||
if (value) {
|
||||
this[kHeaders][HTTP2_HEADER_PATH] = value;
|
||||
}
|
||||
}
|
||||
|
||||
get _mustNotHaveABody() {
|
||||
return this.method === 'GET' || this.method === 'HEAD' || this.method === 'DELETE';
|
||||
}
|
||||
|
||||
_write(chunk, encoding, callback) {
|
||||
// https://github.com/nodejs/node/blob/654df09ae0c5e17d1b52a900a545f0664d8c7627/lib/internal/http2/util.js#L148-L156
|
||||
if (this._mustNotHaveABody) {
|
||||
callback(new Error('The GET, HEAD and DELETE methods must NOT have a body'));
|
||||
/* istanbul ignore next: Node.js 12 throws directly */
|
||||
return;
|
||||
}
|
||||
|
||||
this.flushHeaders();
|
||||
|
||||
const callWrite = () => this._request.write(chunk, encoding, callback);
|
||||
if (this._request) {
|
||||
callWrite();
|
||||
} else {
|
||||
this[kJobs].push(callWrite);
|
||||
}
|
||||
}
|
||||
|
||||
_final(callback) {
|
||||
if (this.destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.flushHeaders();
|
||||
|
||||
const callEnd = () => {
|
||||
// For GET, HEAD and DELETE
|
||||
if (this._mustNotHaveABody) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
this._request.end(callback);
|
||||
};
|
||||
|
||||
if (this._request) {
|
||||
callEnd();
|
||||
} else {
|
||||
this[kJobs].push(callEnd);
|
||||
}
|
||||
}
|
||||
|
||||
abort() {
|
||||
if (this.res && this.res.complete) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.aborted) {
|
||||
process.nextTick(() => this.emit('abort'));
|
||||
}
|
||||
|
||||
this.aborted = true;
|
||||
|
||||
this.destroy();
|
||||
}
|
||||
|
||||
_destroy(error, callback) {
|
||||
if (this.res) {
|
||||
this.res._dump();
|
||||
}
|
||||
|
||||
if (this._request) {
|
||||
this._request.destroy();
|
||||
}
|
||||
|
||||
callback(error);
|
||||
}
|
||||
|
||||
async flushHeaders() {
|
||||
if (this[kFlushedHeaders] || this.destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this[kFlushedHeaders] = true;
|
||||
|
||||
const isConnectMethod = this.method === HTTP2_METHOD_CONNECT;
|
||||
|
||||
// The real magic is here
|
||||
const onStream = stream => {
|
||||
this._request = stream;
|
||||
|
||||
if (this.destroyed) {
|
||||
stream.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
// Forwards `timeout`, `continue`, `close` and `error` events to this instance.
|
||||
if (!isConnectMethod) {
|
||||
proxyEvents(stream, this, ['timeout', 'continue', 'close', 'error']);
|
||||
}
|
||||
|
||||
// Wait for the `finish` event. We don't want to emit the `response` event
|
||||
// before `request.end()` is called.
|
||||
const waitForEnd = fn => {
|
||||
return (...args) => {
|
||||
if (!this.writable && !this.destroyed) {
|
||||
fn(...args);
|
||||
} else {
|
||||
this.once('finish', () => {
|
||||
fn(...args);
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// This event tells we are ready to listen for the data.
|
||||
stream.once('response', waitForEnd((headers, flags, rawHeaders) => {
|
||||
// If we were to emit raw request stream, it would be as fast as the native approach.
|
||||
// Note that wrapping the raw stream in a Proxy instance won't improve the performance (already tested it).
|
||||
const response = new IncomingMessage(this.socket, stream.readableHighWaterMark);
|
||||
this.res = response;
|
||||
|
||||
response.req = this;
|
||||
response.statusCode = headers[HTTP2_HEADER_STATUS];
|
||||
response.headers = headers;
|
||||
response.rawHeaders = rawHeaders;
|
||||
|
||||
response.once('end', () => {
|
||||
if (this.aborted) {
|
||||
response.aborted = true;
|
||||
response.emit('aborted');
|
||||
} else {
|
||||
response.complete = true;
|
||||
|
||||
// Has no effect, just be consistent with the Node.js behavior
|
||||
response.socket = null;
|
||||
response.connection = null;
|
||||
}
|
||||
});
|
||||
|
||||
if (isConnectMethod) {
|
||||
response.upgrade = true;
|
||||
|
||||
// The HTTP1 API says the socket is detached here,
|
||||
// but we can't do that so we pass the original HTTP2 request.
|
||||
if (this.emit('connect', response, stream, Buffer.alloc(0))) {
|
||||
this.emit('close');
|
||||
} else {
|
||||
// No listeners attached, destroy the original request.
|
||||
stream.destroy();
|
||||
}
|
||||
} else {
|
||||
// Forwards data
|
||||
stream.on('data', chunk => {
|
||||
if (!response._dumped && !response.push(chunk)) {
|
||||
stream.pause();
|
||||
}
|
||||
});
|
||||
|
||||
stream.once('end', () => {
|
||||
response.push(null);
|
||||
});
|
||||
|
||||
if (!this.emit('response', response)) {
|
||||
// No listeners attached, dump the response.
|
||||
response._dump();
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
// Emits `information` event
|
||||
stream.once('headers', waitForEnd(
|
||||
headers => this.emit('information', {statusCode: headers[HTTP2_HEADER_STATUS]})
|
||||
));
|
||||
|
||||
stream.once('trailers', waitForEnd((trailers, flags, rawTrailers) => {
|
||||
const {res} = this;
|
||||
|
||||
// Assigns trailers to the response object.
|
||||
res.trailers = trailers;
|
||||
res.rawTrailers = rawTrailers;
|
||||
}));
|
||||
|
||||
const {socket} = stream.session;
|
||||
this.socket = socket;
|
||||
this.connection = socket;
|
||||
|
||||
for (const job of this[kJobs]) {
|
||||
job();
|
||||
}
|
||||
|
||||
this.emit('socket', this.socket);
|
||||
};
|
||||
|
||||
// Makes a HTTP2 request
|
||||
if (this[kSession]) {
|
||||
try {
|
||||
onStream(this[kSession].request(this[kHeaders]));
|
||||
} catch (error) {
|
||||
this.emit('error', error);
|
||||
}
|
||||
} else {
|
||||
this.reusedSocket = true;
|
||||
|
||||
try {
|
||||
onStream(await this.agent.request(this[kOrigin], this[kOptions], this[kHeaders]));
|
||||
} catch (error) {
|
||||
this.emit('error', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getHeader(name) {
|
||||
if (typeof name !== 'string') {
|
||||
throw new ERR_INVALID_ARG_TYPE('name', 'string', name);
|
||||
}
|
||||
|
||||
return this[kHeaders][name.toLowerCase()];
|
||||
}
|
||||
|
||||
get headersSent() {
|
||||
return this[kFlushedHeaders];
|
||||
}
|
||||
|
||||
removeHeader(name) {
|
||||
if (typeof name !== 'string') {
|
||||
throw new ERR_INVALID_ARG_TYPE('name', 'string', name);
|
||||
}
|
||||
|
||||
if (this.headersSent) {
|
||||
throw new ERR_HTTP_HEADERS_SENT('remove');
|
||||
}
|
||||
|
||||
delete this[kHeaders][name.toLowerCase()];
|
||||
}
|
||||
|
||||
setHeader(name, value) {
|
||||
if (this.headersSent) {
|
||||
throw new ERR_HTTP_HEADERS_SENT('set');
|
||||
}
|
||||
|
||||
if (typeof name !== 'string' || (!isValidHttpToken.test(name) && !isRequestPseudoHeader(name))) {
|
||||
throw new ERR_INVALID_HTTP_TOKEN('Header name', name);
|
||||
}
|
||||
|
||||
if (typeof value === 'undefined') {
|
||||
throw new ERR_HTTP_INVALID_HEADER_VALUE(value, name);
|
||||
}
|
||||
|
||||
if (isInvalidHeaderValue.test(value)) {
|
||||
throw new ERR_INVALID_CHAR('header content', name);
|
||||
}
|
||||
|
||||
this[kHeaders][name.toLowerCase()] = value;
|
||||
}
|
||||
|
||||
setNoDelay() {
|
||||
// HTTP2 sockets cannot be malformed, do nothing.
|
||||
}
|
||||
|
||||
setSocketKeepAlive() {
|
||||
// HTTP2 sockets cannot be malformed, do nothing.
|
||||
}
|
||||
|
||||
setTimeout(ms, callback) {
|
||||
const applyTimeout = () => this._request.setTimeout(ms, callback);
|
||||
|
||||
if (this._request) {
|
||||
applyTimeout();
|
||||
} else {
|
||||
this[kJobs].push(applyTimeout);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
get maxHeadersCount() {
|
||||
if (!this.destroyed && this._request) {
|
||||
return this._request.session.localSettings.maxHeaderListSize;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
set maxHeadersCount(_value) {
|
||||
// Updating HTTP2 settings would affect all requests, do nothing.
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ClientRequest;
|
||||
58
mc_test/node_modules/http2-wrapper/source/incoming-message.js
generated
vendored
Executable file
58
mc_test/node_modules/http2-wrapper/source/incoming-message.js
generated
vendored
Executable file
@ -0,0 +1,58 @@
|
||||
'use strict';
|
||||
const {Readable} = require('stream');
|
||||
|
||||
class IncomingMessage extends Readable {
|
||||
constructor(socket, highWaterMark) {
|
||||
super({
|
||||
highWaterMark,
|
||||
autoDestroy: false
|
||||
});
|
||||
|
||||
this.statusCode = null;
|
||||
this.statusMessage = '';
|
||||
this.httpVersion = '2.0';
|
||||
this.httpVersionMajor = 2;
|
||||
this.httpVersionMinor = 0;
|
||||
this.headers = {};
|
||||
this.trailers = {};
|
||||
this.req = null;
|
||||
|
||||
this.aborted = false;
|
||||
this.complete = false;
|
||||
this.upgrade = null;
|
||||
|
||||
this.rawHeaders = [];
|
||||
this.rawTrailers = [];
|
||||
|
||||
this.socket = socket;
|
||||
this.connection = socket;
|
||||
|
||||
this._dumped = false;
|
||||
}
|
||||
|
||||
_destroy(error) {
|
||||
this.req._request.destroy(error);
|
||||
}
|
||||
|
||||
setTimeout(ms, callback) {
|
||||
this.req.setTimeout(ms, callback);
|
||||
return this;
|
||||
}
|
||||
|
||||
_dump() {
|
||||
if (!this._dumped) {
|
||||
this._dumped = true;
|
||||
|
||||
this.removeAllListeners('data');
|
||||
this.resume();
|
||||
}
|
||||
}
|
||||
|
||||
_read() {
|
||||
if (this.req) {
|
||||
this.req._request.resume();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = IncomingMessage;
|
||||
28
mc_test/node_modules/http2-wrapper/source/index.js
generated
vendored
Executable file
28
mc_test/node_modules/http2-wrapper/source/index.js
generated
vendored
Executable file
@ -0,0 +1,28 @@
|
||||
'use strict';
|
||||
const http2 = require('http2');
|
||||
const agent = require('./agent');
|
||||
const ClientRequest = require('./client-request');
|
||||
const IncomingMessage = require('./incoming-message');
|
||||
const auto = require('./auto');
|
||||
|
||||
const request = (url, options, callback) => {
|
||||
return new ClientRequest(url, options, callback);
|
||||
};
|
||||
|
||||
const get = (url, options, callback) => {
|
||||
// eslint-disable-next-line unicorn/prevent-abbreviations
|
||||
const req = new ClientRequest(url, options, callback);
|
||||
req.end();
|
||||
|
||||
return req;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
...http2,
|
||||
ClientRequest,
|
||||
IncomingMessage,
|
||||
...agent,
|
||||
request,
|
||||
get,
|
||||
auto
|
||||
};
|
||||
27
mc_test/node_modules/http2-wrapper/source/utils/calculate-server-name.js
generated
vendored
Executable file
27
mc_test/node_modules/http2-wrapper/source/utils/calculate-server-name.js
generated
vendored
Executable file
@ -0,0 +1,27 @@
|
||||
'use strict';
|
||||
const net = require('net');
|
||||
/* istanbul ignore file: https://github.com/nodejs/node/blob/v13.0.1/lib/_http_agent.js */
|
||||
|
||||
module.exports = options => {
|
||||
let servername = options.host;
|
||||
const hostHeader = options.headers && options.headers.host;
|
||||
|
||||
if (hostHeader) {
|
||||
if (hostHeader.startsWith('[')) {
|
||||
const index = hostHeader.indexOf(']');
|
||||
if (index === -1) {
|
||||
servername = hostHeader;
|
||||
} else {
|
||||
servername = hostHeader.slice(1, -1);
|
||||
}
|
||||
} else {
|
||||
servername = hostHeader.split(':', 1)[0];
|
||||
}
|
||||
}
|
||||
|
||||
if (net.isIP(servername)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return servername;
|
||||
};
|
||||
45
mc_test/node_modules/http2-wrapper/source/utils/errors.js
generated
vendored
Executable file
45
mc_test/node_modules/http2-wrapper/source/utils/errors.js
generated
vendored
Executable file
@ -0,0 +1,45 @@
|
||||
'use strict';
|
||||
/* istanbul ignore file: https://github.com/nodejs/node/blob/master/lib/internal/errors.js */
|
||||
|
||||
const makeError = (Base, key, getMessage) => {
|
||||
module.exports[key] = class NodeError extends Base {
|
||||
constructor(...args) {
|
||||
super(typeof getMessage === 'string' ? getMessage : getMessage(args));
|
||||
this.name = `${super.name} [${key}]`;
|
||||
this.code = key;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
makeError(TypeError, 'ERR_INVALID_ARG_TYPE', args => {
|
||||
const type = args[0].includes('.') ? 'property' : 'argument';
|
||||
|
||||
let valid = args[1];
|
||||
const isManyTypes = Array.isArray(valid);
|
||||
|
||||
if (isManyTypes) {
|
||||
valid = `${valid.slice(0, -1).join(', ')} or ${valid.slice(-1)}`;
|
||||
}
|
||||
|
||||
return `The "${args[0]}" ${type} must be ${isManyTypes ? 'one of' : 'of'} type ${valid}. Received ${typeof args[2]}`;
|
||||
});
|
||||
|
||||
makeError(TypeError, 'ERR_INVALID_PROTOCOL', args => {
|
||||
return `Protocol "${args[0]}" not supported. Expected "${args[1]}"`;
|
||||
});
|
||||
|
||||
makeError(Error, 'ERR_HTTP_HEADERS_SENT', args => {
|
||||
return `Cannot ${args[0]} headers after they are sent to the client`;
|
||||
});
|
||||
|
||||
makeError(TypeError, 'ERR_INVALID_HTTP_TOKEN', args => {
|
||||
return `${args[0]} must be a valid HTTP token [${args[1]}]`;
|
||||
});
|
||||
|
||||
makeError(TypeError, 'ERR_HTTP_INVALID_HEADER_VALUE', args => {
|
||||
return `Invalid value "${args[0]} for header "${args[1]}"`;
|
||||
});
|
||||
|
||||
makeError(TypeError, 'ERR_INVALID_CHAR', args => {
|
||||
return `Invalid character in ${args[0]} [${args[1]}]`;
|
||||
});
|
||||
13
mc_test/node_modules/http2-wrapper/source/utils/is-request-pseudo-header.js
generated
vendored
Executable file
13
mc_test/node_modules/http2-wrapper/source/utils/is-request-pseudo-header.js
generated
vendored
Executable file
@ -0,0 +1,13 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = header => {
|
||||
switch (header) {
|
||||
case ':method':
|
||||
case ':scheme':
|
||||
case ':authority':
|
||||
case ':path':
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
7
mc_test/node_modules/http2-wrapper/source/utils/proxy-events.js
generated
vendored
Executable file
7
mc_test/node_modules/http2-wrapper/source/utils/proxy-events.js
generated
vendored
Executable file
@ -0,0 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (from, to, events) => {
|
||||
for (const event of events) {
|
||||
from.on(event, (...args) => to.emit(event, ...args));
|
||||
}
|
||||
};
|
||||
25
mc_test/node_modules/http2-wrapper/source/utils/url-to-options.js
generated
vendored
Executable file
25
mc_test/node_modules/http2-wrapper/source/utils/url-to-options.js
generated
vendored
Executable file
@ -0,0 +1,25 @@
|
||||
'use strict';
|
||||
/* istanbul ignore file: https://github.com/nodejs/node/blob/a91293d4d9ab403046ab5eb022332e4e3d249bd3/lib/internal/url.js#L1257 */
|
||||
|
||||
module.exports = url => {
|
||||
const options = {
|
||||
protocol: url.protocol,
|
||||
hostname: typeof url.hostname === 'string' && url.hostname.startsWith('[') ? url.hostname.slice(1, -1) : url.hostname,
|
||||
host: url.host,
|
||||
hash: url.hash,
|
||||
search: url.search,
|
||||
pathname: url.pathname,
|
||||
href: url.href,
|
||||
path: `${url.pathname || ''}${url.search || ''}`
|
||||
};
|
||||
|
||||
if (typeof url.port === 'string' && url.port.length !== 0) {
|
||||
options.port = Number(url.port);
|
||||
}
|
||||
|
||||
if (url.username || url.password) {
|
||||
options.auth = `${url.username || ''}:${url.password || ''}`;
|
||||
}
|
||||
|
||||
return options;
|
||||
};
|
||||
Reference in New Issue
Block a user