@wiajs/req 1.7.7
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 +1023 -0
- package/LICENSE +7 -0
- package/MIGRATION_GUIDE.md +3 -0
- package/README.md +1645 -0
- package/SECURITY.md +6 -0
- package/dist/node/req.cjs +4397 -0
- package/dist/node/req.mjs +3551 -0
- package/dist/web/req.mjs +2609 -0
- package/index.d.cts +545 -0
- package/index.d.ts +565 -0
- package/index.js +43 -0
- package/lib/adapters/README.md +37 -0
- package/lib/adapters/adapters.js +57 -0
- package/lib/adapters/fetch.js +229 -0
- package/lib/adapters/http.js +552 -0
- package/lib/adapters/xhr.js +200 -0
- package/lib/axios.js +89 -0
- package/lib/cancel/CancelToken.js +106 -0
- package/lib/cancel/CanceledError.js +20 -0
- package/lib/cancel/isCancel.js +4 -0
- package/lib/core/Axios.js +359 -0
- package/lib/core/AxiosError.js +89 -0
- package/lib/core/AxiosHeaders.js +243 -0
- package/lib/core/InterceptorManager.js +59 -0
- package/lib/core/README.md +8 -0
- package/lib/core/buildFullPath.js +18 -0
- package/lib/core/dispatchRequest.js +72 -0
- package/lib/core/mergeConfig.js +98 -0
- package/lib/core/settle.js +21 -0
- package/lib/core/transformData.js +22 -0
- package/lib/defaults/index.js +136 -0
- package/lib/defaults/transitional.js +6 -0
- package/lib/env/README.md +3 -0
- package/lib/env/classes/FormData.js +2 -0
- package/lib/env/data.js +1 -0
- package/lib/helpers/AxiosTransformStream.js +116 -0
- package/lib/helpers/AxiosURLSearchParams.js +50 -0
- package/lib/helpers/HttpStatusCode.js +69 -0
- package/lib/helpers/README.md +7 -0
- package/lib/helpers/ZlibHeaderTransformStream.js +22 -0
- package/lib/helpers/bind.js +6 -0
- package/lib/helpers/buildURL.js +42 -0
- package/lib/helpers/callbackify.js +14 -0
- package/lib/helpers/combineURLs.js +11 -0
- package/lib/helpers/composeSignals.js +37 -0
- package/lib/helpers/cookies.js +29 -0
- package/lib/helpers/deprecatedMethod.js +18 -0
- package/lib/helpers/formDataToJSON.js +78 -0
- package/lib/helpers/formDataToStream.js +77 -0
- package/lib/helpers/fromDataURI.js +44 -0
- package/lib/helpers/isAbsoluteURL.js +13 -0
- package/lib/helpers/isAxiosError.js +11 -0
- package/lib/helpers/isURLSameOrigin.js +50 -0
- package/lib/helpers/null.js +2 -0
- package/lib/helpers/parseHeaders.js +61 -0
- package/lib/helpers/parseProtocol.js +5 -0
- package/lib/helpers/progressEventReducer.js +54 -0
- package/lib/helpers/readBlob.js +13 -0
- package/lib/helpers/resolveConfig.js +45 -0
- package/lib/helpers/speedometer.js +40 -0
- package/lib/helpers/spread.js +26 -0
- package/lib/helpers/throttle.js +41 -0
- package/lib/helpers/toFormData.js +175 -0
- package/lib/helpers/toURLEncodedForm.js +15 -0
- package/lib/helpers/trackStream.js +75 -0
- package/lib/helpers/validator.js +84 -0
- package/lib/platform/browser/classes/Blob.js +2 -0
- package/lib/platform/browser/classes/FormData.js +2 -0
- package/lib/platform/browser/classes/URLSearchParams.js +3 -0
- package/lib/platform/browser/index.js +19 -0
- package/lib/platform/common/utils.js +37 -0
- package/lib/platform/index.js +6 -0
- package/lib/platform/node/classes/FormData.js +2 -0
- package/lib/platform/node/classes/URLSearchParams.js +3 -0
- package/lib/platform/node/index.js +16 -0
- package/lib/req.js +67 -0
- package/lib/utils.js +635 -0
- package/package.json +214 -0
|
@@ -0,0 +1,552 @@
|
|
|
1
|
+
import request from "@wiajs/request";
|
|
2
|
+
import Agent from "@wiajs/agent";
|
|
3
|
+
import { log as Log, name } from "@wiajs/log";
|
|
4
|
+
import utils from "../utils.js";
|
|
5
|
+
import settle from "../core/settle.js";
|
|
6
|
+
import buildFullPath from "../core/buildFullPath.js";
|
|
7
|
+
import buildURL from "../helpers/buildURL.js";
|
|
8
|
+
// import {getProxyForUrl} from 'proxy-from-env';
|
|
9
|
+
import util from "node:util";
|
|
10
|
+
// import followRedirects from 'follow-redirects';
|
|
11
|
+
// import Redirect from '../request/index.js'
|
|
12
|
+
import zlib from "node:zlib";
|
|
13
|
+
import { VERSION } from "../env/data.js";
|
|
14
|
+
import transitionalDefaults from "../defaults/transitional.js";
|
|
15
|
+
import AxiosError from "../core/AxiosError.js";
|
|
16
|
+
import CanceledError from "../cancel/CanceledError.js";
|
|
17
|
+
import platform from "../platform/index.js";
|
|
18
|
+
import fromDataURI from "../helpers/fromDataURI.js";
|
|
19
|
+
import stream from "node:stream";
|
|
20
|
+
import AxiosHeaders from "../core/AxiosHeaders.js";
|
|
21
|
+
import AxiosTransformStream from "../helpers/AxiosTransformStream.js";
|
|
22
|
+
import { EventEmitter } from "node:events";
|
|
23
|
+
import formDataToStream from "../helpers/formDataToStream.js";
|
|
24
|
+
import readBlob from "../helpers/readBlob.js";
|
|
25
|
+
import callbackify from "../helpers/callbackify.js";
|
|
26
|
+
import { progressEventReducer, progressEventDecorator, asyncDecorator } from "../helpers/progressEventReducer.js";
|
|
27
|
+
// import Agent from '../fea/agent.js'
|
|
28
|
+
const log = Log({
|
|
29
|
+
env: `wia:req:${name(import.meta.url)}`
|
|
30
|
+
});
|
|
31
|
+
const isBrotliSupported = utils.isFunction(zlib.createBrotliDecompress);
|
|
32
|
+
const isHttps = /https:?/;
|
|
33
|
+
const supportedProtocols = platform.protocols.map((protocol)=>`${protocol}:`);
|
|
34
|
+
const isHttpAdapterSupported = typeof process !== "undefined" && utils.kindOf(process) === "process";
|
|
35
|
+
/**
|
|
36
|
+
* !+++
|
|
37
|
+
* 将request 函数改为类,请求拆分为 init 初始化和 请求执行,
|
|
38
|
+
* 如需重新发起请求时,无需重新初始化
|
|
39
|
+
*/ let HttpAdapter = class HttpAdapter {
|
|
40
|
+
/**
|
|
41
|
+
*
|
|
42
|
+
* @param {*} config
|
|
43
|
+
*/ constructor(config){
|
|
44
|
+
this.isDone = false;
|
|
45
|
+
this.rejected = false;
|
|
46
|
+
/** @type {*} */ this.req = null;
|
|
47
|
+
/** @type {*} */ this.config = null;
|
|
48
|
+
/** @type {*} */ this.data = null;
|
|
49
|
+
/** @type {*} */ this.transport = null;
|
|
50
|
+
this.config = config;
|
|
51
|
+
// temporary internal emitter until the AxiosRequest class will be implemented
|
|
52
|
+
this.emitter = new EventEmitter();
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
*
|
|
56
|
+
* @param {number} code
|
|
57
|
+
* @returns
|
|
58
|
+
*/ noBody(code) {
|
|
59
|
+
return this.method === "HEAD" || // Informational
|
|
60
|
+
code >= 100 && code < 200 || // No Content
|
|
61
|
+
code === 204 || // Not Modified
|
|
62
|
+
code === 304;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* 发起终止事件
|
|
66
|
+
* @param {*} reason
|
|
67
|
+
*/ abort(reason) {
|
|
68
|
+
this.emitter.emit("abort", !reason || reason.type ? new CanceledError(null, this.config, this.req) : reason);
|
|
69
|
+
}
|
|
70
|
+
onFinished() {
|
|
71
|
+
const { config, emitter, abort } = this;
|
|
72
|
+
config?.cancelToken?.unsubscribe(abort);
|
|
73
|
+
config?.signal?.removeEventListener("abort", abort);
|
|
74
|
+
emitter?.removeAllListeners();
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
*
|
|
78
|
+
* @param {*} value
|
|
79
|
+
* @param {*} isRejected
|
|
80
|
+
*/ onDone(value, isRejected) {
|
|
81
|
+
this.isDone = true;
|
|
82
|
+
if (isRejected) {
|
|
83
|
+
this.rejected = true;
|
|
84
|
+
this.onFinished();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
*
|
|
89
|
+
* @param {*} value
|
|
90
|
+
* @param {*} isRejected
|
|
91
|
+
* @returns
|
|
92
|
+
*/ done(value, isRejected) {
|
|
93
|
+
if (this.isDone) return;
|
|
94
|
+
this.isDone = true;
|
|
95
|
+
this?.onDone(value, isRejected);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* 初始化,生成 options 供请求调用
|
|
99
|
+
* @returns {*} options
|
|
100
|
+
*/ async init() {
|
|
101
|
+
// biome-ignore lint/complexity/noUselessThisAlias: <explanation>
|
|
102
|
+
const _ = this;
|
|
103
|
+
const { config } = _;
|
|
104
|
+
let { data, lookup, family } = config;
|
|
105
|
+
const method = config.method.toUpperCase();
|
|
106
|
+
_.method = method;
|
|
107
|
+
if (lookup) {
|
|
108
|
+
const _lookup = callbackify(lookup, /** @param {*} value */ (value)=>utils.isArray(value) ? value : [
|
|
109
|
+
value
|
|
110
|
+
]);
|
|
111
|
+
// hotfix to support opt.all option which is required for node 20.x
|
|
112
|
+
/**
|
|
113
|
+
* @param {string} hostname
|
|
114
|
+
* @param {*} opt
|
|
115
|
+
* @param {*} cb
|
|
116
|
+
*/ lookup = (hostname, opt, cb)=>{
|
|
117
|
+
_lookup(hostname, opt, (err, arg0, arg1)=>{
|
|
118
|
+
if (err) return cb(err);
|
|
119
|
+
const addresses = utils.isArray(arg0) ? arg0.map((addr)=>buildAddressEntry(addr)) : [
|
|
120
|
+
buildAddressEntry(arg0, arg1)
|
|
121
|
+
];
|
|
122
|
+
opt.all ? cb(err, addresses) : cb(err, addresses[0].address, addresses[0].family);
|
|
123
|
+
});
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
if (config.cancelToken || config.signal) {
|
|
127
|
+
config.cancelToken?.subscribe(_.abort);
|
|
128
|
+
if (config.signal) {
|
|
129
|
+
if (config.signal.aborted) _.abort();
|
|
130
|
+
else config.signal.addEventListener("abort", _.abort);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// Parse url
|
|
134
|
+
const fullPath = buildFullPath(config.baseURL, config.url);
|
|
135
|
+
// 'https://user:pass@sub.host.com:8080/p/a/t/h?query=string#hash'
|
|
136
|
+
const parsed = new URL(fullPath, platform.hasBrowserEnv ? platform.origin : undefined);
|
|
137
|
+
// http: or https:
|
|
138
|
+
const protocol = parsed.protocol || supportedProtocols[0];
|
|
139
|
+
_.protocol = protocol;
|
|
140
|
+
if (protocol === "data:" && method !== "GET") {
|
|
141
|
+
// throw error
|
|
142
|
+
const response = {
|
|
143
|
+
status: 405,
|
|
144
|
+
statusText: "method not allowed",
|
|
145
|
+
headers: {},
|
|
146
|
+
config
|
|
147
|
+
};
|
|
148
|
+
throw new AxiosError(`Request failed with status code ${response.status}`, [
|
|
149
|
+
AxiosError.ERR_BAD_REQUEST,
|
|
150
|
+
AxiosError.ERR_BAD_RESPONSE
|
|
151
|
+
][Math.floor(response.status / 100) - 4], response.config, response.request, response);
|
|
152
|
+
}
|
|
153
|
+
if (supportedProtocols.indexOf(protocol) === -1) {
|
|
154
|
+
throw new AxiosError(`Unsupported protocol ${protocol}`, AxiosError.ERR_BAD_REQUEST, config);
|
|
155
|
+
}
|
|
156
|
+
const headers = AxiosHeaders.from(config.headers).normalize();
|
|
157
|
+
// Set User-Agent (required by some servers)
|
|
158
|
+
// See https://github.com/axios/axios/issues/69
|
|
159
|
+
// User-Agent is specified; handle case where no UA header is desired
|
|
160
|
+
// Only set header if it hasn't been set in config
|
|
161
|
+
// ! headers.set('User-Agent', 'axios/' + VERSION, false);
|
|
162
|
+
headers.set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.35", false);
|
|
163
|
+
const { onDownloadProgress, onUploadProgress, maxRate } = config;
|
|
164
|
+
// support for spec compliant FormData objects
|
|
165
|
+
if (utils.isSpecCompliantForm(data)) {
|
|
166
|
+
const userBoundary = headers.getContentType(/boundary=([-_\w\d]{10,70})/i);
|
|
167
|
+
data = formDataToStream(data, /** @param {*} formHeaders */ (formHeaders)=>headers.set(formHeaders), {
|
|
168
|
+
tag: `axios-${VERSION}-boundary`,
|
|
169
|
+
boundary: userBoundary?.[1] || undefined
|
|
170
|
+
});
|
|
171
|
+
// support for https://www.npmjs.com/package/form-data api
|
|
172
|
+
} else if (utils.isFormData(data) && utils.isFunction(data.getHeaders)) {
|
|
173
|
+
headers.set(data.getHeaders());
|
|
174
|
+
if (!headers.hasContentLength()) {
|
|
175
|
+
try {
|
|
176
|
+
const knownLength = await util.promisify(data.getLength).call(data);
|
|
177
|
+
Number.isFinite(knownLength) && knownLength >= 0 && headers.setContentLength(knownLength);
|
|
178
|
+
/*eslint no-empty:0*/ } catch (e) {}
|
|
179
|
+
}
|
|
180
|
+
} else if (utils.isBlob(data)) {
|
|
181
|
+
data.size && headers.setContentType(data.type || "application/octet-stream");
|
|
182
|
+
headers.setContentLength(data.size || 0);
|
|
183
|
+
data = stream.Readable.from(readBlob(data));
|
|
184
|
+
} else if (data && !utils.isStream(data)) {
|
|
185
|
+
if (Buffer.isBuffer(data)) {
|
|
186
|
+
// Nothing to do...
|
|
187
|
+
} else if (utils.isArrayBuffer(data)) {
|
|
188
|
+
data = Buffer.from(new Uint8Array(data));
|
|
189
|
+
} else if (utils.isString(data)) {
|
|
190
|
+
data = Buffer.from(data, "utf-8");
|
|
191
|
+
} else {
|
|
192
|
+
throw new AxiosError("Data after transformation must be a string, an ArrayBuffer, a Buffer, or a Stream", AxiosError.ERR_BAD_REQUEST, config);
|
|
193
|
+
}
|
|
194
|
+
// Add Content-Length header if data exists
|
|
195
|
+
headers.setContentLength(data.length, false);
|
|
196
|
+
if (config.maxBodyLength > -1 && data.length > config.maxBodyLength) {
|
|
197
|
+
throw new AxiosError("Request body larger than maxBodyLength limit", AxiosError.ERR_BAD_REQUEST, config);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
const contentLength = utils.toFiniteNumber(headers.getContentLength());
|
|
201
|
+
let maxUploadRate;
|
|
202
|
+
let maxDownloadRate;
|
|
203
|
+
if (utils.isArray(maxRate)) [maxUploadRate, maxDownloadRate] = maxRate;
|
|
204
|
+
else {
|
|
205
|
+
maxUploadRate = maxRate;
|
|
206
|
+
maxDownloadRate = maxRate;
|
|
207
|
+
}
|
|
208
|
+
_.maxUploadRate = maxUploadRate;
|
|
209
|
+
_.maxDownloadRate = maxDownloadRate;
|
|
210
|
+
if (data && (onUploadProgress || maxUploadRate)) {
|
|
211
|
+
if (!utils.isStream(data)) {
|
|
212
|
+
data = stream.Readable.from(data, {
|
|
213
|
+
objectMode: false
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
data = stream.pipeline([
|
|
217
|
+
data,
|
|
218
|
+
new AxiosTransformStream({
|
|
219
|
+
maxRate: utils.toFiniteNumber(maxUploadRate)
|
|
220
|
+
})
|
|
221
|
+
], utils.noop);
|
|
222
|
+
onUploadProgress && data.on("progress", flushOnFinish(data, progressEventDecorator(contentLength, progressEventReducer(asyncDecorator(onUploadProgress), false, 3))));
|
|
223
|
+
}
|
|
224
|
+
// HTTP basic authentication
|
|
225
|
+
let auth;
|
|
226
|
+
if (config.auth) {
|
|
227
|
+
const username = config.auth.username || "";
|
|
228
|
+
const password = config.auth.password || "";
|
|
229
|
+
auth = `${username}:${password}`;
|
|
230
|
+
}
|
|
231
|
+
if (!auth && parsed.username) {
|
|
232
|
+
const urlUsername = parsed.username;
|
|
233
|
+
const urlPassword = parsed.password;
|
|
234
|
+
auth = `${urlUsername}:${urlPassword}`;
|
|
235
|
+
}
|
|
236
|
+
auth && headers.delete("authorization");
|
|
237
|
+
let path;
|
|
238
|
+
try {
|
|
239
|
+
path = buildURL(parsed.pathname + parsed.search, config.params, config.paramsSerializer).replace(/^\?/, "");
|
|
240
|
+
} catch (err) {
|
|
241
|
+
/** @type {*} */ const customErr = new Error(err.message);
|
|
242
|
+
customErr.config = config;
|
|
243
|
+
customErr.url = config.url;
|
|
244
|
+
customErr.exists = true;
|
|
245
|
+
throw customErr;
|
|
246
|
+
}
|
|
247
|
+
headers.set("Accept-Encoding", `gzip, compress, deflate${isBrotliSupported ? ", br" : ""}`, false);
|
|
248
|
+
/** @type {*} */ const options = {
|
|
249
|
+
path,
|
|
250
|
+
method,
|
|
251
|
+
headers: headers.toJSON(),
|
|
252
|
+
agents: {
|
|
253
|
+
http: config.httpAgent,
|
|
254
|
+
https: config.httpsAgent
|
|
255
|
+
},
|
|
256
|
+
auth,
|
|
257
|
+
protocol,
|
|
258
|
+
family,
|
|
259
|
+
beforeRedirect: dispatchBeforeRedirect,
|
|
260
|
+
beforeRedirects: {}
|
|
261
|
+
};
|
|
262
|
+
// cacheable-lookup integration hotfix
|
|
263
|
+
if (!utils.isUndefined(lookup)) options.lookup = lookup;
|
|
264
|
+
if (config.socketPath) options.socketPath = config.socketPath;
|
|
265
|
+
else {
|
|
266
|
+
options.hostname = parsed.hostname.startsWith("[") ? parsed.hostname.slice(1, -1) : parsed.hostname;
|
|
267
|
+
options.port = parsed.port;
|
|
268
|
+
// ! proxy
|
|
269
|
+
if (config.agent) options.agents = new Agent(config.agent);
|
|
270
|
+
}
|
|
271
|
+
// 执行请求的具体对象
|
|
272
|
+
_.transport = config.transport;
|
|
273
|
+
const isHttpsRequest = isHttps.test(options.protocol);
|
|
274
|
+
options.agent = isHttpsRequest ? config.httpsAgent : config.httpAgent;
|
|
275
|
+
if (config.maxBodyLength > -1) options.maxBodyLength = config.maxBodyLength;
|
|
276
|
+
else options.maxBodyLength = Number.POSITIVE_INFINITY;
|
|
277
|
+
// maxRedirects 缺省 21,不跳转,直接使用系统http or https,不支持 stream
|
|
278
|
+
// if (config.maxRedirects === 0 && !config.stream) transport = isHttpsRequest ? https : http;
|
|
279
|
+
// 自动跳转
|
|
280
|
+
// else {
|
|
281
|
+
// 支持跳转或stream,需使用 http、https 封装类
|
|
282
|
+
if (config.maxRedirects) options.maxRedirects = config.maxRedirects;
|
|
283
|
+
if (config.beforeRedirect) options.beforeRedirects.config = config.beforeRedirect;
|
|
284
|
+
if (config.insecureHTTPParser) options.insecureHTTPParser = config.insecureHTTPParser;
|
|
285
|
+
_.options = options;
|
|
286
|
+
_.data = data;
|
|
287
|
+
log({
|
|
288
|
+
config
|
|
289
|
+
}, "init");
|
|
290
|
+
return options;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* 执行请求
|
|
294
|
+
* 需抛出内部异常
|
|
295
|
+
* @param {Axios} axios 实例
|
|
296
|
+
* @returns {Promise<*>}
|
|
297
|
+
*/ async request(axios) {
|
|
298
|
+
/** @type {*} */ // biome-ignore lint/style/useConst: <explanation>
|
|
299
|
+
let R;
|
|
300
|
+
// biome-ignore lint/complexity/noUselessThisAlias: <explanation>
|
|
301
|
+
const _ = this;
|
|
302
|
+
try {
|
|
303
|
+
await _.init();
|
|
304
|
+
const { transport, protocol, config, options, data, abort, emitter, maxDownloadRate } = _;
|
|
305
|
+
const { responseType, responseEncoding, onDownloadProgress } = config;
|
|
306
|
+
if (protocol === "data:") {
|
|
307
|
+
/** @type {*} */ let convertedData;
|
|
308
|
+
try {
|
|
309
|
+
convertedData = fromDataURI(config.url, responseType === "blob", {
|
|
310
|
+
Blob: config.env?.Blob
|
|
311
|
+
});
|
|
312
|
+
} catch (err) {
|
|
313
|
+
throw AxiosError.from(err, AxiosError.ERR_BAD_REQUEST, config);
|
|
314
|
+
}
|
|
315
|
+
if (responseType === "text") {
|
|
316
|
+
convertedData = convertedData.toString(responseEncoding);
|
|
317
|
+
if (!responseEncoding || responseEncoding === "utf8") {
|
|
318
|
+
convertedData = utils.stripBOM(convertedData);
|
|
319
|
+
}
|
|
320
|
+
} else if (responseType === "stream") {
|
|
321
|
+
convertedData = stream.Readable.from(convertedData);
|
|
322
|
+
}
|
|
323
|
+
// 返回响应
|
|
324
|
+
R = {
|
|
325
|
+
data: convertedData,
|
|
326
|
+
status: 200,
|
|
327
|
+
statusText: "OK",
|
|
328
|
+
headers: new AxiosHeaders(),
|
|
329
|
+
config
|
|
330
|
+
};
|
|
331
|
+
} else {
|
|
332
|
+
let transformStream;
|
|
333
|
+
if (onDownloadProgress || maxDownloadRate) {
|
|
334
|
+
transformStream = new AxiosTransformStream({
|
|
335
|
+
maxRate: utils.toFiniteNumber(maxDownloadRate)
|
|
336
|
+
});
|
|
337
|
+
onDownloadProgress && transformStream.on("progress", flushOnFinish(transformStream, progressEventDecorator(transformStream.responseLength, progressEventReducer(asyncDecorator(onDownloadProgress), true, 3))));
|
|
338
|
+
}
|
|
339
|
+
options.transformStream = transformStream;
|
|
340
|
+
// 发起异步请求
|
|
341
|
+
R = await new Promise((resolve, reject)=>{
|
|
342
|
+
_.emitter.once("abort", reject);
|
|
343
|
+
options.stream = config.stream;
|
|
344
|
+
options.decompress = config.decompress;
|
|
345
|
+
// Create the request,promise false: return stream
|
|
346
|
+
// log.debug('request', {options});
|
|
347
|
+
const req = transport ? transport.request(options) : request(options);
|
|
348
|
+
if (!req) return reject(new AxiosError("Request failed.", AxiosError.ERR_BAD_REQUEST, config));
|
|
349
|
+
_.req = req;
|
|
350
|
+
emitter.once("abort", (err)=>{
|
|
351
|
+
log("onabort");
|
|
352
|
+
reject(err);
|
|
353
|
+
req.destroy(err);
|
|
354
|
+
});
|
|
355
|
+
// Handle errors
|
|
356
|
+
req.on("error", /** @param {*} err */ (err)=>{
|
|
357
|
+
log("onerror");
|
|
358
|
+
// @todo remove
|
|
359
|
+
// if (req.aborted && err.code !== AxiosError.ERR_FR_TOO_MANY_REDIRECTS) return;
|
|
360
|
+
reject(AxiosError.from(err, null, config, req));
|
|
361
|
+
});
|
|
362
|
+
// set tcp keep alive to prevent drop connection by peer
|
|
363
|
+
req.on("socket", /** @param {*} socket */ (socket)=>{
|
|
364
|
+
log("onsocket");
|
|
365
|
+
// default interval of sending ack packet is 1 minute
|
|
366
|
+
socket.setKeepAlive(true, 1000 * 60);
|
|
367
|
+
});
|
|
368
|
+
// Handle request timeout
|
|
369
|
+
if (config.timeout) {
|
|
370
|
+
// This is forcing a int timeout to avoid problems if the `req` interface doesn't handle other types.
|
|
371
|
+
const timeout = Number.parseInt(config.timeout);
|
|
372
|
+
if (Number.isNaN(timeout)) {
|
|
373
|
+
reject(new AxiosError("error trying to parse `config.timeout` to int", AxiosError.ERR_BAD_OPTION_VALUE, config, req));
|
|
374
|
+
} else {
|
|
375
|
+
// Sometime, the response will be very slow, and does not respond, the connect event will be block by event loop system.
|
|
376
|
+
// And timer callback will be fired, and abort() will be invoked before connection, then get "socket hang up" and code ECONNRESET.
|
|
377
|
+
// At this time, if we have a large number of request, nodejs will hang up some socket on background. and the number will up and up.
|
|
378
|
+
// And then these socket which be hang up will devouring CPU little by little.
|
|
379
|
+
// ClientRequest.setTimeout will be fired on the specify milliseconds, and can make sure that abort() will be fired after connect.
|
|
380
|
+
req.setTimeout(timeout, ()=>{
|
|
381
|
+
if (_.isDone) return;
|
|
382
|
+
let timeoutErrorMessage = config.timeout ? `timeout of ${config.timeout}ms exceeded` : "timeout exceeded";
|
|
383
|
+
const transitional = config.transitional || transitionalDefaults;
|
|
384
|
+
if (config.timeoutErrorMessage) {
|
|
385
|
+
timeoutErrorMessage = config.timeoutErrorMessage;
|
|
386
|
+
}
|
|
387
|
+
reject(new AxiosError(timeoutErrorMessage, transitional.clarifyTimeoutError ? AxiosError.ETIMEDOUT : AxiosError.ECONNABORTED, config, req));
|
|
388
|
+
abort();
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
// stream finished
|
|
393
|
+
req.on("finished", _.onFinished.bind(_));
|
|
394
|
+
// ! stream 模式不等待响应数据,直接返回 req,建立pipe管道流
|
|
395
|
+
if (config.stream) resolve(req);
|
|
396
|
+
else {
|
|
397
|
+
// 非stream模式,等待响应数据,返回数据
|
|
398
|
+
req.on("response", /**
|
|
399
|
+
* @param {*} res
|
|
400
|
+
* @param {*} stream
|
|
401
|
+
*/ (res, stream)=>{
|
|
402
|
+
if (req.destroyed) return;
|
|
403
|
+
// 'transfer-encoding': 'chunked'时,无content-length,axios v1.2 不能自动解压
|
|
404
|
+
const responseLength = +res.headers["content-length"];
|
|
405
|
+
log("onresponse", {
|
|
406
|
+
statusCode: res.statusCode,
|
|
407
|
+
responseLength,
|
|
408
|
+
headers: res.headers
|
|
409
|
+
});
|
|
410
|
+
// return the last request(ClientRequest) in case of redirects
|
|
411
|
+
const lastRequest = res.req || req;
|
|
412
|
+
/** @type {*} */ const response = {
|
|
413
|
+
status: res.statusCode,
|
|
414
|
+
statusText: res.statusMessage,
|
|
415
|
+
headers: new AxiosHeaders(res.headers),
|
|
416
|
+
config,
|
|
417
|
+
request: lastRequest
|
|
418
|
+
};
|
|
419
|
+
// 直接返回 responseStream
|
|
420
|
+
if (responseType === "stream") {
|
|
421
|
+
response.data = stream;
|
|
422
|
+
settle(resolve, reject, response);
|
|
423
|
+
} else {
|
|
424
|
+
// 处理 responseStream
|
|
425
|
+
/** @type {*} */ const responseBuffer = [];
|
|
426
|
+
let totalResponseBytes = 0;
|
|
427
|
+
// 处理数据
|
|
428
|
+
stream.on("data", /** @param {*} chunk */ (chunk)=>{
|
|
429
|
+
responseBuffer.push(chunk);
|
|
430
|
+
totalResponseBytes += chunk.length;
|
|
431
|
+
// make sure the content length is not over the maxContentLength if specified
|
|
432
|
+
if (config.maxContentLength > -1 && totalResponseBytes > config.maxContentLength) {
|
|
433
|
+
// stream.destroy() emit aborted event before calling reject() on Node.js v16
|
|
434
|
+
_.rejected = true;
|
|
435
|
+
stream.destroy();
|
|
436
|
+
reject(new AxiosError(`maxContentLength size of ${config.maxContentLength} exceeded`, AxiosError.ERR_BAD_RESPONSE, config, lastRequest));
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
stream.on("aborted", function handlerStreamAborted() {
|
|
440
|
+
if (_.rejected) return;
|
|
441
|
+
const err = new AxiosError(`maxContentLength size of ${config.maxContentLength} exceeded`, AxiosError.ERR_BAD_RESPONSE, config, lastRequest);
|
|
442
|
+
stream.destroy(err);
|
|
443
|
+
reject(err);
|
|
444
|
+
});
|
|
445
|
+
stream.on("error", function handleStreamError(err) {
|
|
446
|
+
if (req.destroyed) return;
|
|
447
|
+
reject(AxiosError.from(err, null, config, lastRequest));
|
|
448
|
+
});
|
|
449
|
+
// 数据传输结束
|
|
450
|
+
stream.on("end", function handleStreamEnd() {
|
|
451
|
+
try {
|
|
452
|
+
let responseData = responseBuffer.length === 1 ? responseBuffer[0] : Buffer.concat(responseBuffer);
|
|
453
|
+
if (responseType !== "arraybuffer") {
|
|
454
|
+
responseData = responseData.toString(responseEncoding);
|
|
455
|
+
if (!responseEncoding || responseEncoding === "utf8") {
|
|
456
|
+
responseData = utils.stripBOM(responseData);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
response.data = responseData;
|
|
460
|
+
settle(resolve, reject, response);
|
|
461
|
+
} catch (err) {
|
|
462
|
+
reject(AxiosError.from(err, null, config, response.request, response));
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
emitter.once("abort", (err)=>{
|
|
467
|
+
if (!stream.destroyed) {
|
|
468
|
+
stream.emit("error", err);
|
|
469
|
+
stream.destroy();
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
});
|
|
473
|
+
// 发送数据
|
|
474
|
+
if (utils.isStream(data)) {
|
|
475
|
+
// Send the request
|
|
476
|
+
let ended = false;
|
|
477
|
+
let errored = false;
|
|
478
|
+
data.on("end", ()=>{
|
|
479
|
+
ended = true;
|
|
480
|
+
});
|
|
481
|
+
data.once("error", /** @param {*} err */ (err)=>{
|
|
482
|
+
errored = true;
|
|
483
|
+
req.destroy(err);
|
|
484
|
+
});
|
|
485
|
+
data.on("close", ()=>{
|
|
486
|
+
if (!ended && !errored) {
|
|
487
|
+
abort(new CanceledError("Request stream has been aborted", config, req));
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
data.pipe(req); // stream 写入数据
|
|
491
|
+
} else req.end(data);
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
_.done(R);
|
|
496
|
+
} catch (e) {
|
|
497
|
+
log.error(e, "request");
|
|
498
|
+
_.done(e, true);
|
|
499
|
+
throw e;
|
|
500
|
+
}
|
|
501
|
+
return R;
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
/**
|
|
505
|
+
*
|
|
506
|
+
* @param {*} stream
|
|
507
|
+
* @param {*} param1
|
|
508
|
+
* @returns
|
|
509
|
+
*/ const flushOnFinish = (stream, [throttled, flush])=>{
|
|
510
|
+
stream.on("end", flush).on("error", flush);
|
|
511
|
+
return throttled;
|
|
512
|
+
};
|
|
513
|
+
/** @typedef {import('../core/Axios').default} Axios */ /**
|
|
514
|
+
* If the proxy or config beforeRedirects functions are defined, call them with the options
|
|
515
|
+
* object.
|
|
516
|
+
*
|
|
517
|
+
* @param {Object<string, any>} options - The options object that was passed to the request.
|
|
518
|
+
* @param {*} responseDetails - The options object that was passed to the request.
|
|
519
|
+
*
|
|
520
|
+
*/ function dispatchBeforeRedirect(options, responseDetails) {
|
|
521
|
+
log.debug("dispatchBeforeRedirect", {
|
|
522
|
+
opts: options.beforeRedirects
|
|
523
|
+
});
|
|
524
|
+
if (options.beforeRedirects.proxy) options.beforeRedirects.proxy(options);
|
|
525
|
+
if (options.beforeRedirects.config) options.beforeRedirects.config(options, responseDetails);
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
*
|
|
529
|
+
* @param {{address: string, family: *}} param0
|
|
530
|
+
* @returns
|
|
531
|
+
*/ function resolveFamily({ address, family }) {
|
|
532
|
+
if (!utils.isString(address)) {
|
|
533
|
+
throw TypeError("address must be a string");
|
|
534
|
+
}
|
|
535
|
+
return {
|
|
536
|
+
address,
|
|
537
|
+
family: family || (address.indexOf(".") < 0 ? 6 : 4)
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
*
|
|
542
|
+
* @param {*} address
|
|
543
|
+
* @param {*} family
|
|
544
|
+
*/ function buildAddressEntry(address, family) {
|
|
545
|
+
resolveFamily(utils.isObject(address) ? address : {
|
|
546
|
+
address,
|
|
547
|
+
family
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* null or funciton
|
|
552
|
+
*/ export default isHttpAdapterSupported && HttpAdapter;
|