domain-alive 0.1.14 → 0.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1540 -44
- package/dist/index.mjs +1532 -37
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -4,13 +4,14 @@ var tldts = require('tldts');
|
|
|
4
4
|
var require$$0$1 = require('dgram');
|
|
5
5
|
var require$$0 = require('util');
|
|
6
6
|
var require$$0$2 = require('net');
|
|
7
|
-
var require$$0$
|
|
7
|
+
var require$$0$4 = require('http');
|
|
8
8
|
var require$$1 = require('https');
|
|
9
|
-
var require$$
|
|
10
|
-
var require$$
|
|
11
|
-
var require$$0$
|
|
12
|
-
var require$$
|
|
13
|
-
var require$$
|
|
9
|
+
var require$$0$3 = require('url');
|
|
10
|
+
var require$$1$1 = require('events');
|
|
11
|
+
var require$$0$5 = require('tls');
|
|
12
|
+
var require$$3 = require('http2');
|
|
13
|
+
var require$$0$6 = require('stream');
|
|
14
|
+
var require$$2 = require('assert');
|
|
14
15
|
var asyncRetry = require('foxts/async-retry');
|
|
15
16
|
var net = require('node:net');
|
|
16
17
|
var retrie = require('foxts/retrie');
|
|
@@ -23,13 +24,14 @@ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
|
23
24
|
var require$$0__default$1 = /*#__PURE__*/_interopDefault(require$$0$1);
|
|
24
25
|
var require$$0__default = /*#__PURE__*/_interopDefault(require$$0);
|
|
25
26
|
var require$$0__default$2 = /*#__PURE__*/_interopDefault(require$$0$2);
|
|
26
|
-
var require$$0__default$
|
|
27
|
+
var require$$0__default$4 = /*#__PURE__*/_interopDefault(require$$0$4);
|
|
27
28
|
var require$$1__default = /*#__PURE__*/_interopDefault(require$$1);
|
|
29
|
+
var require$$0__default$3 = /*#__PURE__*/_interopDefault(require$$0$3);
|
|
30
|
+
var require$$1__default$1 = /*#__PURE__*/_interopDefault(require$$1$1);
|
|
31
|
+
var require$$0__default$5 = /*#__PURE__*/_interopDefault(require$$0$5);
|
|
32
|
+
var require$$3__default = /*#__PURE__*/_interopDefault(require$$3);
|
|
33
|
+
var require$$0__default$6 = /*#__PURE__*/_interopDefault(require$$0$6);
|
|
28
34
|
var require$$2__default = /*#__PURE__*/_interopDefault(require$$2);
|
|
29
|
-
var require$$4__default = /*#__PURE__*/_interopDefault(require$$4);
|
|
30
|
-
var require$$0__default$4 = /*#__PURE__*/_interopDefault(require$$0$4);
|
|
31
|
-
var require$$2__default$1 = /*#__PURE__*/_interopDefault(require$$2$1);
|
|
32
|
-
var require$$2__default$2 = /*#__PURE__*/_interopDefault(require$$2$2);
|
|
33
35
|
var net__default = /*#__PURE__*/_interopDefault(net);
|
|
34
36
|
var debug__default = /*#__PURE__*/_interopDefault(debug);
|
|
35
37
|
|
|
@@ -1208,11 +1210,11 @@ var hasRequiredDoh$1;
|
|
|
1208
1210
|
function requireDoh$1 () {
|
|
1209
1211
|
if (hasRequiredDoh$1) return doh$1;
|
|
1210
1212
|
hasRequiredDoh$1 = 1;
|
|
1211
|
-
const http = require$$0__default$
|
|
1213
|
+
const http = require$$0__default$4.default;
|
|
1212
1214
|
const https = require$$1__default.default;
|
|
1213
|
-
const { URL } = require$$
|
|
1215
|
+
const { URL } = require$$0__default$3.default;
|
|
1214
1216
|
const Packet = requirePacket();
|
|
1215
|
-
const EventEmitter = require$$
|
|
1217
|
+
const EventEmitter = require$$1__default$1.default;
|
|
1216
1218
|
const { debuglog } = require$$0__default.default;
|
|
1217
1219
|
const debug = debuglog('dns2-server');
|
|
1218
1220
|
const decodeBase64URL = (str)=>{
|
|
@@ -1356,7 +1358,7 @@ var hasRequiredDns;
|
|
|
1356
1358
|
function requireDns () {
|
|
1357
1359
|
if (hasRequiredDns) return dns;
|
|
1358
1360
|
hasRequiredDns = 1;
|
|
1359
|
-
const EventEmitter = require$$
|
|
1361
|
+
const EventEmitter = require$$1__default$1.default;
|
|
1360
1362
|
const DOHServer = requireDoh$1();
|
|
1361
1363
|
const TCPServer = requireTcp$1();
|
|
1362
1364
|
const UDPServer = requireUdp$1();
|
|
@@ -1477,7 +1479,7 @@ var hasRequiredTcp;
|
|
|
1477
1479
|
function requireTcp () {
|
|
1478
1480
|
if (hasRequiredTcp) return tcp_1;
|
|
1479
1481
|
hasRequiredTcp = 1;
|
|
1480
|
-
const tls = require$$0__default$
|
|
1482
|
+
const tls = require$$0__default$5.default;
|
|
1481
1483
|
const tcp = require$$0__default$2.default;
|
|
1482
1484
|
const Packet = requirePacket();
|
|
1483
1485
|
const makeQuery = ({ name, type = 'A', cls = Packet.CLASS.IN, clientIp, recursive = true })=>{
|
|
@@ -1538,41 +1540,1531 @@ function requireTcp () {
|
|
|
1538
1540
|
return tcp_1;
|
|
1539
1541
|
}
|
|
1540
1542
|
|
|
1543
|
+
var quickLru;
|
|
1544
|
+
var hasRequiredQuickLru;
|
|
1545
|
+
|
|
1546
|
+
function requireQuickLru () {
|
|
1547
|
+
if (hasRequiredQuickLru) return quickLru;
|
|
1548
|
+
hasRequiredQuickLru = 1;
|
|
1549
|
+
var _computedKey;
|
|
1550
|
+
_computedKey = Symbol.iterator;
|
|
1551
|
+
class QuickLRU {
|
|
1552
|
+
constructor(options = {}){
|
|
1553
|
+
if (!(options.maxSize && options.maxSize > 0)) {
|
|
1554
|
+
throw new TypeError('`maxSize` must be a number greater than 0');
|
|
1555
|
+
}
|
|
1556
|
+
this.maxSize = options.maxSize;
|
|
1557
|
+
this.onEviction = options.onEviction;
|
|
1558
|
+
this.cache = new Map();
|
|
1559
|
+
this.oldCache = new Map();
|
|
1560
|
+
this._size = 0;
|
|
1561
|
+
}
|
|
1562
|
+
_set(key, value) {
|
|
1563
|
+
this.cache.set(key, value);
|
|
1564
|
+
this._size++;
|
|
1565
|
+
if (this._size >= this.maxSize) {
|
|
1566
|
+
this._size = 0;
|
|
1567
|
+
if (typeof this.onEviction === 'function') {
|
|
1568
|
+
for (const [key, value] of this.oldCache.entries()){
|
|
1569
|
+
this.onEviction(key, value);
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
this.oldCache = this.cache;
|
|
1573
|
+
this.cache = new Map();
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
get(key) {
|
|
1577
|
+
if (this.cache.has(key)) {
|
|
1578
|
+
return this.cache.get(key);
|
|
1579
|
+
}
|
|
1580
|
+
if (this.oldCache.has(key)) {
|
|
1581
|
+
const value = this.oldCache.get(key);
|
|
1582
|
+
this.oldCache.delete(key);
|
|
1583
|
+
this._set(key, value);
|
|
1584
|
+
return value;
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
set(key, value) {
|
|
1588
|
+
if (this.cache.has(key)) {
|
|
1589
|
+
this.cache.set(key, value);
|
|
1590
|
+
} else {
|
|
1591
|
+
this._set(key, value);
|
|
1592
|
+
}
|
|
1593
|
+
return this;
|
|
1594
|
+
}
|
|
1595
|
+
has(key) {
|
|
1596
|
+
return this.cache.has(key) || this.oldCache.has(key);
|
|
1597
|
+
}
|
|
1598
|
+
peek(key) {
|
|
1599
|
+
if (this.cache.has(key)) {
|
|
1600
|
+
return this.cache.get(key);
|
|
1601
|
+
}
|
|
1602
|
+
if (this.oldCache.has(key)) {
|
|
1603
|
+
return this.oldCache.get(key);
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
delete(key) {
|
|
1607
|
+
const deleted = this.cache.delete(key);
|
|
1608
|
+
if (deleted) {
|
|
1609
|
+
this._size--;
|
|
1610
|
+
}
|
|
1611
|
+
return this.oldCache.delete(key) || deleted;
|
|
1612
|
+
}
|
|
1613
|
+
clear() {
|
|
1614
|
+
this.cache.clear();
|
|
1615
|
+
this.oldCache.clear();
|
|
1616
|
+
this._size = 0;
|
|
1617
|
+
}
|
|
1618
|
+
*keys() {
|
|
1619
|
+
for (const [key] of this){
|
|
1620
|
+
yield key;
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
*values() {
|
|
1624
|
+
for (const [, value] of this){
|
|
1625
|
+
yield value;
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
*[_computedKey]() {
|
|
1629
|
+
for (const item of this.cache){
|
|
1630
|
+
yield item;
|
|
1631
|
+
}
|
|
1632
|
+
for (const item of this.oldCache){
|
|
1633
|
+
const [key] = item;
|
|
1634
|
+
if (!this.cache.has(key)) {
|
|
1635
|
+
yield item;
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
get size() {
|
|
1640
|
+
let oldCacheSize = 0;
|
|
1641
|
+
for (const key of this.oldCache.keys()){
|
|
1642
|
+
if (!this.cache.has(key)) {
|
|
1643
|
+
oldCacheSize++;
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
return Math.min(this._size + oldCacheSize, this.maxSize);
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
quickLru = QuickLRU;
|
|
1650
|
+
return quickLru;
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
var delayAsyncDestroy;
|
|
1654
|
+
var hasRequiredDelayAsyncDestroy;
|
|
1655
|
+
|
|
1656
|
+
function requireDelayAsyncDestroy () {
|
|
1657
|
+
if (hasRequiredDelayAsyncDestroy) return delayAsyncDestroy;
|
|
1658
|
+
hasRequiredDelayAsyncDestroy = 1;
|
|
1659
|
+
delayAsyncDestroy = (stream)=>{
|
|
1660
|
+
if (stream.listenerCount('error') !== 0) {
|
|
1661
|
+
return stream;
|
|
1662
|
+
}
|
|
1663
|
+
stream.__destroy = stream._destroy;
|
|
1664
|
+
stream._destroy = (...args)=>{
|
|
1665
|
+
const callback = args.pop();
|
|
1666
|
+
stream.__destroy(...args, async (error)=>{
|
|
1667
|
+
await Promise.resolve();
|
|
1668
|
+
callback(error);
|
|
1669
|
+
});
|
|
1670
|
+
};
|
|
1671
|
+
const onError = (error)=>{
|
|
1672
|
+
// eslint-disable-next-line promise/prefer-await-to-then
|
|
1673
|
+
Promise.resolve().then(()=>{
|
|
1674
|
+
stream.emit('error', error);
|
|
1675
|
+
});
|
|
1676
|
+
};
|
|
1677
|
+
stream.once('error', onError);
|
|
1678
|
+
// eslint-disable-next-line promise/prefer-await-to-then
|
|
1679
|
+
Promise.resolve().then(()=>{
|
|
1680
|
+
stream.off('error', onError);
|
|
1681
|
+
});
|
|
1682
|
+
return stream;
|
|
1683
|
+
};
|
|
1684
|
+
return delayAsyncDestroy;
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
var agent;
|
|
1688
|
+
var hasRequiredAgent;
|
|
1689
|
+
|
|
1690
|
+
function requireAgent () {
|
|
1691
|
+
if (hasRequiredAgent) return agent;
|
|
1692
|
+
hasRequiredAgent = 1;
|
|
1693
|
+
// See https://github.com/facebook/jest/issues/2549
|
|
1694
|
+
// eslint-disable-next-line node/prefer-global/url
|
|
1695
|
+
const { URL } = require$$0__default$3.default;
|
|
1696
|
+
const EventEmitter = require$$1__default$1.default;
|
|
1697
|
+
const tls = require$$0__default$5.default;
|
|
1698
|
+
const http2 = require$$3__default.default;
|
|
1699
|
+
const QuickLRU = requireQuickLru();
|
|
1700
|
+
const delayAsyncDestroy = requireDelayAsyncDestroy();
|
|
1701
|
+
const kCurrentStreamCount = Symbol('currentStreamCount');
|
|
1702
|
+
const kRequest = Symbol('request');
|
|
1703
|
+
const kOriginSet = Symbol('cachedOriginSet');
|
|
1704
|
+
const kGracefullyClosing = Symbol('gracefullyClosing');
|
|
1705
|
+
const kLength = Symbol('length');
|
|
1706
|
+
const nameKeys = [
|
|
1707
|
+
// Not an Agent option actually
|
|
1708
|
+
'createConnection',
|
|
1709
|
+
// `http2.connect()` options
|
|
1710
|
+
'maxDeflateDynamicTableSize',
|
|
1711
|
+
'maxSettings',
|
|
1712
|
+
'maxSessionMemory',
|
|
1713
|
+
'maxHeaderListPairs',
|
|
1714
|
+
'maxOutstandingPings',
|
|
1715
|
+
'maxReservedRemoteStreams',
|
|
1716
|
+
'maxSendHeaderBlockLength',
|
|
1717
|
+
'paddingStrategy',
|
|
1718
|
+
'peerMaxConcurrentStreams',
|
|
1719
|
+
'settings',
|
|
1720
|
+
// `tls.connect()` source options
|
|
1721
|
+
'family',
|
|
1722
|
+
'localAddress',
|
|
1723
|
+
'rejectUnauthorized',
|
|
1724
|
+
// `tls.connect()` secure context options
|
|
1725
|
+
'pskCallback',
|
|
1726
|
+
'minDHSize',
|
|
1727
|
+
// `tls.connect()` destination options
|
|
1728
|
+
// - `servername` is automatically validated, skip it
|
|
1729
|
+
// - `host` and `port` just describe the destination server,
|
|
1730
|
+
'path',
|
|
1731
|
+
'socket',
|
|
1732
|
+
// `tls.createSecureContext()` options
|
|
1733
|
+
'ca',
|
|
1734
|
+
'cert',
|
|
1735
|
+
'sigalgs',
|
|
1736
|
+
'ciphers',
|
|
1737
|
+
'clientCertEngine',
|
|
1738
|
+
'crl',
|
|
1739
|
+
'dhparam',
|
|
1740
|
+
'ecdhCurve',
|
|
1741
|
+
'honorCipherOrder',
|
|
1742
|
+
'key',
|
|
1743
|
+
'privateKeyEngine',
|
|
1744
|
+
'privateKeyIdentifier',
|
|
1745
|
+
'maxVersion',
|
|
1746
|
+
'minVersion',
|
|
1747
|
+
'pfx',
|
|
1748
|
+
'secureOptions',
|
|
1749
|
+
'secureProtocol',
|
|
1750
|
+
'sessionIdContext',
|
|
1751
|
+
'ticketKeys'
|
|
1752
|
+
];
|
|
1753
|
+
const getSortedIndex = (array, value, compare)=>{
|
|
1754
|
+
let low = 0;
|
|
1755
|
+
let high = array.length;
|
|
1756
|
+
while(low < high){
|
|
1757
|
+
const mid = low + high >>> 1;
|
|
1758
|
+
if (compare(array[mid], value)) {
|
|
1759
|
+
low = mid + 1;
|
|
1760
|
+
} else {
|
|
1761
|
+
high = mid;
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
return low;
|
|
1765
|
+
};
|
|
1766
|
+
const compareSessions = (a, b)=>a.remoteSettings.maxConcurrentStreams > b.remoteSettings.maxConcurrentStreams;
|
|
1767
|
+
// See https://tools.ietf.org/html/rfc8336
|
|
1768
|
+
const closeCoveredSessions = (where, session)=>{
|
|
1769
|
+
// Clients SHOULD NOT emit new requests on any connection whose Origin
|
|
1770
|
+
// Set is a proper subset of another connection's Origin Set, and they
|
|
1771
|
+
// SHOULD close it once all outstanding requests are satisfied.
|
|
1772
|
+
for(let index = 0; index < where.length; index++){
|
|
1773
|
+
const coveredSession = where[index];
|
|
1774
|
+
if (// Unfortunately `.every()` returns true for an empty array
|
|
1775
|
+
coveredSession[kOriginSet].length > 0 && coveredSession[kOriginSet].length < session[kOriginSet].length && coveredSession[kOriginSet].every((origin)=>session[kOriginSet].includes(origin)) && coveredSession[kCurrentStreamCount] + session[kCurrentStreamCount] <= session.remoteSettings.maxConcurrentStreams) {
|
|
1776
|
+
// This allows pending requests to finish and prevents making new requests.
|
|
1777
|
+
gracefullyClose(coveredSession);
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
};
|
|
1781
|
+
// This is basically inverted `closeCoveredSessions(...)`.
|
|
1782
|
+
const closeSessionIfCovered = (where, coveredSession)=>{
|
|
1783
|
+
for(let index = 0; index < where.length; index++){
|
|
1784
|
+
const session = where[index];
|
|
1785
|
+
if (coveredSession[kOriginSet].length > 0 && coveredSession[kOriginSet].length < session[kOriginSet].length && coveredSession[kOriginSet].every((origin)=>session[kOriginSet].includes(origin)) && coveredSession[kCurrentStreamCount] + session[kCurrentStreamCount] <= session.remoteSettings.maxConcurrentStreams) {
|
|
1786
|
+
gracefullyClose(coveredSession);
|
|
1787
|
+
return true;
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
return false;
|
|
1791
|
+
};
|
|
1792
|
+
const gracefullyClose = (session)=>{
|
|
1793
|
+
session[kGracefullyClosing] = true;
|
|
1794
|
+
if (session[kCurrentStreamCount] === 0) {
|
|
1795
|
+
session.close();
|
|
1796
|
+
}
|
|
1797
|
+
};
|
|
1798
|
+
class Agent extends EventEmitter {
|
|
1799
|
+
constructor({ timeout = 0, maxSessions = Number.POSITIVE_INFINITY, maxEmptySessions = 10, maxCachedTlsSessions = 100 } = {}){
|
|
1800
|
+
super();
|
|
1801
|
+
// SESSIONS[NORMALIZED_OPTIONS] = [];
|
|
1802
|
+
this.sessions = {};
|
|
1803
|
+
// The queue for creating new sessions. It looks like this:
|
|
1804
|
+
// QUEUE[NORMALIZED_OPTIONS][NORMALIZED_ORIGIN] = ENTRY_FUNCTION
|
|
1805
|
+
//
|
|
1806
|
+
// It's faster when there are many origins. If there's only one, then QUEUE[`${options}:${origin}`] is faster.
|
|
1807
|
+
// I guess object creation / deletion is causing the slowdown.
|
|
1808
|
+
//
|
|
1809
|
+
// The entry function has `listeners`, `completed` and `destroyed` properties.
|
|
1810
|
+
// `listeners` is an array of objects containing `resolve` and `reject` functions.
|
|
1811
|
+
// `completed` is a boolean. It's set to true after ENTRY_FUNCTION is executed.
|
|
1812
|
+
// `destroyed` is a boolean. If it's set to true, the session will be destroyed if hasn't connected yet.
|
|
1813
|
+
this.queue = {};
|
|
1814
|
+
// Each session will use this timeout value.
|
|
1815
|
+
this.timeout = timeout;
|
|
1816
|
+
// Max sessions in total
|
|
1817
|
+
this.maxSessions = maxSessions;
|
|
1818
|
+
// Max empty sessions in total
|
|
1819
|
+
this.maxEmptySessions = maxEmptySessions;
|
|
1820
|
+
this._emptySessionCount = 0;
|
|
1821
|
+
this._sessionCount = 0;
|
|
1822
|
+
// We don't support push streams by default.
|
|
1823
|
+
this.settings = {
|
|
1824
|
+
enablePush: false,
|
|
1825
|
+
initialWindowSize: 1024 * 1024 * 32 // 32MB, see https://github.com/nodejs/node/issues/38426
|
|
1826
|
+
};
|
|
1827
|
+
// Reusing TLS sessions increases performance.
|
|
1828
|
+
this.tlsSessionCache = new QuickLRU({
|
|
1829
|
+
maxSize: maxCachedTlsSessions
|
|
1830
|
+
});
|
|
1831
|
+
}
|
|
1832
|
+
get protocol() {
|
|
1833
|
+
return 'https:';
|
|
1834
|
+
}
|
|
1835
|
+
normalizeOptions(options) {
|
|
1836
|
+
let normalized = '';
|
|
1837
|
+
for(let index = 0; index < nameKeys.length; index++){
|
|
1838
|
+
const key = nameKeys[index];
|
|
1839
|
+
normalized += ':';
|
|
1840
|
+
if (options && options[key] !== undefined) {
|
|
1841
|
+
normalized += options[key];
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
return normalized;
|
|
1845
|
+
}
|
|
1846
|
+
_processQueue() {
|
|
1847
|
+
if (this._sessionCount >= this.maxSessions) {
|
|
1848
|
+
this.closeEmptySessions(this.maxSessions - this._sessionCount + 1);
|
|
1849
|
+
return;
|
|
1850
|
+
}
|
|
1851
|
+
// eslint-disable-next-line guard-for-in
|
|
1852
|
+
for(const normalizedOptions in this.queue){
|
|
1853
|
+
// eslint-disable-next-line guard-for-in
|
|
1854
|
+
for(const normalizedOrigin in this.queue[normalizedOptions]){
|
|
1855
|
+
const item = this.queue[normalizedOptions][normalizedOrigin];
|
|
1856
|
+
// The entry function can be run only once.
|
|
1857
|
+
if (!item.completed) {
|
|
1858
|
+
item.completed = true;
|
|
1859
|
+
item();
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
_isBetterSession(thisStreamCount, thatStreamCount) {
|
|
1865
|
+
return thisStreamCount > thatStreamCount;
|
|
1866
|
+
}
|
|
1867
|
+
_accept(session, listeners, normalizedOrigin, options) {
|
|
1868
|
+
let index = 0;
|
|
1869
|
+
while(index < listeners.length && session[kCurrentStreamCount] < session.remoteSettings.maxConcurrentStreams){
|
|
1870
|
+
// We assume `resolve(...)` calls `request(...)` *directly*,
|
|
1871
|
+
// otherwise the session will get overloaded.
|
|
1872
|
+
listeners[index].resolve(session);
|
|
1873
|
+
index++;
|
|
1874
|
+
}
|
|
1875
|
+
listeners.splice(0, index);
|
|
1876
|
+
if (listeners.length > 0) {
|
|
1877
|
+
this.getSession(normalizedOrigin, options, listeners);
|
|
1878
|
+
listeners.length = 0;
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
getSession(origin, options, listeners) {
|
|
1882
|
+
return new Promise((resolve, reject)=>{
|
|
1883
|
+
if (Array.isArray(listeners) && listeners.length > 0) {
|
|
1884
|
+
listeners = [
|
|
1885
|
+
...listeners
|
|
1886
|
+
];
|
|
1887
|
+
// Resolve the current promise ASAP, we're just moving the listeners.
|
|
1888
|
+
// They will be executed at a different time.
|
|
1889
|
+
resolve();
|
|
1890
|
+
} else {
|
|
1891
|
+
listeners = [
|
|
1892
|
+
{
|
|
1893
|
+
resolve,
|
|
1894
|
+
reject
|
|
1895
|
+
}
|
|
1896
|
+
];
|
|
1897
|
+
}
|
|
1898
|
+
try {
|
|
1899
|
+
// Parse origin
|
|
1900
|
+
if (typeof origin === 'string') {
|
|
1901
|
+
origin = new URL(origin);
|
|
1902
|
+
} else if (!(origin instanceof URL)) {
|
|
1903
|
+
throw new TypeError('The `origin` argument needs to be a string or an URL object');
|
|
1904
|
+
}
|
|
1905
|
+
if (options) {
|
|
1906
|
+
// Validate servername
|
|
1907
|
+
const { servername } = options;
|
|
1908
|
+
const { hostname } = origin;
|
|
1909
|
+
if (servername && hostname !== servername) {
|
|
1910
|
+
throw new Error(`Origin ${hostname} differs from servername ${servername}`);
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
} catch (error) {
|
|
1914
|
+
for(let index = 0; index < listeners.length; index++){
|
|
1915
|
+
listeners[index].reject(error);
|
|
1916
|
+
}
|
|
1917
|
+
return;
|
|
1918
|
+
}
|
|
1919
|
+
const normalizedOptions = this.normalizeOptions(options);
|
|
1920
|
+
const normalizedOrigin = origin.origin;
|
|
1921
|
+
if (normalizedOptions in this.sessions) {
|
|
1922
|
+
const sessions = this.sessions[normalizedOptions];
|
|
1923
|
+
let maxConcurrentStreams = -1;
|
|
1924
|
+
let currentStreamsCount = -1;
|
|
1925
|
+
let optimalSession;
|
|
1926
|
+
// We could just do this.sessions[normalizedOptions].find(...) but that isn't optimal.
|
|
1927
|
+
// Additionally, we are looking for session which has biggest current pending streams count.
|
|
1928
|
+
//
|
|
1929
|
+
// |------------| |------------| |------------| |------------|
|
|
1930
|
+
// | Session: A | | Session: B | | Session: C | | Session: D |
|
|
1931
|
+
// | Pending: 5 |-| Pending: 8 |-| Pending: 9 |-| Pending: 4 |
|
|
1932
|
+
// | Max: 10 | | Max: 10 | | Max: 9 | | Max: 5 |
|
|
1933
|
+
// |------------| |------------| |------------| |------------|
|
|
1934
|
+
// ^
|
|
1935
|
+
// |
|
|
1936
|
+
// pick this one --
|
|
1937
|
+
//
|
|
1938
|
+
for(let index = 0; index < sessions.length; index++){
|
|
1939
|
+
const session = sessions[index];
|
|
1940
|
+
const sessionMaxConcurrentStreams = session.remoteSettings.maxConcurrentStreams;
|
|
1941
|
+
if (sessionMaxConcurrentStreams < maxConcurrentStreams) {
|
|
1942
|
+
break;
|
|
1943
|
+
}
|
|
1944
|
+
if (!session[kOriginSet].includes(normalizedOrigin)) {
|
|
1945
|
+
continue;
|
|
1946
|
+
}
|
|
1947
|
+
const sessionCurrentStreamsCount = session[kCurrentStreamCount];
|
|
1948
|
+
if (sessionCurrentStreamsCount >= sessionMaxConcurrentStreams || session[kGracefullyClosing] || session.destroyed) {
|
|
1949
|
+
continue;
|
|
1950
|
+
}
|
|
1951
|
+
// We only need set this once.
|
|
1952
|
+
if (!optimalSession) {
|
|
1953
|
+
maxConcurrentStreams = sessionMaxConcurrentStreams;
|
|
1954
|
+
}
|
|
1955
|
+
// Either get the session which has biggest current stream count or the lowest.
|
|
1956
|
+
if (this._isBetterSession(sessionCurrentStreamsCount, currentStreamsCount)) {
|
|
1957
|
+
optimalSession = session;
|
|
1958
|
+
currentStreamsCount = sessionCurrentStreamsCount;
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
if (optimalSession) {
|
|
1962
|
+
this._accept(optimalSession, listeners, normalizedOrigin, options);
|
|
1963
|
+
return;
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
if (normalizedOptions in this.queue) {
|
|
1967
|
+
if (normalizedOrigin in this.queue[normalizedOptions]) {
|
|
1968
|
+
// There's already an item in the queue, just attach ourselves to it.
|
|
1969
|
+
this.queue[normalizedOptions][normalizedOrigin].listeners.push(...listeners);
|
|
1970
|
+
return;
|
|
1971
|
+
}
|
|
1972
|
+
} else {
|
|
1973
|
+
this.queue[normalizedOptions] = {
|
|
1974
|
+
[kLength]: 0
|
|
1975
|
+
};
|
|
1976
|
+
}
|
|
1977
|
+
// The entry must be removed from the queue IMMEDIATELY when:
|
|
1978
|
+
// 1. the session connects successfully,
|
|
1979
|
+
// 2. an error occurs.
|
|
1980
|
+
const removeFromQueue = ()=>{
|
|
1981
|
+
// Our entry can be replaced. We cannot remove the new one.
|
|
1982
|
+
if (normalizedOptions in this.queue && this.queue[normalizedOptions][normalizedOrigin] === entry) {
|
|
1983
|
+
delete this.queue[normalizedOptions][normalizedOrigin];
|
|
1984
|
+
if (--this.queue[normalizedOptions][kLength] === 0) {
|
|
1985
|
+
delete this.queue[normalizedOptions];
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
};
|
|
1989
|
+
// The main logic is here
|
|
1990
|
+
const entry = async ()=>{
|
|
1991
|
+
this._sessionCount++;
|
|
1992
|
+
const name = `${normalizedOrigin}:${normalizedOptions}`;
|
|
1993
|
+
let receivedSettings = false;
|
|
1994
|
+
let socket;
|
|
1995
|
+
try {
|
|
1996
|
+
const computedOptions = {
|
|
1997
|
+
...options
|
|
1998
|
+
};
|
|
1999
|
+
if (computedOptions.settings === undefined) {
|
|
2000
|
+
computedOptions.settings = this.settings;
|
|
2001
|
+
}
|
|
2002
|
+
if (computedOptions.session === undefined) {
|
|
2003
|
+
computedOptions.session = this.tlsSessionCache.get(name);
|
|
2004
|
+
}
|
|
2005
|
+
const createConnection = computedOptions.createConnection || this.createConnection;
|
|
2006
|
+
// A hacky workaround to enable async `createConnection`
|
|
2007
|
+
socket = await createConnection.call(this, origin, computedOptions);
|
|
2008
|
+
computedOptions.createConnection = ()=>socket;
|
|
2009
|
+
const session = http2.connect(origin, computedOptions);
|
|
2010
|
+
session[kCurrentStreamCount] = 0;
|
|
2011
|
+
session[kGracefullyClosing] = false;
|
|
2012
|
+
// Node.js return https://false:443 instead of https://1.1.1.1:443
|
|
2013
|
+
const getOriginSet = ()=>{
|
|
2014
|
+
const { socket } = session;
|
|
2015
|
+
let originSet;
|
|
2016
|
+
if (socket.servername === false) {
|
|
2017
|
+
socket.servername = socket.remoteAddress;
|
|
2018
|
+
originSet = session.originSet;
|
|
2019
|
+
socket.servername = false;
|
|
2020
|
+
} else {
|
|
2021
|
+
originSet = session.originSet;
|
|
2022
|
+
}
|
|
2023
|
+
return originSet;
|
|
2024
|
+
};
|
|
2025
|
+
const isFree = ()=>session[kCurrentStreamCount] < session.remoteSettings.maxConcurrentStreams;
|
|
2026
|
+
session.socket.once('session', (tlsSession)=>{
|
|
2027
|
+
this.tlsSessionCache.set(name, tlsSession);
|
|
2028
|
+
});
|
|
2029
|
+
session.once('error', (error)=>{
|
|
2030
|
+
// Listeners are empty when the session successfully connected.
|
|
2031
|
+
for(let index = 0; index < listeners.length; index++){
|
|
2032
|
+
listeners[index].reject(error);
|
|
2033
|
+
}
|
|
2034
|
+
// The connection got broken, purge the cache.
|
|
2035
|
+
this.tlsSessionCache.delete(name);
|
|
2036
|
+
});
|
|
2037
|
+
session.setTimeout(this.timeout, ()=>{
|
|
2038
|
+
// Terminates all streams owned by this session.
|
|
2039
|
+
session.destroy();
|
|
2040
|
+
});
|
|
2041
|
+
session.once('close', ()=>{
|
|
2042
|
+
this._sessionCount--;
|
|
2043
|
+
if (receivedSettings) {
|
|
2044
|
+
// Assumes session `close` is emitted after request `close`
|
|
2045
|
+
this._emptySessionCount--;
|
|
2046
|
+
// This cannot be moved to the stream logic,
|
|
2047
|
+
// because there may be a session that hadn't made a single request.
|
|
2048
|
+
const where = this.sessions[normalizedOptions];
|
|
2049
|
+
if (where.length === 1) {
|
|
2050
|
+
delete this.sessions[normalizedOptions];
|
|
2051
|
+
} else {
|
|
2052
|
+
where.splice(where.indexOf(session), 1);
|
|
2053
|
+
}
|
|
2054
|
+
} else {
|
|
2055
|
+
// Broken connection
|
|
2056
|
+
removeFromQueue();
|
|
2057
|
+
const error = new Error('Session closed without receiving a SETTINGS frame');
|
|
2058
|
+
error.code = 'HTTP2WRAPPER_NOSETTINGS';
|
|
2059
|
+
for(let index = 0; index < listeners.length; index++){
|
|
2060
|
+
listeners[index].reject(error);
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
// There may be another session awaiting.
|
|
2064
|
+
this._processQueue();
|
|
2065
|
+
});
|
|
2066
|
+
// Iterates over the queue and processes listeners.
|
|
2067
|
+
const processListeners = ()=>{
|
|
2068
|
+
const queue = this.queue[normalizedOptions];
|
|
2069
|
+
if (!queue) {
|
|
2070
|
+
return;
|
|
2071
|
+
}
|
|
2072
|
+
const originSet = session[kOriginSet];
|
|
2073
|
+
for(let index = 0; index < originSet.length; index++){
|
|
2074
|
+
const origin = originSet[index];
|
|
2075
|
+
if (origin in queue) {
|
|
2076
|
+
const { listeners, completed } = queue[origin];
|
|
2077
|
+
let index = 0;
|
|
2078
|
+
// Prevents session overloading.
|
|
2079
|
+
while(index < listeners.length && isFree()){
|
|
2080
|
+
// We assume `resolve(...)` calls `request(...)` *directly*,
|
|
2081
|
+
// otherwise the session will get overloaded.
|
|
2082
|
+
listeners[index].resolve(session);
|
|
2083
|
+
index++;
|
|
2084
|
+
}
|
|
2085
|
+
queue[origin].listeners.splice(0, index);
|
|
2086
|
+
if (queue[origin].listeners.length === 0 && !completed) {
|
|
2087
|
+
delete queue[origin];
|
|
2088
|
+
if (--queue[kLength] === 0) {
|
|
2089
|
+
delete this.queue[normalizedOptions];
|
|
2090
|
+
break;
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
// We're no longer free, no point in continuing.
|
|
2094
|
+
if (!isFree()) {
|
|
2095
|
+
break;
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
};
|
|
2100
|
+
// The Origin Set cannot shrink. No need to check if it suddenly became covered by another one.
|
|
2101
|
+
session.on('origin', ()=>{
|
|
2102
|
+
session[kOriginSet] = getOriginSet() || [];
|
|
2103
|
+
session[kGracefullyClosing] = false;
|
|
2104
|
+
closeSessionIfCovered(this.sessions[normalizedOptions], session);
|
|
2105
|
+
if (session[kGracefullyClosing] || !isFree()) {
|
|
2106
|
+
return;
|
|
2107
|
+
}
|
|
2108
|
+
processListeners();
|
|
2109
|
+
if (!isFree()) {
|
|
2110
|
+
return;
|
|
2111
|
+
}
|
|
2112
|
+
// Close covered sessions (if possible).
|
|
2113
|
+
closeCoveredSessions(this.sessions[normalizedOptions], session);
|
|
2114
|
+
});
|
|
2115
|
+
session.once('remoteSettings', ()=>{
|
|
2116
|
+
// The Agent could have been destroyed already.
|
|
2117
|
+
if (entry.destroyed) {
|
|
2118
|
+
const error = new Error('Agent has been destroyed');
|
|
2119
|
+
for(let index = 0; index < listeners.length; index++){
|
|
2120
|
+
listeners[index].reject(error);
|
|
2121
|
+
}
|
|
2122
|
+
session.destroy();
|
|
2123
|
+
return;
|
|
2124
|
+
}
|
|
2125
|
+
// See https://github.com/nodejs/node/issues/38426
|
|
2126
|
+
if (session.setLocalWindowSize) {
|
|
2127
|
+
session.setLocalWindowSize(1024 * 1024 * 4); // 4 MB
|
|
2128
|
+
}
|
|
2129
|
+
session[kOriginSet] = getOriginSet() || [];
|
|
2130
|
+
if (session.socket.encrypted) {
|
|
2131
|
+
const mainOrigin = session[kOriginSet][0];
|
|
2132
|
+
if (mainOrigin !== normalizedOrigin) {
|
|
2133
|
+
const error = new Error(`Requested origin ${normalizedOrigin} does not match server ${mainOrigin}`);
|
|
2134
|
+
for(let index = 0; index < listeners.length; index++){
|
|
2135
|
+
listeners[index].reject(error);
|
|
2136
|
+
}
|
|
2137
|
+
session.destroy();
|
|
2138
|
+
return;
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
removeFromQueue();
|
|
2142
|
+
{
|
|
2143
|
+
const where = this.sessions;
|
|
2144
|
+
if (normalizedOptions in where) {
|
|
2145
|
+
const sessions = where[normalizedOptions];
|
|
2146
|
+
sessions.splice(getSortedIndex(sessions, session, compareSessions), 0, session);
|
|
2147
|
+
} else {
|
|
2148
|
+
where[normalizedOptions] = [
|
|
2149
|
+
session
|
|
2150
|
+
];
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
receivedSettings = true;
|
|
2154
|
+
this._emptySessionCount++;
|
|
2155
|
+
this.emit('session', session);
|
|
2156
|
+
this._accept(session, listeners, normalizedOrigin, options);
|
|
2157
|
+
if (session[kCurrentStreamCount] === 0 && this._emptySessionCount > this.maxEmptySessions) {
|
|
2158
|
+
this.closeEmptySessions(this._emptySessionCount - this.maxEmptySessions);
|
|
2159
|
+
}
|
|
2160
|
+
// `session.remoteSettings.maxConcurrentStreams` might get increased
|
|
2161
|
+
session.on('remoteSettings', ()=>{
|
|
2162
|
+
if (!isFree()) {
|
|
2163
|
+
return;
|
|
2164
|
+
}
|
|
2165
|
+
processListeners();
|
|
2166
|
+
if (!isFree()) {
|
|
2167
|
+
return;
|
|
2168
|
+
}
|
|
2169
|
+
// In case the Origin Set changes
|
|
2170
|
+
closeCoveredSessions(this.sessions[normalizedOptions], session);
|
|
2171
|
+
});
|
|
2172
|
+
});
|
|
2173
|
+
// Shim `session.request()` in order to catch all streams
|
|
2174
|
+
session[kRequest] = session.request;
|
|
2175
|
+
session.request = (headers, streamOptions)=>{
|
|
2176
|
+
if (session[kGracefullyClosing]) {
|
|
2177
|
+
throw new Error('The session is gracefully closing. No new streams are allowed.');
|
|
2178
|
+
}
|
|
2179
|
+
const stream = session[kRequest](headers, streamOptions);
|
|
2180
|
+
// The process won't exit until the session is closed or all requests are gone.
|
|
2181
|
+
session.ref();
|
|
2182
|
+
if (session[kCurrentStreamCount]++ === 0) {
|
|
2183
|
+
this._emptySessionCount--;
|
|
2184
|
+
}
|
|
2185
|
+
stream.once('close', ()=>{
|
|
2186
|
+
if (--session[kCurrentStreamCount] === 0) {
|
|
2187
|
+
this._emptySessionCount++;
|
|
2188
|
+
session.unref();
|
|
2189
|
+
if (this._emptySessionCount > this.maxEmptySessions || session[kGracefullyClosing]) {
|
|
2190
|
+
session.close();
|
|
2191
|
+
return;
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
if (session.destroyed || session.closed) {
|
|
2195
|
+
return;
|
|
2196
|
+
}
|
|
2197
|
+
if (isFree() && !closeSessionIfCovered(this.sessions[normalizedOptions], session)) {
|
|
2198
|
+
closeCoveredSessions(this.sessions[normalizedOptions], session);
|
|
2199
|
+
processListeners();
|
|
2200
|
+
if (session[kCurrentStreamCount] === 0) {
|
|
2201
|
+
this._processQueue();
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
});
|
|
2205
|
+
return stream;
|
|
2206
|
+
};
|
|
2207
|
+
} catch (error) {
|
|
2208
|
+
removeFromQueue();
|
|
2209
|
+
this._sessionCount--;
|
|
2210
|
+
for(let index = 0; index < listeners.length; index++){
|
|
2211
|
+
listeners[index].reject(error);
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
};
|
|
2215
|
+
entry.listeners = listeners;
|
|
2216
|
+
entry.completed = false;
|
|
2217
|
+
entry.destroyed = false;
|
|
2218
|
+
this.queue[normalizedOptions][normalizedOrigin] = entry;
|
|
2219
|
+
this.queue[normalizedOptions][kLength]++;
|
|
2220
|
+
this._processQueue();
|
|
2221
|
+
});
|
|
2222
|
+
}
|
|
2223
|
+
request(origin, options, headers, streamOptions) {
|
|
2224
|
+
return new Promise((resolve, reject)=>{
|
|
2225
|
+
this.getSession(origin, options, [
|
|
2226
|
+
{
|
|
2227
|
+
reject,
|
|
2228
|
+
resolve: (session)=>{
|
|
2229
|
+
try {
|
|
2230
|
+
const stream = session.request(headers, streamOptions);
|
|
2231
|
+
// Do not throw before `request(...)` has been awaited
|
|
2232
|
+
delayAsyncDestroy(stream);
|
|
2233
|
+
resolve(stream);
|
|
2234
|
+
} catch (error) {
|
|
2235
|
+
reject(error);
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
}
|
|
2239
|
+
]);
|
|
2240
|
+
});
|
|
2241
|
+
}
|
|
2242
|
+
async createConnection(origin, options) {
|
|
2243
|
+
return Agent.connect(origin, options);
|
|
2244
|
+
}
|
|
2245
|
+
static connect(origin, options) {
|
|
2246
|
+
options.ALPNProtocols = [
|
|
2247
|
+
'h2'
|
|
2248
|
+
];
|
|
2249
|
+
const port = origin.port || 443;
|
|
2250
|
+
const host = origin.hostname;
|
|
2251
|
+
if (typeof options.servername === 'undefined') {
|
|
2252
|
+
options.servername = host;
|
|
2253
|
+
}
|
|
2254
|
+
const socket = tls.connect(port, host, options);
|
|
2255
|
+
if (options.socket) {
|
|
2256
|
+
socket._peername = {
|
|
2257
|
+
family: undefined,
|
|
2258
|
+
address: undefined,
|
|
2259
|
+
port
|
|
2260
|
+
};
|
|
2261
|
+
}
|
|
2262
|
+
return socket;
|
|
2263
|
+
}
|
|
2264
|
+
closeEmptySessions(maxCount = Number.POSITIVE_INFINITY) {
|
|
2265
|
+
let closedCount = 0;
|
|
2266
|
+
const { sessions } = this;
|
|
2267
|
+
// eslint-disable-next-line guard-for-in
|
|
2268
|
+
for(const key in sessions){
|
|
2269
|
+
const thisSessions = sessions[key];
|
|
2270
|
+
for(let index = 0; index < thisSessions.length; index++){
|
|
2271
|
+
const session = thisSessions[index];
|
|
2272
|
+
if (session[kCurrentStreamCount] === 0) {
|
|
2273
|
+
closedCount++;
|
|
2274
|
+
session.close();
|
|
2275
|
+
if (closedCount >= maxCount) {
|
|
2276
|
+
return closedCount;
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
2280
|
+
}
|
|
2281
|
+
return closedCount;
|
|
2282
|
+
}
|
|
2283
|
+
destroy(reason) {
|
|
2284
|
+
const { sessions, queue } = this;
|
|
2285
|
+
// eslint-disable-next-line guard-for-in
|
|
2286
|
+
for(const key in sessions){
|
|
2287
|
+
const thisSessions = sessions[key];
|
|
2288
|
+
for(let index = 0; index < thisSessions.length; index++){
|
|
2289
|
+
thisSessions[index].destroy(reason);
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2292
|
+
// eslint-disable-next-line guard-for-in
|
|
2293
|
+
for(const normalizedOptions in queue){
|
|
2294
|
+
const entries = queue[normalizedOptions];
|
|
2295
|
+
// eslint-disable-next-line guard-for-in
|
|
2296
|
+
for(const normalizedOrigin in entries){
|
|
2297
|
+
entries[normalizedOrigin].destroyed = true;
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
// New requests should NOT attach to destroyed sessions
|
|
2301
|
+
this.queue = {};
|
|
2302
|
+
this.tlsSessionCache.clear();
|
|
2303
|
+
}
|
|
2304
|
+
get emptySessionCount() {
|
|
2305
|
+
return this._emptySessionCount;
|
|
2306
|
+
}
|
|
2307
|
+
get pendingSessionCount() {
|
|
2308
|
+
return this._sessionCount - this._emptySessionCount;
|
|
2309
|
+
}
|
|
2310
|
+
get sessionCount() {
|
|
2311
|
+
return this._sessionCount;
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2314
|
+
Agent.kCurrentStreamCount = kCurrentStreamCount;
|
|
2315
|
+
Agent.kGracefullyClosing = kGracefullyClosing;
|
|
2316
|
+
agent = {
|
|
2317
|
+
Agent,
|
|
2318
|
+
globalAgent: new Agent()
|
|
2319
|
+
};
|
|
2320
|
+
return agent;
|
|
2321
|
+
}
|
|
2322
|
+
|
|
2323
|
+
var incomingMessage;
|
|
2324
|
+
var hasRequiredIncomingMessage;
|
|
2325
|
+
|
|
2326
|
+
function requireIncomingMessage () {
|
|
2327
|
+
if (hasRequiredIncomingMessage) return incomingMessage;
|
|
2328
|
+
hasRequiredIncomingMessage = 1;
|
|
2329
|
+
const { Readable } = require$$0__default$6.default;
|
|
2330
|
+
class IncomingMessage extends Readable {
|
|
2331
|
+
constructor(socket, highWaterMark){
|
|
2332
|
+
super({
|
|
2333
|
+
emitClose: false,
|
|
2334
|
+
autoDestroy: true,
|
|
2335
|
+
highWaterMark
|
|
2336
|
+
});
|
|
2337
|
+
this.statusCode = null;
|
|
2338
|
+
this.statusMessage = '';
|
|
2339
|
+
this.httpVersion = '2.0';
|
|
2340
|
+
this.httpVersionMajor = 2;
|
|
2341
|
+
this.httpVersionMinor = 0;
|
|
2342
|
+
this.headers = {};
|
|
2343
|
+
this.trailers = {};
|
|
2344
|
+
this.req = null;
|
|
2345
|
+
this.aborted = false;
|
|
2346
|
+
this.complete = false;
|
|
2347
|
+
this.upgrade = null;
|
|
2348
|
+
this.rawHeaders = [];
|
|
2349
|
+
this.rawTrailers = [];
|
|
2350
|
+
this.socket = socket;
|
|
2351
|
+
this._dumped = false;
|
|
2352
|
+
}
|
|
2353
|
+
get connection() {
|
|
2354
|
+
return this.socket;
|
|
2355
|
+
}
|
|
2356
|
+
set connection(value) {
|
|
2357
|
+
this.socket = value;
|
|
2358
|
+
}
|
|
2359
|
+
_destroy(error, callback) {
|
|
2360
|
+
if (!this.readableEnded) {
|
|
2361
|
+
this.aborted = true;
|
|
2362
|
+
}
|
|
2363
|
+
// See https://github.com/nodejs/node/issues/35303
|
|
2364
|
+
callback();
|
|
2365
|
+
this.req._request.destroy(error);
|
|
2366
|
+
}
|
|
2367
|
+
setTimeout(ms, callback) {
|
|
2368
|
+
this.req.setTimeout(ms, callback);
|
|
2369
|
+
return this;
|
|
2370
|
+
}
|
|
2371
|
+
_dump() {
|
|
2372
|
+
if (!this._dumped) {
|
|
2373
|
+
this._dumped = true;
|
|
2374
|
+
this.removeAllListeners('data');
|
|
2375
|
+
this.resume();
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
_read() {
|
|
2379
|
+
if (this.req) {
|
|
2380
|
+
this.req._request.resume();
|
|
2381
|
+
}
|
|
2382
|
+
}
|
|
2383
|
+
}
|
|
2384
|
+
incomingMessage = IncomingMessage;
|
|
2385
|
+
return incomingMessage;
|
|
2386
|
+
}
|
|
2387
|
+
|
|
2388
|
+
var proxyEvents;
|
|
2389
|
+
var hasRequiredProxyEvents;
|
|
2390
|
+
|
|
2391
|
+
function requireProxyEvents () {
|
|
2392
|
+
if (hasRequiredProxyEvents) return proxyEvents;
|
|
2393
|
+
hasRequiredProxyEvents = 1;
|
|
2394
|
+
proxyEvents = (from, to, events)=>{
|
|
2395
|
+
for (const event of events){
|
|
2396
|
+
from.on(event, (...args)=>to.emit(event, ...args));
|
|
2397
|
+
}
|
|
2398
|
+
};
|
|
2399
|
+
return proxyEvents;
|
|
2400
|
+
}
|
|
2401
|
+
|
|
2402
|
+
var errors$1 = {exports: {}};
|
|
2403
|
+
|
|
2404
|
+
var hasRequiredErrors;
|
|
2405
|
+
|
|
2406
|
+
function requireErrors () {
|
|
2407
|
+
if (hasRequiredErrors) return errors$1.exports;
|
|
2408
|
+
hasRequiredErrors = 1;
|
|
2409
|
+
(function (module) {
|
|
2410
|
+
/* istanbul ignore file: https://github.com/nodejs/node/blob/master/lib/internal/errors.js */ const makeError = (Base, key, getMessage)=>{
|
|
2411
|
+
module.exports[key] = class NodeError extends Base {
|
|
2412
|
+
constructor(...args){
|
|
2413
|
+
super(typeof getMessage === 'string' ? getMessage : getMessage(args));
|
|
2414
|
+
this.name = `${super.name} [${key}]`;
|
|
2415
|
+
this.code = key;
|
|
2416
|
+
}
|
|
2417
|
+
};
|
|
2418
|
+
};
|
|
2419
|
+
makeError(TypeError, 'ERR_INVALID_ARG_TYPE', (args)=>{
|
|
2420
|
+
const type = args[0].includes('.') ? 'property' : 'argument';
|
|
2421
|
+
let valid = args[1];
|
|
2422
|
+
const isManyTypes = Array.isArray(valid);
|
|
2423
|
+
if (isManyTypes) {
|
|
2424
|
+
valid = `${valid.slice(0, -1).join(', ')} or ${valid.slice(-1)}`;
|
|
2425
|
+
}
|
|
2426
|
+
return `The "${args[0]}" ${type} must be ${isManyTypes ? 'one of' : 'of'} type ${valid}. Received ${typeof args[2]}`;
|
|
2427
|
+
});
|
|
2428
|
+
makeError(TypeError, 'ERR_INVALID_PROTOCOL', (args)=>`Protocol "${args[0]}" not supported. Expected "${args[1]}"`);
|
|
2429
|
+
makeError(Error, 'ERR_HTTP_HEADERS_SENT', (args)=>`Cannot ${args[0]} headers after they are sent to the client`);
|
|
2430
|
+
makeError(TypeError, 'ERR_INVALID_HTTP_TOKEN', (args)=>`${args[0]} must be a valid HTTP token [${args[1]}]`);
|
|
2431
|
+
makeError(TypeError, 'ERR_HTTP_INVALID_HEADER_VALUE', (args)=>`Invalid value "${args[0]} for header "${args[1]}"`);
|
|
2432
|
+
makeError(TypeError, 'ERR_INVALID_CHAR', (args)=>`Invalid character in ${args[0]} [${args[1]}]`);
|
|
2433
|
+
makeError(Error, 'ERR_HTTP2_NO_SOCKET_MANIPULATION', 'HTTP/2 sockets should not be directly manipulated (e.g. read and written)');
|
|
2434
|
+
} (errors$1));
|
|
2435
|
+
return errors$1.exports;
|
|
2436
|
+
}
|
|
2437
|
+
|
|
2438
|
+
var isRequestPseudoHeader;
|
|
2439
|
+
var hasRequiredIsRequestPseudoHeader;
|
|
2440
|
+
|
|
2441
|
+
function requireIsRequestPseudoHeader () {
|
|
2442
|
+
if (hasRequiredIsRequestPseudoHeader) return isRequestPseudoHeader;
|
|
2443
|
+
hasRequiredIsRequestPseudoHeader = 1;
|
|
2444
|
+
isRequestPseudoHeader = (header)=>{
|
|
2445
|
+
switch(header){
|
|
2446
|
+
case ':method':
|
|
2447
|
+
case ':scheme':
|
|
2448
|
+
case ':authority':
|
|
2449
|
+
case ':path':
|
|
2450
|
+
return true;
|
|
2451
|
+
default:
|
|
2452
|
+
return false;
|
|
2453
|
+
}
|
|
2454
|
+
};
|
|
2455
|
+
return isRequestPseudoHeader;
|
|
2456
|
+
}
|
|
2457
|
+
|
|
2458
|
+
var validateHeaderName;
|
|
2459
|
+
var hasRequiredValidateHeaderName;
|
|
2460
|
+
|
|
2461
|
+
function requireValidateHeaderName () {
|
|
2462
|
+
if (hasRequiredValidateHeaderName) return validateHeaderName;
|
|
2463
|
+
hasRequiredValidateHeaderName = 1;
|
|
2464
|
+
const { ERR_INVALID_HTTP_TOKEN } = requireErrors();
|
|
2465
|
+
const isRequestPseudoHeader = requireIsRequestPseudoHeader();
|
|
2466
|
+
const isValidHttpToken = /^[\^`\-\w!#$%&*+.|~]+$/;
|
|
2467
|
+
validateHeaderName = (name)=>{
|
|
2468
|
+
if (typeof name !== 'string' || !isValidHttpToken.test(name) && !isRequestPseudoHeader(name)) {
|
|
2469
|
+
throw new ERR_INVALID_HTTP_TOKEN('Header name', name);
|
|
2470
|
+
}
|
|
2471
|
+
};
|
|
2472
|
+
return validateHeaderName;
|
|
2473
|
+
}
|
|
2474
|
+
|
|
2475
|
+
var validateHeaderValue;
|
|
2476
|
+
var hasRequiredValidateHeaderValue;
|
|
2477
|
+
|
|
2478
|
+
function requireValidateHeaderValue () {
|
|
2479
|
+
if (hasRequiredValidateHeaderValue) return validateHeaderValue;
|
|
2480
|
+
hasRequiredValidateHeaderValue = 1;
|
|
2481
|
+
const { ERR_HTTP_INVALID_HEADER_VALUE, ERR_INVALID_CHAR } = requireErrors();
|
|
2482
|
+
const isInvalidHeaderValue = /[^\t\u0020-\u007E\u0080-\u00FF]/;
|
|
2483
|
+
validateHeaderValue = (name, value)=>{
|
|
2484
|
+
if (typeof value === 'undefined') {
|
|
2485
|
+
throw new ERR_HTTP_INVALID_HEADER_VALUE(value, name);
|
|
2486
|
+
}
|
|
2487
|
+
if (isInvalidHeaderValue.test(value)) {
|
|
2488
|
+
throw new ERR_INVALID_CHAR('header content', name);
|
|
2489
|
+
}
|
|
2490
|
+
};
|
|
2491
|
+
return validateHeaderValue;
|
|
2492
|
+
}
|
|
2493
|
+
|
|
2494
|
+
var proxySocketHandler_1;
|
|
2495
|
+
var hasRequiredProxySocketHandler;
|
|
2496
|
+
|
|
2497
|
+
function requireProxySocketHandler () {
|
|
2498
|
+
if (hasRequiredProxySocketHandler) return proxySocketHandler_1;
|
|
2499
|
+
hasRequiredProxySocketHandler = 1;
|
|
2500
|
+
const { ERR_HTTP2_NO_SOCKET_MANIPULATION } = requireErrors();
|
|
2501
|
+
/* istanbul ignore file */ /* https://github.com/nodejs/node/blob/6eec858f34a40ffa489c1ec54bb24da72a28c781/lib/internal/http2/compat.js#L195-L272 */ const proxySocketHandler = {
|
|
2502
|
+
has (stream, property) {
|
|
2503
|
+
// Replaced [kSocket] with .socket
|
|
2504
|
+
const reference = stream.session === undefined ? stream : stream.session.socket;
|
|
2505
|
+
return property in stream || property in reference;
|
|
2506
|
+
},
|
|
2507
|
+
get (stream, property) {
|
|
2508
|
+
switch(property){
|
|
2509
|
+
case 'on':
|
|
2510
|
+
case 'once':
|
|
2511
|
+
case 'end':
|
|
2512
|
+
case 'emit':
|
|
2513
|
+
case 'destroy':
|
|
2514
|
+
return stream[property].bind(stream);
|
|
2515
|
+
case 'writable':
|
|
2516
|
+
case 'destroyed':
|
|
2517
|
+
return stream[property];
|
|
2518
|
+
case 'readable':
|
|
2519
|
+
if (stream.destroyed) {
|
|
2520
|
+
return false;
|
|
2521
|
+
}
|
|
2522
|
+
return stream.readable;
|
|
2523
|
+
case 'setTimeout':
|
|
2524
|
+
{
|
|
2525
|
+
const { session } = stream;
|
|
2526
|
+
if (session !== undefined) {
|
|
2527
|
+
return session.setTimeout.bind(session);
|
|
2528
|
+
}
|
|
2529
|
+
return stream.setTimeout.bind(stream);
|
|
2530
|
+
}
|
|
2531
|
+
case 'write':
|
|
2532
|
+
case 'read':
|
|
2533
|
+
case 'pause':
|
|
2534
|
+
case 'resume':
|
|
2535
|
+
throw new ERR_HTTP2_NO_SOCKET_MANIPULATION();
|
|
2536
|
+
default:
|
|
2537
|
+
{
|
|
2538
|
+
// Replaced [kSocket] with .socket
|
|
2539
|
+
const reference = stream.session === undefined ? stream : stream.session.socket;
|
|
2540
|
+
const value = reference[property];
|
|
2541
|
+
return typeof value === 'function' ? value.bind(reference) : value;
|
|
2542
|
+
}
|
|
2543
|
+
}
|
|
2544
|
+
},
|
|
2545
|
+
getPrototypeOf (stream) {
|
|
2546
|
+
if (stream.session !== undefined) {
|
|
2547
|
+
// Replaced [kSocket] with .socket
|
|
2548
|
+
return Reflect.getPrototypeOf(stream.session.socket);
|
|
2549
|
+
}
|
|
2550
|
+
return Reflect.getPrototypeOf(stream);
|
|
2551
|
+
},
|
|
2552
|
+
set (stream, property, value) {
|
|
2553
|
+
switch(property){
|
|
2554
|
+
case 'writable':
|
|
2555
|
+
case 'readable':
|
|
2556
|
+
case 'destroyed':
|
|
2557
|
+
case 'on':
|
|
2558
|
+
case 'once':
|
|
2559
|
+
case 'end':
|
|
2560
|
+
case 'emit':
|
|
2561
|
+
case 'destroy':
|
|
2562
|
+
stream[property] = value;
|
|
2563
|
+
return true;
|
|
2564
|
+
case 'setTimeout':
|
|
2565
|
+
{
|
|
2566
|
+
const { session } = stream;
|
|
2567
|
+
if (session === undefined) {
|
|
2568
|
+
stream.setTimeout = value;
|
|
2569
|
+
} else {
|
|
2570
|
+
session.setTimeout = value;
|
|
2571
|
+
}
|
|
2572
|
+
return true;
|
|
2573
|
+
}
|
|
2574
|
+
case 'write':
|
|
2575
|
+
case 'read':
|
|
2576
|
+
case 'pause':
|
|
2577
|
+
case 'resume':
|
|
2578
|
+
throw new ERR_HTTP2_NO_SOCKET_MANIPULATION();
|
|
2579
|
+
default:
|
|
2580
|
+
{
|
|
2581
|
+
// Replaced [kSocket] with .socket
|
|
2582
|
+
const reference = stream.session === undefined ? stream : stream.session.socket;
|
|
2583
|
+
reference[property] = value;
|
|
2584
|
+
return true;
|
|
2585
|
+
}
|
|
2586
|
+
}
|
|
2587
|
+
}
|
|
2588
|
+
};
|
|
2589
|
+
proxySocketHandler_1 = proxySocketHandler;
|
|
2590
|
+
return proxySocketHandler_1;
|
|
2591
|
+
}
|
|
2592
|
+
|
|
2593
|
+
var clientRequest;
|
|
2594
|
+
var hasRequiredClientRequest;
|
|
2595
|
+
|
|
2596
|
+
function requireClientRequest () {
|
|
2597
|
+
if (hasRequiredClientRequest) return clientRequest;
|
|
2598
|
+
hasRequiredClientRequest = 1;
|
|
2599
|
+
// See https://github.com/facebook/jest/issues/2549
|
|
2600
|
+
// eslint-disable-next-line node/prefer-global/url
|
|
2601
|
+
const { URL, urlToHttpOptions } = require$$0__default$3.default;
|
|
2602
|
+
const http2 = require$$3__default.default;
|
|
2603
|
+
const { Writable } = require$$0__default$6.default;
|
|
2604
|
+
const { Agent, globalAgent } = requireAgent();
|
|
2605
|
+
const IncomingMessage = requireIncomingMessage();
|
|
2606
|
+
const proxyEvents = requireProxyEvents();
|
|
2607
|
+
const { ERR_INVALID_ARG_TYPE, ERR_INVALID_PROTOCOL, ERR_HTTP_HEADERS_SENT } = requireErrors();
|
|
2608
|
+
const validateHeaderName = requireValidateHeaderName();
|
|
2609
|
+
const validateHeaderValue = requireValidateHeaderValue();
|
|
2610
|
+
const proxySocketHandler = requireProxySocketHandler();
|
|
2611
|
+
const { HTTP2_HEADER_STATUS, HTTP2_HEADER_METHOD, HTTP2_HEADER_PATH, HTTP2_HEADER_AUTHORITY, HTTP2_METHOD_CONNECT } = http2.constants;
|
|
2612
|
+
const kHeaders = Symbol('headers');
|
|
2613
|
+
const kOrigin = Symbol('origin');
|
|
2614
|
+
const kSession = Symbol('session');
|
|
2615
|
+
const kOptions = Symbol('options');
|
|
2616
|
+
const kFlushedHeaders = Symbol('flushedHeaders');
|
|
2617
|
+
const kJobs = Symbol('jobs');
|
|
2618
|
+
const kPendingAgentPromise = Symbol('pendingAgentPromise');
|
|
2619
|
+
class ClientRequest extends Writable {
|
|
2620
|
+
constructor(input, options, callback){
|
|
2621
|
+
super({
|
|
2622
|
+
autoDestroy: false,
|
|
2623
|
+
emitClose: false
|
|
2624
|
+
});
|
|
2625
|
+
if (typeof input === 'string') {
|
|
2626
|
+
input = urlToHttpOptions(new URL(input));
|
|
2627
|
+
} else if (input instanceof URL) {
|
|
2628
|
+
input = urlToHttpOptions(input);
|
|
2629
|
+
} else {
|
|
2630
|
+
input = {
|
|
2631
|
+
...input
|
|
2632
|
+
};
|
|
2633
|
+
}
|
|
2634
|
+
if (typeof options === 'function' || options === undefined) {
|
|
2635
|
+
// (options, callback)
|
|
2636
|
+
callback = options;
|
|
2637
|
+
options = input;
|
|
2638
|
+
} else {
|
|
2639
|
+
// (input, options, callback)
|
|
2640
|
+
options = Object.assign(input, options);
|
|
2641
|
+
}
|
|
2642
|
+
if (options.h2session) {
|
|
2643
|
+
this[kSession] = options.h2session;
|
|
2644
|
+
if (this[kSession].destroyed) {
|
|
2645
|
+
throw new Error('The session has been closed already');
|
|
2646
|
+
}
|
|
2647
|
+
this.protocol = this[kSession].socket.encrypted ? 'https:' : 'http:';
|
|
2648
|
+
} else if (options.agent === false) {
|
|
2649
|
+
this.agent = new Agent({
|
|
2650
|
+
maxEmptySessions: 0
|
|
2651
|
+
});
|
|
2652
|
+
} else if (typeof options.agent === 'undefined' || options.agent === null) {
|
|
2653
|
+
this.agent = globalAgent;
|
|
2654
|
+
} else if (typeof options.agent.request === 'function') {
|
|
2655
|
+
this.agent = options.agent;
|
|
2656
|
+
} else {
|
|
2657
|
+
throw new ERR_INVALID_ARG_TYPE('options.agent', [
|
|
2658
|
+
'http2wrapper.Agent-like Object',
|
|
2659
|
+
'undefined',
|
|
2660
|
+
'false'
|
|
2661
|
+
], options.agent);
|
|
2662
|
+
}
|
|
2663
|
+
if (this.agent) {
|
|
2664
|
+
this.protocol = this.agent.protocol;
|
|
2665
|
+
}
|
|
2666
|
+
if (options.protocol && options.protocol !== this.protocol) {
|
|
2667
|
+
throw new ERR_INVALID_PROTOCOL(options.protocol, this.protocol);
|
|
2668
|
+
}
|
|
2669
|
+
if (!options.port) {
|
|
2670
|
+
options.port = options.defaultPort || this.agent && this.agent.defaultPort || 443;
|
|
2671
|
+
}
|
|
2672
|
+
options.host = options.hostname || options.host || 'localhost';
|
|
2673
|
+
// Unused
|
|
2674
|
+
delete options.hostname;
|
|
2675
|
+
const { timeout } = options;
|
|
2676
|
+
options.timeout = undefined;
|
|
2677
|
+
this[kHeaders] = Object.create(null);
|
|
2678
|
+
this[kJobs] = [];
|
|
2679
|
+
this[kPendingAgentPromise] = undefined;
|
|
2680
|
+
this.socket = null;
|
|
2681
|
+
this.connection = null;
|
|
2682
|
+
this.method = options.method || 'GET';
|
|
2683
|
+
if (!(this.method === 'CONNECT' && (options.path === '/' || options.path === undefined))) {
|
|
2684
|
+
this.path = options.path;
|
|
2685
|
+
}
|
|
2686
|
+
this.res = null;
|
|
2687
|
+
this.aborted = false;
|
|
2688
|
+
this.reusedSocket = false;
|
|
2689
|
+
const { headers } = options;
|
|
2690
|
+
if (headers) {
|
|
2691
|
+
// eslint-disable-next-line guard-for-in
|
|
2692
|
+
for(const header in headers){
|
|
2693
|
+
this.setHeader(header, headers[header]);
|
|
2694
|
+
}
|
|
2695
|
+
}
|
|
2696
|
+
if (options.auth && !('authorization' in this[kHeaders])) {
|
|
2697
|
+
this[kHeaders].authorization = 'Basic ' + Buffer.from(options.auth).toString('base64');
|
|
2698
|
+
}
|
|
2699
|
+
options.session = options.tlsSession;
|
|
2700
|
+
options.path = options.socketPath;
|
|
2701
|
+
this[kOptions] = options;
|
|
2702
|
+
// Clients that generate HTTP/2 requests directly SHOULD use the :authority pseudo-header field instead of the Host header field.
|
|
2703
|
+
this[kOrigin] = new URL(`${this.protocol}//${options.servername || options.host}:${options.port}`);
|
|
2704
|
+
// A socket is being reused
|
|
2705
|
+
const reuseSocket = options._reuseSocket;
|
|
2706
|
+
if (reuseSocket) {
|
|
2707
|
+
options.createConnection = (...args)=>{
|
|
2708
|
+
if (reuseSocket.destroyed) {
|
|
2709
|
+
return this.agent.createConnection(...args);
|
|
2710
|
+
}
|
|
2711
|
+
return reuseSocket;
|
|
2712
|
+
};
|
|
2713
|
+
// eslint-disable-next-line promise/prefer-await-to-then
|
|
2714
|
+
this.agent.getSession(this[kOrigin], this[kOptions]).catch(()=>{});
|
|
2715
|
+
}
|
|
2716
|
+
if (timeout) {
|
|
2717
|
+
this.setTimeout(timeout);
|
|
2718
|
+
}
|
|
2719
|
+
if (callback) {
|
|
2720
|
+
this.once('response', callback);
|
|
2721
|
+
}
|
|
2722
|
+
this[kFlushedHeaders] = false;
|
|
2723
|
+
}
|
|
2724
|
+
get method() {
|
|
2725
|
+
return this[kHeaders][HTTP2_HEADER_METHOD];
|
|
2726
|
+
}
|
|
2727
|
+
set method(value) {
|
|
2728
|
+
if (value) {
|
|
2729
|
+
this[kHeaders][HTTP2_HEADER_METHOD] = value.toUpperCase();
|
|
2730
|
+
}
|
|
2731
|
+
}
|
|
2732
|
+
get path() {
|
|
2733
|
+
const header = this.method === 'CONNECT' ? HTTP2_HEADER_AUTHORITY : HTTP2_HEADER_PATH;
|
|
2734
|
+
return this[kHeaders][header];
|
|
2735
|
+
}
|
|
2736
|
+
set path(value) {
|
|
2737
|
+
if (value) {
|
|
2738
|
+
const header = this.method === 'CONNECT' ? HTTP2_HEADER_AUTHORITY : HTTP2_HEADER_PATH;
|
|
2739
|
+
this[kHeaders][header] = value;
|
|
2740
|
+
}
|
|
2741
|
+
}
|
|
2742
|
+
get host() {
|
|
2743
|
+
return this[kOrigin].hostname;
|
|
2744
|
+
}
|
|
2745
|
+
set host(_value) {
|
|
2746
|
+
// Do nothing as this is read only.
|
|
2747
|
+
}
|
|
2748
|
+
get _mustNotHaveABody() {
|
|
2749
|
+
return this.method === 'GET' || this.method === 'HEAD' || this.method === 'DELETE';
|
|
2750
|
+
}
|
|
2751
|
+
_write(chunk, encoding, callback) {
|
|
2752
|
+
// https://github.com/nodejs/node/blob/654df09ae0c5e17d1b52a900a545f0664d8c7627/lib/internal/http2/util.js#L148-L156
|
|
2753
|
+
if (this._mustNotHaveABody) {
|
|
2754
|
+
callback(new Error('The GET, HEAD and DELETE methods must NOT have a body'));
|
|
2755
|
+
/* istanbul ignore next: Node.js 12 throws directly */ return;
|
|
2756
|
+
}
|
|
2757
|
+
this.flushHeaders();
|
|
2758
|
+
const callWrite = ()=>this._request.write(chunk, encoding, callback);
|
|
2759
|
+
if (this._request) {
|
|
2760
|
+
callWrite();
|
|
2761
|
+
} else {
|
|
2762
|
+
this[kJobs].push(callWrite);
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
_final(callback) {
|
|
2766
|
+
this.flushHeaders();
|
|
2767
|
+
const callEnd = ()=>{
|
|
2768
|
+
// For GET, HEAD and DELETE and CONNECT
|
|
2769
|
+
if (this._mustNotHaveABody || this.method === 'CONNECT') {
|
|
2770
|
+
callback();
|
|
2771
|
+
return;
|
|
2772
|
+
}
|
|
2773
|
+
this._request.end(callback);
|
|
2774
|
+
};
|
|
2775
|
+
if (this._request) {
|
|
2776
|
+
callEnd();
|
|
2777
|
+
} else {
|
|
2778
|
+
this[kJobs].push(callEnd);
|
|
2779
|
+
}
|
|
2780
|
+
}
|
|
2781
|
+
abort() {
|
|
2782
|
+
if (this.res && this.res.complete) {
|
|
2783
|
+
return;
|
|
2784
|
+
}
|
|
2785
|
+
if (!this.aborted) {
|
|
2786
|
+
process.nextTick(()=>this.emit('abort'));
|
|
2787
|
+
}
|
|
2788
|
+
this.aborted = true;
|
|
2789
|
+
this.destroy();
|
|
2790
|
+
}
|
|
2791
|
+
async _destroy(error, callback) {
|
|
2792
|
+
if (this.res) {
|
|
2793
|
+
this.res._dump();
|
|
2794
|
+
}
|
|
2795
|
+
if (this._request) {
|
|
2796
|
+
this._request.destroy();
|
|
2797
|
+
} else {
|
|
2798
|
+
process.nextTick(()=>{
|
|
2799
|
+
this.emit('close');
|
|
2800
|
+
});
|
|
2801
|
+
}
|
|
2802
|
+
try {
|
|
2803
|
+
await this[kPendingAgentPromise];
|
|
2804
|
+
} catch (internalError) {
|
|
2805
|
+
if (this.aborted) {
|
|
2806
|
+
error = internalError;
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2809
|
+
callback(error);
|
|
2810
|
+
}
|
|
2811
|
+
async flushHeaders() {
|
|
2812
|
+
if (this[kFlushedHeaders] || this.destroyed) {
|
|
2813
|
+
return;
|
|
2814
|
+
}
|
|
2815
|
+
this[kFlushedHeaders] = true;
|
|
2816
|
+
const isConnectMethod = this.method === HTTP2_METHOD_CONNECT;
|
|
2817
|
+
// The real magic is here
|
|
2818
|
+
const onStream = (stream)=>{
|
|
2819
|
+
this._request = stream;
|
|
2820
|
+
if (this.destroyed) {
|
|
2821
|
+
stream.destroy();
|
|
2822
|
+
return;
|
|
2823
|
+
}
|
|
2824
|
+
// Forwards `timeout`, `continue`, `close` and `error` events to this instance.
|
|
2825
|
+
if (!isConnectMethod) {
|
|
2826
|
+
// TODO: Should we proxy `close` here?
|
|
2827
|
+
proxyEvents(stream, this, [
|
|
2828
|
+
'timeout',
|
|
2829
|
+
'continue'
|
|
2830
|
+
]);
|
|
2831
|
+
}
|
|
2832
|
+
stream.once('error', (error)=>{
|
|
2833
|
+
this.destroy(error);
|
|
2834
|
+
});
|
|
2835
|
+
stream.once('aborted', ()=>{
|
|
2836
|
+
const { res } = this;
|
|
2837
|
+
if (res) {
|
|
2838
|
+
res.aborted = true;
|
|
2839
|
+
res.emit('aborted');
|
|
2840
|
+
res.destroy();
|
|
2841
|
+
} else {
|
|
2842
|
+
this.destroy(new Error('The server aborted the HTTP/2 stream'));
|
|
2843
|
+
}
|
|
2844
|
+
});
|
|
2845
|
+
const onResponse = (headers, flags, rawHeaders)=>{
|
|
2846
|
+
// If we were to emit raw request stream, it would be as fast as the native approach.
|
|
2847
|
+
// Note that wrapping the raw stream in a Proxy instance won't improve the performance (already tested it).
|
|
2848
|
+
const response = new IncomingMessage(this.socket, stream.readableHighWaterMark);
|
|
2849
|
+
this.res = response;
|
|
2850
|
+
// Undocumented, but it is used by `cacheable-request`
|
|
2851
|
+
response.url = `${this[kOrigin].origin}${this.path}`;
|
|
2852
|
+
response.req = this;
|
|
2853
|
+
response.statusCode = headers[HTTP2_HEADER_STATUS];
|
|
2854
|
+
response.headers = headers;
|
|
2855
|
+
response.rawHeaders = rawHeaders;
|
|
2856
|
+
response.once('end', ()=>{
|
|
2857
|
+
response.complete = true;
|
|
2858
|
+
// Has no effect, just be consistent with the Node.js behavior
|
|
2859
|
+
response.socket = null;
|
|
2860
|
+
response.connection = null;
|
|
2861
|
+
});
|
|
2862
|
+
if (isConnectMethod) {
|
|
2863
|
+
response.upgrade = true;
|
|
2864
|
+
// The HTTP1 API says the socket is detached here,
|
|
2865
|
+
// but we can't do that so we pass the original HTTP2 request.
|
|
2866
|
+
if (this.emit('connect', response, stream, Buffer.alloc(0))) {
|
|
2867
|
+
this.emit('close');
|
|
2868
|
+
} else {
|
|
2869
|
+
// No listeners attached, destroy the original request.
|
|
2870
|
+
stream.destroy();
|
|
2871
|
+
}
|
|
2872
|
+
} else {
|
|
2873
|
+
// Forwards data
|
|
2874
|
+
stream.on('data', (chunk)=>{
|
|
2875
|
+
if (!response._dumped && !response.push(chunk)) {
|
|
2876
|
+
stream.pause();
|
|
2877
|
+
}
|
|
2878
|
+
});
|
|
2879
|
+
stream.once('end', ()=>{
|
|
2880
|
+
if (!this.aborted) {
|
|
2881
|
+
response.push(null);
|
|
2882
|
+
}
|
|
2883
|
+
});
|
|
2884
|
+
if (!this.emit('response', response)) {
|
|
2885
|
+
// No listeners attached, dump the response.
|
|
2886
|
+
response._dump();
|
|
2887
|
+
}
|
|
2888
|
+
}
|
|
2889
|
+
};
|
|
2890
|
+
// This event tells we are ready to listen for the data.
|
|
2891
|
+
stream.once('response', onResponse);
|
|
2892
|
+
// Emits `information` event
|
|
2893
|
+
stream.once('headers', (headers)=>this.emit('information', {
|
|
2894
|
+
statusCode: headers[HTTP2_HEADER_STATUS]
|
|
2895
|
+
}));
|
|
2896
|
+
stream.once('trailers', (trailers, flags, rawTrailers)=>{
|
|
2897
|
+
const { res } = this;
|
|
2898
|
+
// https://github.com/nodejs/node/issues/41251
|
|
2899
|
+
if (res === null) {
|
|
2900
|
+
onResponse(trailers, flags, rawTrailers);
|
|
2901
|
+
return;
|
|
2902
|
+
}
|
|
2903
|
+
// Assigns trailers to the response object.
|
|
2904
|
+
res.trailers = trailers;
|
|
2905
|
+
res.rawTrailers = rawTrailers;
|
|
2906
|
+
});
|
|
2907
|
+
stream.once('close', ()=>{
|
|
2908
|
+
const { aborted, res } = this;
|
|
2909
|
+
if (res) {
|
|
2910
|
+
if (aborted) {
|
|
2911
|
+
res.aborted = true;
|
|
2912
|
+
res.emit('aborted');
|
|
2913
|
+
res.destroy();
|
|
2914
|
+
}
|
|
2915
|
+
const finish = ()=>{
|
|
2916
|
+
res.emit('close');
|
|
2917
|
+
this.destroy();
|
|
2918
|
+
this.emit('close');
|
|
2919
|
+
};
|
|
2920
|
+
if (res.readable) {
|
|
2921
|
+
res.once('end', finish);
|
|
2922
|
+
} else {
|
|
2923
|
+
finish();
|
|
2924
|
+
}
|
|
2925
|
+
return;
|
|
2926
|
+
}
|
|
2927
|
+
if (!this.destroyed) {
|
|
2928
|
+
this.destroy(new Error('The HTTP/2 stream has been early terminated'));
|
|
2929
|
+
this.emit('close');
|
|
2930
|
+
return;
|
|
2931
|
+
}
|
|
2932
|
+
this.destroy();
|
|
2933
|
+
this.emit('close');
|
|
2934
|
+
});
|
|
2935
|
+
this.socket = new Proxy(stream, proxySocketHandler);
|
|
2936
|
+
for (const job of this[kJobs]){
|
|
2937
|
+
job();
|
|
2938
|
+
}
|
|
2939
|
+
this[kJobs].length = 0;
|
|
2940
|
+
this.emit('socket', this.socket);
|
|
2941
|
+
};
|
|
2942
|
+
if (!(HTTP2_HEADER_AUTHORITY in this[kHeaders]) && !isConnectMethod) {
|
|
2943
|
+
this[kHeaders][HTTP2_HEADER_AUTHORITY] = this[kOrigin].host;
|
|
2944
|
+
}
|
|
2945
|
+
// Makes a HTTP2 request
|
|
2946
|
+
if (this[kSession]) {
|
|
2947
|
+
try {
|
|
2948
|
+
onStream(this[kSession].request(this[kHeaders]));
|
|
2949
|
+
} catch (error) {
|
|
2950
|
+
this.destroy(error);
|
|
2951
|
+
}
|
|
2952
|
+
} else {
|
|
2953
|
+
this.reusedSocket = true;
|
|
2954
|
+
try {
|
|
2955
|
+
const promise = this.agent.request(this[kOrigin], this[kOptions], this[kHeaders]);
|
|
2956
|
+
this[kPendingAgentPromise] = promise;
|
|
2957
|
+
onStream(await promise);
|
|
2958
|
+
this[kPendingAgentPromise] = false;
|
|
2959
|
+
} catch (error) {
|
|
2960
|
+
this[kPendingAgentPromise] = false;
|
|
2961
|
+
this.destroy(error);
|
|
2962
|
+
}
|
|
2963
|
+
}
|
|
2964
|
+
}
|
|
2965
|
+
get connection() {
|
|
2966
|
+
return this.socket;
|
|
2967
|
+
}
|
|
2968
|
+
set connection(value) {
|
|
2969
|
+
this.socket = value;
|
|
2970
|
+
}
|
|
2971
|
+
getHeaderNames() {
|
|
2972
|
+
return Object.keys(this[kHeaders]);
|
|
2973
|
+
}
|
|
2974
|
+
hasHeader(name) {
|
|
2975
|
+
if (typeof name !== 'string') {
|
|
2976
|
+
throw new ERR_INVALID_ARG_TYPE('name', 'string', name);
|
|
2977
|
+
}
|
|
2978
|
+
return Boolean(this[kHeaders][name.toLowerCase()]);
|
|
2979
|
+
}
|
|
2980
|
+
getHeader(name) {
|
|
2981
|
+
if (typeof name !== 'string') {
|
|
2982
|
+
throw new ERR_INVALID_ARG_TYPE('name', 'string', name);
|
|
2983
|
+
}
|
|
2984
|
+
return this[kHeaders][name.toLowerCase()];
|
|
2985
|
+
}
|
|
2986
|
+
get headersSent() {
|
|
2987
|
+
return this[kFlushedHeaders];
|
|
2988
|
+
}
|
|
2989
|
+
removeHeader(name) {
|
|
2990
|
+
if (typeof name !== 'string') {
|
|
2991
|
+
throw new ERR_INVALID_ARG_TYPE('name', 'string', name);
|
|
2992
|
+
}
|
|
2993
|
+
if (this.headersSent) {
|
|
2994
|
+
throw new ERR_HTTP_HEADERS_SENT('remove');
|
|
2995
|
+
}
|
|
2996
|
+
delete this[kHeaders][name.toLowerCase()];
|
|
2997
|
+
}
|
|
2998
|
+
setHeader(name, value) {
|
|
2999
|
+
if (this.headersSent) {
|
|
3000
|
+
throw new ERR_HTTP_HEADERS_SENT('set');
|
|
3001
|
+
}
|
|
3002
|
+
validateHeaderName(name);
|
|
3003
|
+
validateHeaderValue(name, value);
|
|
3004
|
+
const lowercased = name.toLowerCase();
|
|
3005
|
+
if (lowercased === 'connection') {
|
|
3006
|
+
if (value.toLowerCase() === 'keep-alive') {
|
|
3007
|
+
return;
|
|
3008
|
+
}
|
|
3009
|
+
throw new Error(`Invalid 'connection' header: ${value}`);
|
|
3010
|
+
}
|
|
3011
|
+
if (lowercased === 'host' && this.method === 'CONNECT') {
|
|
3012
|
+
this[kHeaders][HTTP2_HEADER_AUTHORITY] = value;
|
|
3013
|
+
} else {
|
|
3014
|
+
this[kHeaders][lowercased] = value;
|
|
3015
|
+
}
|
|
3016
|
+
}
|
|
3017
|
+
setNoDelay() {
|
|
3018
|
+
// HTTP2 sockets cannot be malformed, do nothing.
|
|
3019
|
+
}
|
|
3020
|
+
setSocketKeepAlive() {
|
|
3021
|
+
// HTTP2 sockets cannot be malformed, do nothing.
|
|
3022
|
+
}
|
|
3023
|
+
setTimeout(ms, callback) {
|
|
3024
|
+
const applyTimeout = ()=>this._request.setTimeout(ms, callback);
|
|
3025
|
+
if (this._request) {
|
|
3026
|
+
applyTimeout();
|
|
3027
|
+
} else {
|
|
3028
|
+
this[kJobs].push(applyTimeout);
|
|
3029
|
+
}
|
|
3030
|
+
return this;
|
|
3031
|
+
}
|
|
3032
|
+
get maxHeadersCount() {
|
|
3033
|
+
if (!this.destroyed && this._request) {
|
|
3034
|
+
return this._request.session.localSettings.maxHeaderListSize;
|
|
3035
|
+
}
|
|
3036
|
+
return undefined;
|
|
3037
|
+
}
|
|
3038
|
+
set maxHeadersCount(_value) {
|
|
3039
|
+
// Updating HTTP2 settings would affect all requests, do nothing.
|
|
3040
|
+
}
|
|
3041
|
+
}
|
|
3042
|
+
clientRequest = ClientRequest;
|
|
3043
|
+
return clientRequest;
|
|
3044
|
+
}
|
|
3045
|
+
|
|
1541
3046
|
var doh;
|
|
1542
3047
|
var hasRequiredDoh;
|
|
1543
3048
|
|
|
1544
3049
|
function requireDoh () {
|
|
1545
3050
|
if (hasRequiredDoh) return doh;
|
|
1546
3051
|
hasRequiredDoh = 1;
|
|
1547
|
-
const http = require$$0__default$
|
|
3052
|
+
const http = require$$0__default$4.default;
|
|
1548
3053
|
const https = require$$1__default.default;
|
|
1549
|
-
const
|
|
3054
|
+
const HTTP2ClientRequest = requireClientRequest();
|
|
1550
3055
|
const Packet = requirePacket();
|
|
3056
|
+
// https://github.com/szmarczak/http2-wrapper/blob/f1a1776d4b1aef1cd47d55b1a950986e86145a09/source/index.js#L24
|
|
3057
|
+
// Copy the implementation allows us to pull in minimum code,
|
|
3058
|
+
// instead of importing the entire module to reduce bundle size
|
|
3059
|
+
function http2Get(url, options, callback) {
|
|
3060
|
+
const req = new HTTP2ClientRequest(url, options, callback);
|
|
3061
|
+
req.end();
|
|
3062
|
+
return req;
|
|
3063
|
+
}
|
|
1551
3064
|
const protocols = {
|
|
1552
3065
|
'http:': http.get,
|
|
1553
3066
|
'https:': https.get,
|
|
1554
|
-
'h2:':
|
|
1555
|
-
const urlObj = new URL(url);
|
|
1556
|
-
const client = http2.connect(url.replace('h2:', 'https:'));
|
|
1557
|
-
const req = client.request({
|
|
1558
|
-
':path': `${urlObj.pathname}${urlObj.search}`,
|
|
1559
|
-
':method': 'GET',
|
|
1560
|
-
...options.headers
|
|
1561
|
-
});
|
|
1562
|
-
req.on('response', (headers)=>{
|
|
1563
|
-
client.close();
|
|
1564
|
-
done({
|
|
1565
|
-
headers,
|
|
1566
|
-
statusCode: headers[':status'],
|
|
1567
|
-
on: req.on.bind(req)
|
|
1568
|
-
});
|
|
1569
|
-
});
|
|
1570
|
-
req.on('error', (_err)=>{
|
|
1571
|
-
client.close();
|
|
1572
|
-
});
|
|
1573
|
-
req.end();
|
|
1574
|
-
return req;
|
|
1575
|
-
}
|
|
3067
|
+
'h2:': http2Get
|
|
1576
3068
|
};
|
|
1577
3069
|
const makeRequest = (url, query)=>new Promise((resolve, reject)=>{
|
|
1578
3070
|
const index = url.indexOf('://');
|
|
@@ -1587,6 +3079,10 @@ function requireDoh () {
|
|
|
1587
3079
|
u.search = searchParams.toString();
|
|
1588
3080
|
const get = protocols[u.protocol];
|
|
1589
3081
|
if (!get) throw new Error(`Unsupported protocol: ${u.protocol}, must be specified (http://, https:// or h2://)`);
|
|
3082
|
+
// after determining the protocol, convert h2 to https for get
|
|
3083
|
+
if (u.protocol === 'h2:') {
|
|
3084
|
+
u.protocol = 'https:';
|
|
3085
|
+
}
|
|
1590
3086
|
const req = get(u.toString(), {
|
|
1591
3087
|
headers: {
|
|
1592
3088
|
accept: 'application/dns-message'
|
|
@@ -1644,7 +3140,7 @@ function requireUdp () {
|
|
|
1644
3140
|
hasRequiredUdp = 1;
|
|
1645
3141
|
const udp = require$$0__default$1.default;
|
|
1646
3142
|
const Packet = requirePacket();
|
|
1647
|
-
const { equal } = require$$2__default
|
|
3143
|
+
const { equal } = require$$2__default.default;
|
|
1648
3144
|
const { debuglog } = require$$0__default.default;
|
|
1649
3145
|
const debug = debuglog('dns2');
|
|
1650
3146
|
udp_1 = ({ dns = '8.8.8.8', port = 53, socketType = 'udp4' } = {})=>{
|
|
@@ -1712,7 +3208,7 @@ function requireDns2 () {
|
|
|
1712
3208
|
if (hasRequiredDns2) return dns2;
|
|
1713
3209
|
hasRequiredDns2 = 1;
|
|
1714
3210
|
const { TCPServer, UDPServer, DOHServer, createTCPServer, createUDPServer, createDOHServer, createServer } = requireServer();
|
|
1715
|
-
const EventEmitter = require$$
|
|
3211
|
+
const EventEmitter = require$$1__default$1.default;
|
|
1716
3212
|
/**
|
|
1717
3213
|
* [DNS description]
|
|
1718
3214
|
* @docs https://tools.ietf.org/html/rfc1034
|
|
@@ -3112,7 +4608,7 @@ const sharedNullResponse = Object.freeze({
|
|
|
3112
4608
|
const mutex = createAsyncMutex();
|
|
3113
4609
|
const dnsClients = getDnsClients(dnsServers);
|
|
3114
4610
|
return async function isRegisterableDomainAlive(domain) {
|
|
3115
|
-
domain = require$$
|
|
4611
|
+
domain = require$$0$3.domainToASCII(domain);
|
|
3116
4612
|
return mutex(domain, ()=>cacheApply(registerableDomainResultCache, domain, async ()=>{
|
|
3117
4613
|
// Step 0: we normalize the domain and find the registerable part
|
|
3118
4614
|
const registerableDomain = tldts.getDomain(domain, getRegisterableDomainTldtsOption);
|
|
@@ -3218,7 +4714,7 @@ function createDomainAliveChecker(options = {}) {
|
|
|
3218
4714
|
const mutex = createAsyncMutex();
|
|
3219
4715
|
const dnsClients = getDnsClients(dnsServers);
|
|
3220
4716
|
return async function isDomainAlive(domain) {
|
|
3221
|
-
domain = require$$
|
|
4717
|
+
domain = require$$0$3.domainToASCII(domain);
|
|
3222
4718
|
const registerableDomainAliveResult = await isRegisterableDomainAlive(domain);
|
|
3223
4719
|
if (registerableDomainAliveResult.registerableDomain === null) {
|
|
3224
4720
|
return sharedNullishResult;
|