@wiajs/request 3.0.29 → 3.0.31

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/request.cjs CHANGED
@@ -1,16 +1,16 @@
1
1
  /*!
2
- * wia request v3.0.29
2
+ * wia request v3.0.31
3
3
  * (c) 2022-2024 Sibyl Yu and contributors
4
4
  * Released under the MIT License.
5
5
  */
6
6
  'use strict';
7
7
 
8
+ const stream = require('node:stream');
8
9
  const log$2 = require('@wiajs/log');
9
10
  const http = require('node:http');
10
11
  const https = require('node:https');
11
12
  const assert = require('node:assert');
12
13
  const url = require('node:url');
13
- const stream = require('node:stream');
14
14
  const zlib = require('node:zlib');
15
15
  const mime = require('mime-types');
16
16
 
@@ -467,7 +467,7 @@ const log$1 = log$2.log({env: `wia:req:${log$2.name((typeof document === 'undefi
467
467
  * @prop {*} [beforeRedirect]
468
468
  * @prop {boolean} [followRedirects]
469
469
  * @prop {number} [maxRedirects=21]
470
- * @prop {number} [maxBodyLength = 0]
470
+ * @prop {number} [maxBodyLength = -1]
471
471
  * @prop {*} [trackRedirects]
472
472
  * @prop {*} [data]
473
473
  */
@@ -478,7 +478,7 @@ const log$1 = log$2.log({env: `wia:req:${log$2.name((typeof document === 'undefi
478
478
  * @prop {number} [responseStartTime]
479
479
  */
480
480
 
481
- /** @typedef { http.IncomingMessage & ResponseExt} Response*/
481
+ /** @typedef { http.IncomingMessage & ResponseExt} Response */
482
482
 
483
483
  const httpModules = {'http:': http, 'https:': https};
484
484
 
@@ -528,12 +528,12 @@ const writeEvents = [
528
528
  'connect',
529
529
  'continue',
530
530
  'drain',
531
- 'error',
531
+ // 'error', // 单独处理,未注册 'error' 事件处理程序,错误将冒泡到全局导致程序崩溃
532
532
  'finish',
533
533
  'information',
534
534
  'pipe',
535
535
  // 'response', 由 processResponse 触发
536
- 'socket',
536
+ 'socket', // 建立连接时触发
537
537
  'timeout',
538
538
  'unpipe',
539
539
  'upgrade',
@@ -545,7 +545,7 @@ for (const ev of writeEvents)
545
545
  writeEventEmit[ev] = /** @param {...any} args */ function (...args) {
546
546
  const m = this; // 事件回调,this === clientRequest 实例
547
547
  // log('req event', {ev})
548
- m.redirectReq.emit(ev, ...args); // req 事情映射到 redirectReq 上触发
548
+ m.redirectReq.emit(ev, ...args); // 内部请求req 事情转发到 Request
549
549
  };
550
550
 
551
551
  // stream.Readable,在响应流上转发读流取事件
@@ -578,6 +578,21 @@ const MaxBodyLengthExceededError = utils.createErrorType(
578
578
 
579
579
  const WriteAfterEndError = utils.createErrorType('ERR_STREAM_WRITE_AFTER_END', 'write after end');
580
580
 
581
+ // request err
582
+ const HostNotfoundError = utils.createErrorType('ERR_HOSTNOTFOUND', 'DNS 解析失败,主机名可能无效');
583
+ const ConnRefusedError = utils.createErrorType(
584
+ 'ERR_CONNREFUSED',
585
+ '连接被拒绝,目标服务器可能不可用'
586
+ );
587
+ const ConnTimedoutError = utils.createErrorType(
588
+ 'ERR_CONNTIMEDOUT',
589
+ '请求超时,请检查网络连接或服务器负载'
590
+ );
591
+ const ConnResetError = utils.createErrorType(
592
+ 'ERR_CONNRESET',
593
+ '连接被重置,可能是网络问题或服务器关闭了连接'
594
+ );
595
+
581
596
  /**
582
597
  * An HTTP(S) request that can be redirected
583
598
  * wrap http.ClientRequest
@@ -777,7 +792,41 @@ class Request extends stream.Duplex {
777
792
  // 启动 startTimer
778
793
  if (m.startTimer) m._currentRequest.once('socket', m.startTimer);
779
794
 
780
- // 接收req事件,转发 redirectReq 发射
795
+ // set tcp keep alive to prevent drop connection by peer
796
+ req.on(
797
+ 'socket',
798
+ /** @param {*} socket */ socket => {
799
+ // default interval of sending ack packet is 1 minute
800
+ socket.setKeepAlive(true, 1000 * 60);
801
+ }
802
+ );
803
+
804
+ // 请求error单独处理
805
+ // 'error' 事件处理,避免错误将冒泡到全局导致程序崩溃
806
+ req.on('error', err => {
807
+ destroyRequest(req); // 释放资源
808
+ // @ts-ignore
809
+ log$1.error({errcode: err?.code}, 'request');
810
+ // @ts-ignore
811
+ switch (err?.code) {
812
+ case 'ENOTFOUND':
813
+ m.emit('error', new HostNotfoundError());
814
+ break
815
+ case 'ECONNREFUSED':
816
+ m.emit('error', new ConnRefusedError());
817
+ break
818
+ case 'ETIMEDOUT':
819
+ m.emit('error', new ConnTimedoutError());
820
+ break
821
+ case 'ECONNRESET':
822
+ m.emit('error', new ConnResetError());
823
+ break
824
+ default:
825
+ m.emit('error', utils.createErrorType('ERR_CONNOTHER', `网络错误: ${err.message}`));
826
+ }
827
+ });
828
+
829
+ // 接收req事件,转发 到 request 上发射,网络关闭事件,触发 error
781
830
  for (const ev of writeEvents) req.on(ev, writeEventEmit[ev]);
782
831
 
783
832
  // RFC7230§5.3.1: When making a request directly to an origin server, […]
@@ -826,9 +875,11 @@ class Request extends stream.Duplex {
826
875
  return R
827
876
  }
828
877
 
878
+ /**
879
+ * 写入错误,释放请求,触发 abort 终止事件
880
+ */
829
881
  abort() {
830
882
  destroyRequest(this._currentRequest);
831
- this._currentRequest.abort();
832
883
  this.emit('abort');
833
884
  }
834
885
 
@@ -892,7 +943,11 @@ class Request extends stream.Duplex {
892
943
  // log({data: chunk, encoding, cb}, 'write')
893
944
 
894
945
  // Writing is not allowed if end has been called
895
- if (m._ending) throw new WriteAfterEndError()
946
+ if (m._ending) {
947
+ // throw new WriteAfterEndError()
948
+ m.emit('error', new WriteAfterEndError());
949
+ return
950
+ }
896
951
 
897
952
  // ! 数据写入时连接,pipe 时可设置 header
898
953
  if (!m._currentRequest) m.request();
@@ -1087,6 +1142,7 @@ class Request extends stream.Duplex {
1087
1142
  m.on('error', clearTimer);
1088
1143
  m.on('response', clearTimer);
1089
1144
  m.on('close', clearTimer);
1145
+
1090
1146
  return m
1091
1147
  }
1092
1148
 
@@ -1277,14 +1333,15 @@ class Request extends stream.Duplex {
1277
1333
  /**
1278
1334
  * 处理响应stream
1279
1335
  * 自动解压,透传流,需设置 decompress = false,避免解压数据
1280
- * @param {*} res
1336
+ * @param {Response} res
1337
+ * @returns {Response | stream.Readable}
1281
1338
  */
1282
1339
  processStream(res) {
1283
1340
  const m = this;
1284
1341
  const {opt} = m;
1285
1342
 
1286
1343
  const streams = [res];
1287
-
1344
+ let responseStream = res;
1288
1345
  // 'transfer-encoding': 'chunked'时,无content-length,axios v1.2 不能自动解压
1289
1346
  const responseLength = +res.headers['content-length'];
1290
1347
 
@@ -1317,6 +1374,7 @@ class Request extends stream.Duplex {
1317
1374
  case 'compress':
1318
1375
  case 'x-compress':
1319
1376
  // add the unzipper to the body stream processing pipeline
1377
+ // @ts-ignore
1320
1378
  streams.push(zlib.createUnzip(zlibOptions));
1321
1379
 
1322
1380
  // remove the content-encoding in order to not confuse downstream operations
@@ -1324,9 +1382,11 @@ class Request extends stream.Duplex {
1324
1382
  break
1325
1383
 
1326
1384
  case 'deflate':
1385
+ // @ts-ignore
1327
1386
  streams.push(new ZlibTransform());
1328
1387
 
1329
1388
  // add the unzipper to the body stream processing pipeline
1389
+ // @ts-ignore
1330
1390
  streams.push(zlib.createUnzip(zlibOptions));
1331
1391
 
1332
1392
  // remove the content-encoding in order to not confuse downstream operations
@@ -1335,6 +1395,7 @@ class Request extends stream.Duplex {
1335
1395
 
1336
1396
  case 'br':
1337
1397
  if (isBrotliSupported) {
1398
+ // @ts-ignore
1338
1399
  streams.push(zlib.createBrotliDecompress(brotliOptions));
1339
1400
  res.headers['content-encoding'] = undefined;
1340
1401
  }
@@ -1343,10 +1404,13 @@ class Request extends stream.Duplex {
1343
1404
  }
1344
1405
 
1345
1406
  // 响应流,用于读
1346
- const responseStream = streams.length > 1 ? stream.pipeline(streams, utils.noop) : streams[0];
1407
+ // @ts-ignore
1408
+ responseStream = streams.length > 1 ? stream.pipeline(streams, utils.noop) : streams[0];
1347
1409
  // 将内部 responseStream 可读流 映射到 redirectReq
1348
1410
 
1411
+ // @ts-ignore
1349
1412
  m.responseStream = responseStream;
1413
+ // @ts-ignore
1350
1414
  responseStream.redirectReq = m; // 事情触发时引用
1351
1415
 
1352
1416
  // stream 模式,事件透传到 请求类
@@ -1518,7 +1582,9 @@ class Request extends stream.Duplex {
1518
1582
  }
1519
1583
 
1520
1584
  /**
1521
- *
1585
+ * 释放请求,触发error事件
1586
+ * 'error' event, and emit a 'close' event.
1587
+ * Calling this will cause remaining data in the response to be dropped and the socket to be destroyed.
1522
1588
  * @param {*} request
1523
1589
  * @param {*} error
1524
1590
  */
@@ -1527,7 +1593,7 @@ function destroyRequest(request, error) {
1527
1593
  request.removeListener(ev, writeEventEmit[ev]);
1528
1594
  }
1529
1595
  request.on('error', utils.noop);
1530
- request.destroy(error);
1596
+ request.destroy(error); // 触发 error 事件
1531
1597
  }
1532
1598
 
1533
1599
  /**
@@ -1569,9 +1635,35 @@ function isSubdomain(subdomain, domain) {
1569
1635
  * 代理模式下,http or https 请求,取决于 proxy 代理服务器,而不是目的服务器。
1570
1636
  */
1571
1637
 
1572
-
1573
1638
  const log = log$2.log({env: `wia:req:${log$2.name((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('request.cjs', document.baseURI).href)))}`}); // __filename
1574
1639
 
1640
+ /** @typedef { import('./request').Response} Response */
1641
+
1642
+ /**
1643
+ * @typedef {object} Opts
1644
+ * @prop {Object.<string,string>} [headers]
1645
+ * @prop {string} [url]
1646
+ * @prop {'http:' | 'https:'} [protocol]
1647
+ * @prop {string} [host]
1648
+ * @prop {string} [family]
1649
+ * @prop {string} [path]
1650
+ * @prop {string} [method]
1651
+ * @prop {*} [agent] - 发送请求的agent
1652
+ * @prop {*} [agents] - http、https agent,根据协议自动选择
1653
+ * @prop {*} [body] - body 数据,优先body,其次data
1654
+ * @prop {*} [data] - body 数据
1655
+ * @prop {boolean} [stream] - 以流的方式工作
1656
+ * @prop {boolean} [decompress=true] - 自动解压
1657
+ * @prop {*} [transformStream]
1658
+ * @prop {*} [beforeRedirect]
1659
+ * @prop {boolean} [followRedirects] - 自动完成重定向
1660
+ * @prop {number} [maxRedirects=21] - 最大重定向次数
1661
+ * @prop {number} [maxBodyLength = 0] - body限制,缺省不限
1662
+ * @prop {*} [trackRedirects]
1663
+ */
1664
+
1665
+ /** @typedef {(res: Response, stream?: stream.Readable) => void} Cb*/
1666
+
1575
1667
  utils.createErrorType(
1576
1668
  'ERR_STREAM_WRITE_BEEN_ABORTED',
1577
1669
  'Request stream has been aborted'
@@ -1584,7 +1676,7 @@ utils.createErrorType(
1584
1676
  const looksLikeBrowser = typeof window !== 'undefined' && typeof document !== 'undefined';
1585
1677
  const looksLikeV8 = utils.isFunction(Error.captureStackTrace);
1586
1678
  if (!looksLikeNode && (looksLikeBrowser || !looksLikeV8)) {
1587
- console.warn('The follow-redirects package should be excluded from browser builds.');
1679
+ log.warn('The follow-redirects package should be excluded from browser builds.');
1588
1680
  }
1589
1681
  })();
1590
1682
 
@@ -1596,10 +1688,10 @@ utils.createErrorType(
1596
1688
 
1597
1689
  /**
1598
1690
  * 初始化参数
1599
- * @param {*} uri/opts
1600
- * @param {*} opts/cb
1601
- * @param {*} cb/null
1602
- * @returns
1691
+ * @param {string | Opts} uri/opts
1692
+ * @param {Opts | Cb} [opts] /cb
1693
+ * @param {Cb} [cb]
1694
+ * @returns {{opts: Opts, cb: Cb}}
1603
1695
  */
1604
1696
  function init(uri, opts, cb) {
1605
1697
  let R;
@@ -1608,39 +1700,59 @@ function init(uri, opts, cb) {
1608
1700
  if (utils.isURL(uri)) uri = utils.spreadUrlObject(uri);
1609
1701
  else if (utils.isString(uri)) uri = utils.spreadUrlObject(utils.parseUrl(uri));
1610
1702
  else {
1703
+ // @ts-ignore
1611
1704
  cb = opts;
1705
+ // @ts-ignore
1612
1706
  opts = uri;
1707
+ // @ts-ignore
1613
1708
  const {url} = opts;
1709
+ // 有url,解析
1614
1710
  if (url) {
1711
+ // @ts-ignore
1712
+ // biome-ignore lint/performance/noDelete: <explanation>
1615
1713
  delete opts.url;
1616
1714
  if (utils.isURL(url)) uri = utils.spreadUrlObject(url);
1617
1715
  else if (utils.isString(url)) uri = utils.spreadUrlObject(utils.parseUrl(url));
1618
1716
  } else {
1619
- opts = utils.validateUrl(uri);
1717
+ // @ts-ignore
1718
+ opts = uri; // 不判断 utils.validateUrl(uri)
1620
1719
  uri = {};
1621
1720
  }
1622
1721
  }
1623
1722
 
1624
1723
  if (utils.isFunction(opts)) {
1724
+ // @ts-ignore
1625
1725
  cb = opts;
1626
- opts = null;
1726
+ opts = {};
1627
1727
  }
1628
1728
 
1629
1729
  // copy options
1630
1730
  opts = {
1731
+ // @ts-ignore
1631
1732
  ...uri,
1632
1733
  ...opts,
1633
1734
  };
1634
1735
 
1736
+ // @ts-ignore
1635
1737
  if (!utils.isString(opts.host) && !utils.isString(opts.hostname)) opts.hostname = '::1';
1738
+ // @ts-ignore
1636
1739
  if (opts.method) opts.method = opts.method.toUpperCase();
1637
1740
 
1638
- R = {opts: opts, cb: cb};
1741
+ // @ts-ignore
1742
+ // follow-redirects does not skip comparison, so it should always succeed for axios -1 unlimited
1743
+ opts.maxBodyLength = opts.maxBodyLength ?? Number.POSITIVE_INFINITY;
1744
+ // @ts-ignore
1745
+ opts.maxRedirects = opts.maxRedirects ?? 21;
1746
+ // @ts-ignore
1747
+ if (opts.maxRedirects === 0) opts.followRedirects = false;
1748
+
1749
+ R = {opts, cb};
1639
1750
  // log({R}, 'init')
1640
1751
  } catch (e) {
1641
1752
  log.err(e, 'init');
1642
1753
  }
1643
1754
 
1755
+ // @ts-ignore
1644
1756
  return R
1645
1757
  }
1646
1758
 
@@ -1651,19 +1763,26 @@ function init(uri, opts, cb) {
1651
1763
  * 注意变参 (options[, callback]) or (url[, options][, callback])
1652
1764
  maxRedirects: _.maxRedirects,
1653
1765
  maxBodyLength: _.maxBodyLength,
1654
- * @param {*} uri/options
1655
- * @param {*} options/callback
1656
- * @param {*} callback/null
1657
- * @returns
1766
+ * @param {string | Opts} uri /options
1767
+ * @param {Opts | Cb} [options] /callback
1768
+ * @param {Cb} [callback] /null
1769
+ * @returns {Request}
1658
1770
  */
1659
1771
  function request(uri, options, callback) {
1660
1772
  let R = null;
1661
1773
 
1662
1774
  try {
1775
+ // @ts-ignore
1663
1776
  const {opts, cb} = init(uri, options, callback);
1664
- // log({uri, options, opts}, 'request')
1665
- const req = new Request(opts, cb);
1777
+ // log.error({uri, options, opts}, 'request')
1778
+
1666
1779
  const {data, stream} = opts;
1780
+ // data 在本函数完成处理,不传递到 request
1781
+ opts.data = undefined;
1782
+
1783
+ // @ts-ignore
1784
+ const req = new Request(opts, cb);
1785
+
1667
1786
  // 非流模式,自动发送请求,流模式通过流写入发送
1668
1787
  if (!stream) {
1669
1788
  // 发送数据
@@ -1690,8 +1809,12 @@ function request(uri, options, callback) {
1690
1809
  }
1691
1810
  });
1692
1811
 
1812
+ // log.error({data}, 'request data.pipe')
1693
1813
  data.pipe(req); // 写入数据流
1694
- } else req.end(data);
1814
+ } else {
1815
+ // log.error({data}, 'request req.end')
1816
+ req.end(data); // 写入数据
1817
+ }
1695
1818
  }
1696
1819
 
1697
1820
  R = req;
@@ -1708,40 +1831,23 @@ function request(uri, options, callback) {
1708
1831
  * 复杂数据,请使用 @wiajs/req库(fork from axios),该库封装了当前库,提供了更多功能
1709
1832
  * organize params for patch, post, put, head, del
1710
1833
  * @param {string} verb
1711
- * @returns {Request} Duplex
1834
+ * @returns {(url: string | Opts, opts?: Opts | Cb, cb?: Cb) => void}}
1712
1835
  */
1713
1836
  function fn(verb) {
1714
1837
  const method = verb.toUpperCase();
1715
-
1716
- // @ts-ignore
1717
- return (uri, options, callback) => {
1718
- const {opts, cb} = init(uri, options, callback);
1838
+ /**
1839
+ *
1840
+ * @param {string | Opts} uri /options
1841
+ * @param {Opts | Cb} [opts] /callback
1842
+ * @param {Cb} [cb] /null
1843
+ * @returns
1844
+ */
1845
+ function fn(uri, opts, cb) {
1846
+ // @ts-ignore
1719
1847
  opts.method = method;
1720
- const req = new Request(opts, cb);
1721
- const {data, stream} = opts;
1722
- // 非流模式,自动发送请求,流模式通过流写入发送
1723
- if (!stream) {
1724
- // 发送数据
1725
- if (utils.isStream(data)) {
1726
-
1727
- data.on('end', () => {
1728
- });
1729
-
1730
- data.once(
1731
- 'error',
1732
- /** @param {*} err */ err => {
1733
- // req.destroy(err)
1734
- }
1735
- );
1736
-
1737
- data.on('close', () => {
1738
- });
1739
-
1740
- data.pipe(req); // 写入数据流
1741
- } else req.end(data);
1742
- }
1743
- return req
1848
+ return request(uri, opts, cb)
1744
1849
  }
1850
+ return fn
1745
1851
  }
1746
1852
 
1747
1853
  // define like this to please codeintel/intellisense IDEs