nstarter-http-request 0.1.11 → 0.1.13
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/CHANGELOG.md +6 -0
- package/README.md +1 -2
- package/dist/cjs/adapter/axios.adapter.js +97 -2
- package/dist/cjs/adapter/axios.adapter.js.map +1 -1
- package/dist/cjs/adapter/base.js +20 -1
- package/dist/cjs/adapter/base.js.map +1 -1
- package/dist/cjs/adapter/undici.adapter.js +97 -77
- package/dist/cjs/adapter/undici.adapter.js.map +1 -1
- package/dist/cjs/client.js +99 -56
- package/dist/cjs/client.js.map +1 -1
- package/dist/cjs/config.js +1 -1
- package/dist/cjs/config.js.map +1 -1
- package/dist/cjs/const/config.const.js +8 -3
- package/dist/cjs/const/config.const.js.map +1 -1
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/cjs/types/errors.js +30 -2
- package/dist/cjs/types/errors.js.map +1 -1
- package/dist/cjs/utils/index.js +2 -1
- package/dist/cjs/utils/index.js.map +1 -1
- package/dist/cjs/utils/redirect.js +138 -0
- package/dist/cjs/utils/redirect.js.map +1 -0
- package/dist/esm/adapter/axios.adapter.js +99 -4
- package/dist/esm/adapter/axios.adapter.js.map +1 -1
- package/dist/esm/adapter/base.js +20 -1
- package/dist/esm/adapter/base.js.map +1 -1
- package/dist/esm/adapter/undici.adapter.js +98 -78
- package/dist/esm/adapter/undici.adapter.js.map +1 -1
- package/dist/esm/client.js +100 -57
- package/dist/esm/client.js.map +1 -1
- package/dist/esm/config.js +1 -1
- package/dist/esm/config.js.map +1 -1
- package/dist/esm/const/config.const.js +7 -2
- package/dist/esm/const/config.const.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/esm/types/errors.js +28 -1
- package/dist/esm/types/errors.js.map +1 -1
- package/dist/esm/utils/index.js +1 -0
- package/dist/esm/utils/index.js.map +1 -1
- package/dist/esm/utils/redirect.js +133 -0
- package/dist/esm/utils/redirect.js.map +1 -0
- package/dist/types/adapter/axios.adapter.d.ts +22 -0
- package/dist/types/adapter/axios.adapter.d.ts.map +1 -1
- package/dist/types/adapter/base.d.ts +6 -0
- package/dist/types/adapter/base.d.ts.map +1 -1
- package/dist/types/adapter/undici.adapter.d.ts +24 -12
- package/dist/types/adapter/undici.adapter.d.ts.map +1 -1
- package/dist/types/client.d.ts +11 -0
- package/dist/types/client.d.ts.map +1 -1
- package/dist/types/const/config.const.d.ts +6 -0
- package/dist/types/const/config.const.d.ts.map +1 -1
- package/dist/types/types/config.d.ts +2 -2
- package/dist/types/types/config.d.ts.map +1 -1
- package/dist/types/types/errors.d.ts +26 -1
- package/dist/types/types/errors.d.ts.map +1 -1
- package/dist/types/types/request_response.d.ts +1 -1
- package/dist/types/types/request_response.d.ts.map +1 -1
- package/dist/types/utils/index.d.ts +1 -0
- package/dist/types/utils/index.d.ts.map +1 -1
- package/dist/types/utils/redirect.d.ts +26 -0
- package/dist/types/utils/redirect.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/adapter/axios.adapter.ts +128 -4
- package/src/adapter/base.ts +22 -1
- package/src/adapter/undici.adapter.ts +112 -91
- package/src/client.ts +114 -63
- package/src/config.ts +1 -1
- package/src/const/config.const.ts +8 -2
- package/src/types/config.ts +2 -3
- package/src/types/errors.ts +39 -1
- package/src/types/request_response.ts +1 -2
- package/src/utils/index.ts +1 -0
- package/src/utils/redirect.ts +163 -0
|
@@ -36,7 +36,6 @@ export declare class UndiciAdapter extends BaseHttpAdapter {
|
|
|
36
36
|
/**
|
|
37
37
|
* 构建 Dispatcher(代理或 TLS 配置)
|
|
38
38
|
* @param options 请求选项
|
|
39
|
-
* @param timeout 请求总超时时间
|
|
40
39
|
*/
|
|
41
40
|
private buildDispatcher;
|
|
42
41
|
/**
|
|
@@ -63,6 +62,10 @@ export declare class UndiciAdapter extends BaseHttpAdapter {
|
|
|
63
62
|
* 获取请求级超时时间。
|
|
64
63
|
*/
|
|
65
64
|
private resolveTimeout;
|
|
65
|
+
/**
|
|
66
|
+
* 获取传给 undici 的阶段超时时间。0 用于内部禁用 undici 默认阶段超时。
|
|
67
|
+
*/
|
|
68
|
+
private resolveUndiciTimeout;
|
|
66
69
|
/**
|
|
67
70
|
* 判断超时时间是否启用。
|
|
68
71
|
*/
|
|
@@ -74,28 +77,37 @@ export declare class UndiciAdapter extends BaseHttpAdapter {
|
|
|
74
77
|
*/
|
|
75
78
|
private handleUndiciError;
|
|
76
79
|
/**
|
|
77
|
-
*
|
|
80
|
+
* 2xx 和 304 作为成功结果返回。undici 未跟跳的 3xx 交回调用方。
|
|
78
81
|
*/
|
|
79
82
|
private ensureSuccessStatus;
|
|
80
83
|
/**
|
|
81
|
-
*
|
|
84
|
+
* 解析 Content-Length 响应头。仅接受合法的非负整数,重复头/非法值视为未知。
|
|
85
|
+
*/
|
|
86
|
+
private parseContentLength;
|
|
87
|
+
/**
|
|
88
|
+
* 丢弃未读完的响应体并终止下载。
|
|
89
|
+
* undici 的 BodyReadable.destroy() 不带错误时会以 RequestAbortedError 触发 error 事件,
|
|
90
|
+
* 没有监听器会变成未捕获异常,因此先挂空监听器。
|
|
82
91
|
*/
|
|
83
|
-
private
|
|
92
|
+
private discardBody;
|
|
84
93
|
/**
|
|
85
|
-
*
|
|
94
|
+
* 带大小上限地读取非流式响应体。
|
|
95
|
+
*
|
|
96
|
+
* 1. Content-Length 预检:响应头已声明超限时,不读取响应体,直接销毁并拒绝。
|
|
97
|
+
* 2. 逐分块累计字节数,累计值超过上限(等于上限放行)立即销毁响应流并拒绝,后续数据不再下载;
|
|
98
|
+
* 该行为对齐 axios(Node http 适配器)的 maxContentLength。
|
|
86
99
|
*/
|
|
87
|
-
private
|
|
100
|
+
private readBodyWithLimit;
|
|
101
|
+
/**
|
|
102
|
+
* 基于已读取(已受大小限制)的字节构造 Response,复用运行时的 blob/formData 解析逻辑。
|
|
103
|
+
*/
|
|
104
|
+
private toFetchResponse;
|
|
88
105
|
/**
|
|
89
106
|
* 读取响应体
|
|
90
107
|
* @param response Undici 响应对象
|
|
91
|
-
* @param
|
|
108
|
+
* @param options 请求选项
|
|
92
109
|
*/
|
|
93
110
|
private readResponseBody;
|
|
94
|
-
/**
|
|
95
|
-
* 按 axios 的默认兼容行为读取 JSON 响应。
|
|
96
|
-
* 空响应体或非 JSON 文本不应让成功状态码请求因为解析失败变成异常。
|
|
97
|
-
*/
|
|
98
|
-
private readJsonResponseBody;
|
|
99
111
|
/**
|
|
100
112
|
* 获取状态文本
|
|
101
113
|
* @param statusCode 状态码
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"undici.adapter.d.ts","sourceRoot":"","sources":["../../../src/adapter/undici.adapter.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EACR,kBAAkB,EAClB,YAAY,
|
|
1
|
+
{"version":3,"file":"undici.adapter.d.ts","sourceRoot":"","sources":["../../../src/adapter/undici.adapter.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EACR,kBAAkB,EAClB,YAAY,EACf,MAAM,UAAU,CAAC;AAKlB,OAAO,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;AAOzC,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAU1C;;GAEG;AACH,qBAAa,aAAc,SAAQ,eAAe;IAC9C,SAAgB,IAAI,wBAAkC;IAEtD;;OAEG;IACI,WAAW,IAAI,OAAO;IAI7B;;;OAGG;cACa,WAAW,CAAC,CAAC,EAAE,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IA4DrF;;;OAGG;IACH,OAAO,CAAC,cAAc;IAUtB;;;;OAIG;IACH,OAAO,CAAC,WAAW;IAgBnB;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAa3B;;;OAGG;IACH,OAAO,CAAC,eAAe;IA4BvB;;;OAGG;IACH,OAAO,CAAC,sBAAsB;IA4B9B;;;;OAIG;IACH,OAAO,CAAC,uBAAuB;IAiB/B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAoB1B;;;OAGG;IACH,OAAO,CAAC,yBAAyB;IA4BjC;;OAEG;IACH,OAAO,CAAC,cAAc;IAItB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAK5B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAIzB;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;IAmBzB;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAkB3B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAQ1B;;;;OAIG;IACH,OAAO,CAAC,WAAW;IAKnB;;;;;;OAMG;YACW,iBAAiB;IAgC/B;;OAEG;IACH,OAAO,CAAC,eAAe;IAOvB;;;;OAIG;YACW,gBAAgB;IA0C9B;;;OAGG;IACH,OAAO,CAAC,aAAa;CAGxB"}
|
package/dist/types/client.d.ts
CHANGED
|
@@ -47,6 +47,13 @@ export declare class NsHttpClient implements IHttpClient {
|
|
|
47
47
|
* @returns 响应结果
|
|
48
48
|
*/
|
|
49
49
|
request<T = any>(options: HttpRequestOptions): Promise<HttpResponse<T>>;
|
|
50
|
+
/**
|
|
51
|
+
*
|
|
52
|
+
* @param options 请求选项
|
|
53
|
+
* @param remainedRedirects 剩余可重定向次数
|
|
54
|
+
* @returns 响应结果
|
|
55
|
+
*/
|
|
56
|
+
private safeRequest;
|
|
50
57
|
/**
|
|
51
58
|
* 便捷方法:GET 请求
|
|
52
59
|
*/
|
|
@@ -71,6 +78,10 @@ export declare class NsHttpClient implements IHttpClient {
|
|
|
71
78
|
* 记录响应日志
|
|
72
79
|
*/
|
|
73
80
|
private logResponse;
|
|
81
|
+
/**
|
|
82
|
+
* 销毁不再交给调用方的响应流
|
|
83
|
+
*/
|
|
84
|
+
private discardResponseStream;
|
|
74
85
|
/**
|
|
75
86
|
* 判断响应数据是否为流对象(Node.js Readable)
|
|
76
87
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAER,uBAAuB,EACvB,kBAAkB,EAClB,YAAY,EAEZ,WAAW,EAGX,aAAa,EAGhB,MAAM,SAAS,CAAC;AAcjB,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -16,6 +16,12 @@ export declare const DEFAULT_LOGGING_CONFIG: LoggingConfig;
|
|
|
16
16
|
* 默认请求配置
|
|
17
17
|
*/
|
|
18
18
|
export declare const DEFAULT_REQUEST_CONFIG: RequestConfig;
|
|
19
|
+
/**
|
|
20
|
+
* 响应体大小预检的线上长度容差比例。
|
|
21
|
+
* axios 的 onDownloadProgress.total 是压缩后的 Content-Length,而 maxContentLength 按解压后计数;
|
|
22
|
+
* 不可压缩数据经 gzip 等压缩后可能略大于解压大小,预检仅在超过上限该比例时提前中止。
|
|
23
|
+
*/
|
|
24
|
+
export declare const MAX_RESPONSE_SIZE_TOLERANCE_RATIO = 0.01;
|
|
19
25
|
/**
|
|
20
26
|
* 默认 HTTP 客户端配置
|
|
21
27
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.const.d.ts","sourceRoot":"","sources":["../../../src/const/config.const.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACR,gBAAgB,EAChB,aAAa,EACb,aAAa,EACb,cAAc,EACjB,MAAM,UAAU,CAAC;AAIlB;;;GAGG;AACH,eAAO,MAAM,iBAAiB,QAAgB,CAAC;AAE/C;;GAEG;AACH,eAAO,MAAM,uBAAuB,EAAE,cASrC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,sBAAsB,EAAE,aAKpC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,sBAAsB,EAAE,
|
|
1
|
+
{"version":3,"file":"config.const.d.ts","sourceRoot":"","sources":["../../../src/const/config.const.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACR,gBAAgB,EAChB,aAAa,EACb,aAAa,EACb,cAAc,EACjB,MAAM,UAAU,CAAC;AAIlB;;;GAGG;AACH,eAAO,MAAM,iBAAiB,QAAgB,CAAC;AAE/C;;GAEG;AACH,eAAO,MAAM,uBAAuB,EAAE,cASrC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,sBAAsB,EAAE,aAKpC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,sBAAsB,EAAE,aAMpC,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,iCAAiC,OAAO,CAAC;AAEtD;;GAEG;AACH,eAAO,MAAM,0BAA0B,EAAE,gBAKxC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/types/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EACR,OAAO,EACP,iBAAiB,EACjB,eAAe,EAClB,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAE/C;;GAEG;AACH,MAAM,WAAW,WAAW;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3B,IAAI,CAAC,EAAE;QACH,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAA;KACnB,CAAC;CACL;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC3B,YAAY;IACZ,kBAAkB,EAAE,OAAO,CAAC;IAC5B,gBAAgB;IAChB,mBAAmB,EAAE,OAAO,CAAC;IAC7B,iBAAiB;IACjB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,gBAAgB;IAChB,cAAc,EAAE,OAAO,CAAC;IACxB,2CAA2C;IAC3C,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,2CAA2C;IAC3C,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,qBAAqB;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,kBAAkB;IAClB,eAAe,EAAE,MAAM,CAAC;CAC3B;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC1B,WAAW;IACX,OAAO,EAAE,OAAO,CAAC;IACjB,mBAAmB;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,wBAAwB;IACxB,UAAU,CAAC,EAAE,iBAAiB,CAAC;IAC/B,YAAY;IACZ,cAAc,EAAE,OAAO,CAAC;IACxB,YAAY;IACZ,eAAe,EAAE,OAAO,CAAC;IACzB,oBAAoB;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc;IACd,MAAM,CAAC,EAAE,eAAe,CAAC;CAC5B;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC1B,
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/types/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EACR,OAAO,EACP,iBAAiB,EACjB,eAAe,EAClB,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAE/C;;GAEG;AACH,MAAM,WAAW,WAAW;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3B,IAAI,CAAC,EAAE;QACH,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAA;KACnB,CAAC;CACL;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC3B,YAAY;IACZ,kBAAkB,EAAE,OAAO,CAAC;IAC5B,gBAAgB;IAChB,mBAAmB,EAAE,OAAO,CAAC;IAC7B,iBAAiB;IACjB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,gBAAgB;IAChB,cAAc,EAAE,OAAO,CAAC;IACxB,2CAA2C;IAC3C,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,2CAA2C;IAC3C,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,qBAAqB;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,kBAAkB;IAClB,eAAe,EAAE,MAAM,CAAC;CAC3B;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC1B,WAAW;IACX,OAAO,EAAE,OAAO,CAAC;IACjB,mBAAmB;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,wBAAwB;IACxB,UAAU,CAAC,EAAE,iBAAiB,CAAC;IAC/B,YAAY;IACZ,cAAc,EAAE,OAAO,CAAC;IACxB,YAAY;IACZ,eAAe,EAAE,OAAO,CAAC;IACzB,oBAAoB;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc;IACd,MAAM,CAAC,EAAE,eAAe,CAAC;CAC5B;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC1B,4BAA4B;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW;IACX,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,cAAc;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,kBAAkB;IAClB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,kBAAkB;IAClB,mBAAmB,EAAE,MAAM,CAAC;CAC/B;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC7B,YAAY;IACZ,OAAO,EAAE,cAAc,CAAC;IACxB,WAAW;IACX,QAAQ,EAAE,cAAc,CAAC;IACzB,WAAW;IACX,OAAO,EAAE,aAAa,CAAC;IACvB,WAAW;IACX,OAAO,EAAE,aAAa,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACpC,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC;IACnC,OAAO,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;IACjC,OAAO,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;CACpC"}
|
|
@@ -10,7 +10,32 @@ export declare class HttpError extends Error {
|
|
|
10
10
|
readonly status?: number | undefined;
|
|
11
11
|
readonly response?: HttpResponse | undefined;
|
|
12
12
|
readonly request?: HttpRequestOptions | undefined;
|
|
13
|
-
|
|
13
|
+
readonly retriable: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* @param message 错误信息
|
|
16
|
+
* @param code 错误码
|
|
17
|
+
* @param status HTTP 状态码
|
|
18
|
+
* @param response 响应(不应携带不再需要的大体积响应体)
|
|
19
|
+
* @param request 请求选项
|
|
20
|
+
* @param retriable 是否允许重试,默认 true;重试无法改变结果的确定性失败应传 false
|
|
21
|
+
*/
|
|
22
|
+
constructor(message: string, code: string, status?: number | undefined, response?: HttpResponse | undefined, request?: HttpRequestOptions | undefined, retriable?: boolean);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* 响应体超过 maxResponseBodySize 限制的错误。
|
|
26
|
+
*
|
|
27
|
+
* - 错误码与文案对齐 axios 的 maxContentLength(ERR_BAD_RESPONSE)。
|
|
28
|
+
* - 重试不会改变响应体大小,因此标记为不可重试,避免每次重试都重复下载。
|
|
29
|
+
* - 不携带 response:仅保留状态码,防止超限数据随错误对象存活(与 axios 抛出该错误时一致)。
|
|
30
|
+
*/
|
|
31
|
+
export declare class ResponseTooLargeError extends HttpError {
|
|
32
|
+
readonly maxResponseBodySize: number;
|
|
33
|
+
/**
|
|
34
|
+
* @param maxResponseBodySize 响应体大小上限
|
|
35
|
+
* @param request 请求选项
|
|
36
|
+
* @param status 响应状态码(未知时不传)
|
|
37
|
+
*/
|
|
38
|
+
constructor(maxResponseBodySize: number, request: HttpRequestOptions, status?: number);
|
|
14
39
|
}
|
|
15
40
|
/**
|
|
16
41
|
* 安全验证错误
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../src/types/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAE3E;;GAEG;AACH,qBAAa,SAAU,SAAQ,KAAK;
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../src/types/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAE3E;;GAEG;AACH,qBAAa,SAAU,SAAQ,KAAK;aAWZ,IAAI,EAAE,MAAM;aACZ,MAAM,CAAC,EAAE,MAAM;aACf,QAAQ,CAAC,EAAE,YAAY;aACvB,OAAO,CAAC,EAAE,kBAAkB;aAC5B,SAAS;IAd7B;;;;;;;OAOG;gBAEC,OAAO,EAAE,MAAM,EACC,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,MAAM,YAAA,EACf,QAAQ,CAAC,EAAE,YAAY,YAAA,EACvB,OAAO,CAAC,EAAE,kBAAkB,YAAA,EAC5B,SAAS,UAAO;CAMvC;AAED;;;;;;GAMG;AACH,qBAAa,qBAAsB,SAAQ,SAAS;aAO5B,mBAAmB,EAAE,MAAM;IAN/C;;;;OAIG;gBAEiB,mBAAmB,EAAE,MAAM,EAC3C,OAAO,EAAE,kBAAkB,EAC3B,MAAM,CAAC,EAAE,MAAM;CAWtB;AAED;;GAEG;AACH,qBAAa,aAAc,SAAQ,KAAK;aAGhB,IAAI,EAAE,MAAM;aACZ,GAAG,CAAC,EAAE,MAAM;gBAF5B,OAAO,EAAE,MAAM,EACC,IAAI,EAAE,MAAM,EACZ,GAAG,CAAC,EAAE,MAAM,YAAA;CAMnC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request_response.d.ts","sourceRoot":"","sources":["../../../src/types/request_response.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;AAE1F;;GAEG;AACH,MAAM,MAAM,YAAY,GAClB,aAAa,GACb,MAAM,GACN,UAAU,GACV,MAAM,GACN,MAAM,GACN,QAAQ,GACR,UAAU,CAAC;AAEjB;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAC/B,cAAc;IACd,MAAM,EAAE,UAAU,CAAC;IACnB,aAAa;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU;IACV,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;IAC1C,eAAe;IACf,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;IACnD,UAAU;IACV,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,
|
|
1
|
+
{"version":3,"file":"request_response.d.ts","sourceRoot":"","sources":["../../../src/types/request_response.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;AAE1F;;GAEG;AACH,MAAM,MAAM,YAAY,GAClB,aAAa,GACb,MAAM,GACN,UAAU,GACV,MAAM,GACN,MAAM,GACN,QAAQ,GACR,UAAU,CAAC;AAEjB;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAC/B,cAAc;IACd,MAAM,EAAE,UAAU,CAAC;IACnB,aAAa;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU;IACV,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;IAC1C,eAAe;IACf,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;IACnD,UAAU;IACV,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,mCAAmC;IACnC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mBAAmB;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC3B,WAAW;IACX,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,iDAAiD;IACjD,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,GAAG;IACjC,YAAY;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,aAAa;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU;IACV,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,UAAU;IACV,IAAI,EAAE,CAAC,CAAC;IACR,WAAW;IACX,MAAM,EAAE,kBAAkB,CAAC;CAC9B"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * as IpRangeUtils from './ip.range';
|
|
2
2
|
export * as CommonUtils from './common';
|
|
3
3
|
export * as DomainUtils from './domain';
|
|
4
|
+
export * as RedirectUtils from './redirect';
|
|
4
5
|
export * as TraceContextUtils from './trace.context';
|
|
5
6
|
export { TraceContext } from './trace.context';
|
|
6
7
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,YAAY,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,WAAW,MAAM,UAAU,CAAC;AACxC,OAAO,KAAK,WAAW,MAAM,UAAU,CAAC;AACxC,OAAO,KAAK,iBAAiB,MAAM,iBAAiB,CAAC;AAGrD,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,YAAY,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,WAAW,MAAM,UAAU,CAAC;AACxC,OAAO,KAAK,WAAW,MAAM,UAAU,CAAC;AACxC,OAAO,KAAK,aAAa,MAAM,YAAY,CAAC;AAC5C,OAAO,KAAK,iBAAiB,MAAM,iBAAiB,CAAC;AAGrD,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { HttpRequestOptions, HttpResponse } from '../types';
|
|
2
|
+
import { EClientAdapter } from '../const';
|
|
3
|
+
/**
|
|
4
|
+
* 判断状态码是否需要跟跳。
|
|
5
|
+
* @param status HTTP 状态码
|
|
6
|
+
*/
|
|
7
|
+
export declare function isRedirectStatus(status: number): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* 根据响应里的 Location 构造下一跳请求。
|
|
10
|
+
* 没有 Location 时返回 undefined,调用方把当前 3xx 原样返回。
|
|
11
|
+
* axios:POST 的 301/302,以及非 GET/HEAD 的 303,改成 GET 并去掉 body 和 content-*。
|
|
12
|
+
* undici:只有非 HEAD 的 303 改成 GET 并去掉 body;303 仍去掉 content-*。
|
|
13
|
+
* 两边都去掉 host;跨域去掉 authorization、cookie、proxy-authorization。
|
|
14
|
+
* @param options 当前这一跳的请求
|
|
15
|
+
* @param response 当前这一跳的响应
|
|
16
|
+
* @param adapter 当前适配器
|
|
17
|
+
* @param configuredTimeout 客户端配置中的超时。请求未单独设置时使用。
|
|
18
|
+
*/
|
|
19
|
+
export declare function buildRedirectOptions(options: HttpRequestOptions, response: HttpResponse, adapter: EClientAdapter, configuredTimeout?: number): HttpRequestOptions | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* 跟跳共用同一次超时。把本跳已经花掉的时间从 redirectOptions.timeout 扣掉。
|
|
22
|
+
* @param redirectOptions 下一跳请求
|
|
23
|
+
* @param startTime 本跳开始时间
|
|
24
|
+
*/
|
|
25
|
+
export declare function consumeRedirectTimeout(redirectOptions: HttpRequestOptions, startTime: number): void;
|
|
26
|
+
//# sourceMappingURL=redirect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redirect.d.ts","sourceRoot":"","sources":["../../../src/utils/redirect.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACR,kBAAkB,EAClB,YAAY,EACf,MAAM,UAAU,CAAC;AAGlB,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAe1C;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAExD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAChC,OAAO,EAAE,kBAAkB,EAC3B,QAAQ,EAAE,YAAY,EACtB,OAAO,EAAE,cAAc,EACvB,iBAAiB,CAAC,EAAE,MAAM,GAC3B,kBAAkB,GAAG,SAAS,CA6ChC;AAgDD;;;;GAIG;AACH,wBAAgB,sBAAsB,CAClC,eAAe,EAAE,kBAAkB,EACnC,SAAS,EAAE,MAAM,GAClB,IAAI,CAgBN"}
|
package/package.json
CHANGED
|
@@ -7,16 +7,24 @@ import type {
|
|
|
7
7
|
HttpResponse,
|
|
8
8
|
ProxyConfig
|
|
9
9
|
} from '../types';
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
HttpError,
|
|
12
|
+
ResponseTooLargeError
|
|
13
|
+
} from '../types';
|
|
11
14
|
import { BaseHttpAdapter } from './base';
|
|
12
15
|
import type {
|
|
16
|
+
AxiosProgressEvent,
|
|
13
17
|
AxiosProxyConfig,
|
|
14
18
|
AxiosRequestConfig
|
|
15
19
|
} from 'axios';
|
|
16
20
|
import axios from 'axios';
|
|
17
21
|
import { HttpsProxyAgent } from 'https-proxy-agent';
|
|
18
22
|
import * as https from 'https';
|
|
19
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
MAX_RESPONSE_SIZE_TOLERANCE_RATIO,
|
|
25
|
+
EClientAdapter
|
|
26
|
+
} from '../const';
|
|
27
|
+
import { RedirectUtils } from '../utils';
|
|
20
28
|
|
|
21
29
|
|
|
22
30
|
/**
|
|
@@ -42,7 +50,9 @@ export class AxiosAdapter extends BaseHttpAdapter {
|
|
|
42
50
|
}
|
|
43
51
|
|
|
44
52
|
const url = this.buildUrl(options.url, options.params);
|
|
53
|
+
const timeout = options.timeout ?? this.requestConfig.timeout;
|
|
45
54
|
const axiosConfig = this.buildAxiosConfig(options, url);
|
|
55
|
+
const signalState = this.buildAxiosSignals(axiosConfig, options, timeout);
|
|
46
56
|
|
|
47
57
|
try {
|
|
48
58
|
const startTime = Date.now();
|
|
@@ -67,8 +77,80 @@ export class AxiosAdapter extends BaseHttpAdapter {
|
|
|
67
77
|
|
|
68
78
|
return response;
|
|
69
79
|
} catch (error: any) {
|
|
70
|
-
throw this.handleAxiosError(error, options);
|
|
80
|
+
throw signalState.abortError() ?? this.handleAxiosError(error, options);
|
|
81
|
+
} finally {
|
|
82
|
+
signalState.cleanup();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 创建整次请求超时与响应体超限两个中止信号,合并后写入 axiosConfig(原地修改)。
|
|
88
|
+
* 返回 catch 中还原中止原因的 abortError(非本方法中止时返回 undefined),以及 finally 中清理定时器的 cleanup。
|
|
89
|
+
*/
|
|
90
|
+
private buildAxiosSignals(axiosConfig: AxiosRequestConfig, options: HttpRequestOptions, timeout?: number): {
|
|
91
|
+
abortError: () => HttpError | undefined,
|
|
92
|
+
cleanup: () => void
|
|
93
|
+
} {
|
|
94
|
+
// maxRedirects 为 0 时 axios 使用原生 http 而不是 follow-redirects。
|
|
95
|
+
// 原生 timeout 是套接字空闲超时,在比如持续上传的场景下会被不断重置,需要使用中止信号来结束请求。
|
|
96
|
+
const deadline = this.createDeadline(timeout);
|
|
97
|
+
const bodySizeGuard = this.createBodySizeGuard(options);
|
|
98
|
+
const signals = [deadline.signal, bodySizeGuard.signal].filter((item): item is AbortSignal => !!item);
|
|
99
|
+
axiosConfig.signal = signals.length > 1 ? AbortSignal.any(signals) : signals[0];
|
|
100
|
+
axiosConfig.onDownloadProgress = bodySizeGuard.onDownloadProgress;
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
abortError: () => {
|
|
104
|
+
if (deadline.timedOut()) {
|
|
105
|
+
return new HttpError(
|
|
106
|
+
`timeout of ${ timeout }ms exceeded`,
|
|
107
|
+
'ECONNABORTED',
|
|
108
|
+
undefined,
|
|
109
|
+
undefined,
|
|
110
|
+
options
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
if (bodySizeGuard.exceeded()) {
|
|
114
|
+
return new ResponseTooLargeError(this.requestConfig.maxResponseBodySize, options);
|
|
115
|
+
}
|
|
116
|
+
return undefined;
|
|
117
|
+
},
|
|
118
|
+
cleanup: deadline.cleanup
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* 创建 Content-Length 预检。
|
|
124
|
+
*
|
|
125
|
+
* axios 的 Node http 适配器只按已收到的分块累计 maxContentLength,响应头已声明超限时仍会下载到上限才中断。
|
|
126
|
+
* 这里借助 onDownloadProgress 的 total(响应头 Content-Length),在首个分块到达时就中止请求。
|
|
127
|
+
* - 无 Content-Length(chunked)时不触发,由 axios 的分块累计兜底;
|
|
128
|
+
* - stream 响应不启用,避免额外插入转换流;
|
|
129
|
+
* - total 是线上字节,对几乎不可压缩的 gzip 响应可能略大于解压后的大小,
|
|
130
|
+
* 因此比较时保留 MAX_RESPONSE_SIZE_TOLERANCE_RATIO 的容差。
|
|
131
|
+
*/
|
|
132
|
+
private createBodySizeGuard(options: HttpRequestOptions): {
|
|
133
|
+
signal?: AbortSignal,
|
|
134
|
+
onDownloadProgress?: (event: AxiosProgressEvent) => void,
|
|
135
|
+
exceeded: () => boolean
|
|
136
|
+
} {
|
|
137
|
+
const maxRespBodySize = this.resolveMaxResponseBodySize();
|
|
138
|
+
if (options.responseType === 'stream' || maxRespBodySize === undefined) {
|
|
139
|
+
return { exceeded: () => false };
|
|
71
140
|
}
|
|
141
|
+
|
|
142
|
+
const controller = new AbortController();
|
|
143
|
+
let exceeded = false;
|
|
144
|
+
return {
|
|
145
|
+
signal: controller.signal,
|
|
146
|
+
onDownloadProgress: (event) => {
|
|
147
|
+
if (typeof event.total === 'number' && event.total > maxRespBodySize + Math.ceil(maxRespBodySize * MAX_RESPONSE_SIZE_TOLERANCE_RATIO)) {
|
|
148
|
+
exceeded = true;
|
|
149
|
+
controller.abort();
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
exceeded: () => exceeded
|
|
153
|
+
};
|
|
72
154
|
}
|
|
73
155
|
|
|
74
156
|
/**
|
|
@@ -81,14 +163,20 @@ export class AxiosAdapter extends BaseHttpAdapter {
|
|
|
81
163
|
method: options.method.toLowerCase(),
|
|
82
164
|
url,
|
|
83
165
|
headers: options.headers || {},
|
|
84
|
-
timeout: options.timeout ?? this.requestConfig.timeout,
|
|
85
166
|
maxRedirects: this.requestConfig.maxRedirects,
|
|
167
|
+
validateStatus: (status: number) => {
|
|
168
|
+
return (status >= 200 && status < 300) || status === 304 || RedirectUtils.isRedirectStatus(status);
|
|
169
|
+
},
|
|
86
170
|
maxContentLength: this.requestConfig.maxResponseBodySize,
|
|
87
171
|
maxBodyLength: this.requestConfig.maxRequestBodySize,
|
|
88
172
|
httpsAgent: new https.Agent({
|
|
89
173
|
rejectUnauthorized: this.securityConfig.rejectUnauthorized
|
|
90
174
|
})
|
|
91
175
|
};
|
|
176
|
+
const timeout = options.timeout ?? this.requestConfig.timeout;
|
|
177
|
+
if (timeout !== undefined) {
|
|
178
|
+
axiosConfig.timeout = timeout;
|
|
179
|
+
}
|
|
92
180
|
|
|
93
181
|
// 设置请求体
|
|
94
182
|
if (options.body !== undefined) {
|
|
@@ -178,6 +266,12 @@ export class AxiosAdapter extends BaseHttpAdapter {
|
|
|
178
266
|
* @param options 请求选项
|
|
179
267
|
*/
|
|
180
268
|
private handleAxiosError(error: any, options: HttpRequestOptions): HttpError {
|
|
269
|
+
// axios 没有为 maxContentLength 超限提供专用错误码(与 5xx 共用 ERR_BAD_RESPONSE),
|
|
270
|
+
// 只能靠固定的消息前缀识别,识别后转成不可重试的 ResponseTooLargeError。
|
|
271
|
+
if (error.isAxiosError && error.code === 'ERR_BAD_RESPONSE' && error.message.startsWith('maxContentLength size of')) {
|
|
272
|
+
return new ResponseTooLargeError(this.requestConfig.maxResponseBodySize, options);
|
|
273
|
+
}
|
|
274
|
+
|
|
181
275
|
if (error.isAxiosError) {
|
|
182
276
|
return new HttpError(
|
|
183
277
|
error.message,
|
|
@@ -223,4 +317,34 @@ export class AxiosAdapter extends BaseHttpAdapter {
|
|
|
223
317
|
|
|
224
318
|
return config;
|
|
225
319
|
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* 按墙钟时间中止请求,到点后 timedOut 为 true
|
|
323
|
+
* @param timeout 超时时间,单位毫秒
|
|
324
|
+
* @returns 中止信号、是否超时、清理函数
|
|
325
|
+
*/
|
|
326
|
+
private createDeadline(timeout?: number): {
|
|
327
|
+
signal?: AbortSignal,
|
|
328
|
+
timedOut: () => boolean,
|
|
329
|
+
cleanup: () => void
|
|
330
|
+
} {
|
|
331
|
+
if (typeof timeout !== 'number' || !Number.isFinite(timeout) || timeout <= 0) {
|
|
332
|
+
return {
|
|
333
|
+
timedOut: () => false,
|
|
334
|
+
cleanup: () => undefined
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
const controller = new AbortController();
|
|
338
|
+
let timedOut = false;
|
|
339
|
+
const timer = setTimeout(() => {
|
|
340
|
+
timedOut = true;
|
|
341
|
+
controller.abort();
|
|
342
|
+
}, timeout);
|
|
343
|
+
timer.unref?.();
|
|
344
|
+
return {
|
|
345
|
+
signal: controller.signal,
|
|
346
|
+
timedOut: () => timedOut,
|
|
347
|
+
cleanup: () => clearTimeout(timer)
|
|
348
|
+
};
|
|
349
|
+
}
|
|
226
350
|
}
|
package/src/adapter/base.ts
CHANGED
|
@@ -10,11 +10,14 @@ import type {
|
|
|
10
10
|
SecurityConfig,
|
|
11
11
|
ILogger
|
|
12
12
|
} from '../types';
|
|
13
|
+
import { HttpError } from '../types';
|
|
13
14
|
import { CommonUtils } from '../utils';
|
|
14
15
|
import type { EClientAdapter } from '../const';
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* HTTP 适配器抽象基类
|
|
19
|
+
*
|
|
20
|
+
* g-7114106599:不允许直接跟跳3XX,防止SSRF。调用方需要自行处理 3XX 响应。
|
|
18
21
|
*/
|
|
19
22
|
export abstract class BaseHttpAdapter implements IHttpAdapter {
|
|
20
23
|
protected requestConfig: RequestConfig;
|
|
@@ -22,7 +25,7 @@ export abstract class BaseHttpAdapter implements IHttpAdapter {
|
|
|
22
25
|
protected logger?: ILogger;
|
|
23
26
|
|
|
24
27
|
constructor(requestConfig: RequestConfig, securityConfig: SecurityConfig, logger?: ILogger) {
|
|
25
|
-
this.requestConfig = requestConfig;
|
|
28
|
+
this.requestConfig = { ...requestConfig, maxRedirects: 0 };
|
|
26
29
|
this.securityConfig = securityConfig;
|
|
27
30
|
this.logger = logger;
|
|
28
31
|
}
|
|
@@ -85,6 +88,16 @@ export abstract class BaseHttpAdapter implements IHttpAdapter {
|
|
|
85
88
|
error: error instanceof Error ? error.message : String(error)
|
|
86
89
|
});
|
|
87
90
|
|
|
91
|
+
// 默认所有错误均可重试;仅在 HttpError 的 retriable 为 false 时终止重试
|
|
92
|
+
if (error instanceof HttpError && !error.retriable) {
|
|
93
|
+
this.logger?.debug('HTTP request error is not retriable, skip remaining retries', {
|
|
94
|
+
method: options.method,
|
|
95
|
+
url: options.url,
|
|
96
|
+
attempt: attempt + 1
|
|
97
|
+
});
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
|
|
88
101
|
// 最后一次尝试,不再重试
|
|
89
102
|
if (attempt === retries) {
|
|
90
103
|
break;
|
|
@@ -106,6 +119,14 @@ export abstract class BaseHttpAdapter implements IHttpAdapter {
|
|
|
106
119
|
throw lastError;
|
|
107
120
|
}
|
|
108
121
|
|
|
122
|
+
/**
|
|
123
|
+
* @returns 有效的响应体大小上限;不限制时返回 undefined
|
|
124
|
+
*/
|
|
125
|
+
protected resolveMaxResponseBodySize(): number | undefined {
|
|
126
|
+
const max = this.requestConfig.maxResponseBodySize;
|
|
127
|
+
return typeof max === 'number' && max > 0 ? max : undefined;
|
|
128
|
+
}
|
|
129
|
+
|
|
109
130
|
/**
|
|
110
131
|
* 更新请求配置
|
|
111
132
|
* @param config 新的请求配置
|