urllib 3.12.0 → 3.13.0
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/package.json +5 -5
- package/src/HttpAgent.ts +2 -2
- package/src/HttpClient.ts +13 -13
- package/src/Request.ts +2 -2
- package/src/Response.ts +2 -2
- package/src/cjs/HttpAgent.d.ts +1 -1
- package/src/cjs/HttpAgent.js +11 -21
- package/src/cjs/HttpAgent.js.map +1 -1
- package/src/cjs/HttpClient.d.ts +2 -2
- package/src/cjs/HttpClient.js +436 -448
- package/src/cjs/HttpClient.js.map +1 -1
- package/src/cjs/Request.d.ts +2 -2
- package/src/cjs/Response.d.ts +2 -2
- package/src/cjs/diagnosticsChannel.js +11 -11
- package/src/cjs/diagnosticsChannel.js.map +1 -1
- package/src/cjs/utils.js +8 -8
- package/src/cjs/utils.js.map +1 -1
- package/src/diagnosticsChannel.ts +3 -3
- package/src/esm/HttpAgent.d.ts +1 -1
- package/src/esm/HttpAgent.js +9 -19
- package/src/esm/HttpAgent.js.map +1 -1
- package/src/esm/HttpClient.d.ts +2 -2
- package/src/esm/HttpClient.js +431 -443
- package/src/esm/HttpClient.js.map +1 -1
- package/src/esm/Request.d.ts +2 -2
- package/src/esm/Response.d.ts +2 -2
- package/src/esm/diagnosticsChannel.js +3 -3
- package/src/esm/diagnosticsChannel.js.map +1 -1
- package/src/esm/utils.js +3 -3
- package/src/esm/utils.js.map +1 -1
- package/src/utils.ts +3 -3
package/src/esm/HttpClient.js
CHANGED
@@ -1,26 +1,14 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
};
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
};
|
12
|
-
var _BlobFromStream_stream, _BlobFromStream_type, _HttpClient_instances, _HttpClient_defaultArgs, _HttpClient_dispatcher, _HttpClient_requestInternal, _HttpClient_updateSocketInfo;
|
13
|
-
import { EventEmitter } from 'events';
|
14
|
-
import { STATUS_CODES } from 'http';
|
15
|
-
import { debuglog } from 'util';
|
16
|
-
import { createGunzip, createBrotliDecompress, gunzipSync, brotliDecompressSync, } from 'zlib';
|
17
|
-
import { Blob } from 'buffer';
|
18
|
-
import { Readable, pipeline } from 'stream';
|
19
|
-
import stream from 'stream';
|
20
|
-
import { basename } from 'path';
|
21
|
-
import { createReadStream } from 'fs';
|
22
|
-
import { format as urlFormat } from 'url';
|
23
|
-
import { performance } from 'perf_hooks';
|
1
|
+
import { EventEmitter } from 'node:events';
|
2
|
+
import { STATUS_CODES } from 'node:http';
|
3
|
+
import { debuglog } from 'node:util';
|
4
|
+
import { createGunzip, createBrotliDecompress, gunzipSync, brotliDecompressSync, } from 'node:zlib';
|
5
|
+
import { Blob } from 'node:buffer';
|
6
|
+
import { Readable, pipeline } from 'node:stream';
|
7
|
+
import stream from 'node:stream';
|
8
|
+
import { basename } from 'node:path';
|
9
|
+
import { createReadStream } from 'node:fs';
|
10
|
+
import { format as urlFormat } from 'node:url';
|
11
|
+
import { performance } from 'node:perf_hooks';
|
24
12
|
import { FormData as FormDataNative, request as undiciRequest, } from 'undici';
|
25
13
|
import { FormData as FormDataNode } from 'formdata-node';
|
26
14
|
import { FormDataEncoder } from 'form-data-encoder';
|
@@ -51,19 +39,19 @@ function noop() {
|
|
51
39
|
const debug = debuglog('urllib:HttpClient');
|
52
40
|
// https://github.com/octet-stream/form-data
|
53
41
|
class BlobFromStream {
|
42
|
+
#stream;
|
43
|
+
#type;
|
54
44
|
constructor(stream, type) {
|
55
|
-
|
56
|
-
|
57
|
-
__classPrivateFieldSet(this, _BlobFromStream_stream, stream, "f");
|
58
|
-
__classPrivateFieldSet(this, _BlobFromStream_type, type, "f");
|
45
|
+
this.#stream = stream;
|
46
|
+
this.#type = type;
|
59
47
|
}
|
60
48
|
stream() {
|
61
|
-
return
|
49
|
+
return this.#stream;
|
62
50
|
}
|
63
51
|
get type() {
|
64
|
-
return
|
52
|
+
return this.#type;
|
65
53
|
}
|
66
|
-
get [
|
54
|
+
get [Symbol.toStringTag]() {
|
67
55
|
return 'Blob';
|
68
56
|
}
|
69
57
|
}
|
@@ -87,483 +75,483 @@ function defaultIsRetry(response) {
|
|
87
75
|
return response.status >= 500;
|
88
76
|
}
|
89
77
|
export class HttpClient extends EventEmitter {
|
78
|
+
#defaultArgs;
|
79
|
+
#dispatcher;
|
90
80
|
constructor(clientOptions) {
|
91
81
|
super();
|
92
|
-
|
93
|
-
_HttpClient_defaultArgs.set(this, void 0);
|
94
|
-
_HttpClient_dispatcher.set(this, void 0);
|
95
|
-
__classPrivateFieldSet(this, _HttpClient_defaultArgs, clientOptions?.defaultArgs, "f");
|
82
|
+
this.#defaultArgs = clientOptions?.defaultArgs;
|
96
83
|
if (clientOptions?.lookup || clientOptions?.checkAddress || clientOptions?.connect) {
|
97
|
-
|
84
|
+
this.#dispatcher = new HttpAgent({
|
98
85
|
lookup: clientOptions.lookup,
|
99
86
|
checkAddress: clientOptions.checkAddress,
|
100
87
|
connect: clientOptions.connect,
|
101
|
-
})
|
88
|
+
});
|
102
89
|
}
|
103
90
|
initDiagnosticsChannel();
|
104
91
|
}
|
105
92
|
async request(url, options) {
|
106
|
-
return await
|
93
|
+
return await this.#requestInternal(url, options);
|
107
94
|
}
|
108
95
|
// alias to request, keep compatible with urlib@2 HttpClient.curl
|
109
96
|
async curl(url, options) {
|
110
97
|
return await this.request(url, options);
|
111
98
|
}
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
requestUrl = new URL(url);
|
122
|
-
}
|
123
|
-
else {
|
124
|
-
if (!url.searchParams) {
|
125
|
-
// url maybe url.parse(url) object in urllib2
|
126
|
-
requestUrl = new URL(urlFormat(url));
|
127
|
-
}
|
128
|
-
else {
|
129
|
-
requestUrl = url;
|
130
|
-
}
|
131
|
-
}
|
132
|
-
const method = (options?.method ?? 'GET').toUpperCase();
|
133
|
-
const originalHeaders = options?.headers;
|
134
|
-
const headers = {};
|
135
|
-
const args = {
|
136
|
-
retry: 0,
|
137
|
-
...__classPrivateFieldGet(this, _HttpClient_defaultArgs, "f"),
|
138
|
-
...options,
|
139
|
-
// keep method and headers exists on args for request event handler to easy use
|
140
|
-
method,
|
141
|
-
headers,
|
142
|
-
};
|
143
|
-
requestContext = {
|
144
|
-
retries: 0,
|
145
|
-
...requestContext,
|
146
|
-
};
|
147
|
-
const requestStartTime = performance.now();
|
148
|
-
// https://developer.chrome.com/docs/devtools/network/reference/?utm_source=devtools#timing-explanation
|
149
|
-
const timing = {
|
150
|
-
// socket assigned
|
151
|
-
queuing: 0,
|
152
|
-
// dns lookup time
|
153
|
-
// dnslookup: 0,
|
154
|
-
// socket connected
|
155
|
-
connected: 0,
|
156
|
-
// request headers sent
|
157
|
-
requestHeadersSent: 0,
|
158
|
-
// request sent, including headers and body
|
159
|
-
requestSent: 0,
|
160
|
-
// Time to first byte (TTFB), the response headers have been received
|
161
|
-
waiting: 0,
|
162
|
-
// the response body and trailers have been received
|
163
|
-
contentDownload: 0,
|
164
|
-
};
|
165
|
-
const orginalOpaque = args.opaque;
|
166
|
-
// using opaque to diagnostics channel, binding request and socket
|
167
|
-
const internalOpaque = {
|
168
|
-
[symbols.kRequestId]: requestId,
|
169
|
-
[symbols.kRequestStartTime]: requestStartTime,
|
170
|
-
[symbols.kEnableRequestTiming]: !!args.timing,
|
171
|
-
[symbols.kRequestTiming]: timing,
|
172
|
-
[symbols.kRequestOrginalOpaque]: orginalOpaque,
|
173
|
-
};
|
174
|
-
const reqMeta = {
|
175
|
-
requestId,
|
176
|
-
url: requestUrl.href,
|
177
|
-
args,
|
178
|
-
ctx: args.ctx,
|
179
|
-
retries: requestContext.retries,
|
180
|
-
};
|
181
|
-
const socketInfo = {
|
182
|
-
id: 0,
|
183
|
-
localAddress: '',
|
184
|
-
localPort: 0,
|
185
|
-
remoteAddress: '',
|
186
|
-
remotePort: 0,
|
187
|
-
remoteFamily: '',
|
188
|
-
bytesWritten: 0,
|
189
|
-
bytesRead: 0,
|
190
|
-
handledRequests: 0,
|
191
|
-
handledResponses: 0,
|
192
|
-
};
|
193
|
-
// keep urllib createCallbackResponse style
|
194
|
-
const resHeaders = {};
|
195
|
-
let res = {
|
196
|
-
status: -1,
|
197
|
-
statusCode: -1,
|
198
|
-
statusText: '',
|
199
|
-
headers: resHeaders,
|
200
|
-
size: 0,
|
201
|
-
aborted: false,
|
202
|
-
rt: 0,
|
203
|
-
keepAliveSocket: true,
|
204
|
-
requestUrls: [],
|
205
|
-
timing,
|
206
|
-
socket: socketInfo,
|
207
|
-
};
|
208
|
-
let headersTimeout = 5000;
|
209
|
-
let bodyTimeout = 5000;
|
210
|
-
if (args.timeout) {
|
211
|
-
if (Array.isArray(args.timeout)) {
|
212
|
-
headersTimeout = args.timeout[0] ?? headersTimeout;
|
213
|
-
bodyTimeout = args.timeout[1] ?? bodyTimeout;
|
99
|
+
async #requestInternal(url, options, requestContext) {
|
100
|
+
const requestId = globalId('HttpClientRequest');
|
101
|
+
let requestUrl;
|
102
|
+
if (typeof url === 'string') {
|
103
|
+
if (!PROTO_RE.test(url)) {
|
104
|
+
// Support `request('www.server.com')`
|
105
|
+
url = 'http://' + url;
|
106
|
+
}
|
107
|
+
requestUrl = new URL(url);
|
214
108
|
}
|
215
109
|
else {
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
110
|
+
if (!url.searchParams) {
|
111
|
+
// url maybe url.parse(url) object in urllib2
|
112
|
+
requestUrl = new URL(urlFormat(url));
|
113
|
+
}
|
114
|
+
else {
|
115
|
+
requestUrl = url;
|
116
|
+
}
|
223
117
|
}
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
headers['user-agent'] = HEADER_USER_AGENT;
|
233
|
-
}
|
234
|
-
// Alias to dataType = 'stream'
|
235
|
-
if (args.streaming || args.customResponse) {
|
236
|
-
args.dataType = 'stream';
|
237
|
-
}
|
238
|
-
if (args.dataType === 'json' && !headers.accept) {
|
239
|
-
headers.accept = 'application/json';
|
240
|
-
}
|
241
|
-
// gzip alias to compressed
|
242
|
-
if (args.gzip && args.compressed !== false) {
|
243
|
-
args.compressed = true;
|
244
|
-
}
|
245
|
-
if (args.compressed && !headers['accept-encoding']) {
|
246
|
-
headers['accept-encoding'] = 'gzip, br';
|
247
|
-
}
|
248
|
-
if (requestContext.retries > 0) {
|
249
|
-
headers['x-urllib-retry'] = `${requestContext.retries}/${args.retry}`;
|
250
|
-
}
|
251
|
-
if (args.auth && !headers.authorization) {
|
252
|
-
headers.authorization = `Basic ${Buffer.from(args.auth).toString('base64')}`;
|
253
|
-
}
|
254
|
-
try {
|
255
|
-
const requestOptions = {
|
118
|
+
const method = (options?.method ?? 'GET').toUpperCase();
|
119
|
+
const originalHeaders = options?.headers;
|
120
|
+
const headers = {};
|
121
|
+
const args = {
|
122
|
+
retry: 0,
|
123
|
+
...this.#defaultArgs,
|
124
|
+
...options,
|
125
|
+
// keep method and headers exists on args for request event handler to easy use
|
256
126
|
method,
|
257
|
-
maxRedirections: args.maxRedirects ?? 10,
|
258
|
-
headersTimeout,
|
259
127
|
headers,
|
260
|
-
bodyTimeout,
|
261
|
-
opaque: internalOpaque,
|
262
|
-
dispatcher: args.dispatcher ?? __classPrivateFieldGet(this, _HttpClient_dispatcher, "f"),
|
263
128
|
};
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
const
|
271
|
-
|
272
|
-
|
273
|
-
//
|
274
|
-
//
|
275
|
-
|
276
|
-
|
277
|
-
|
129
|
+
requestContext = {
|
130
|
+
retries: 0,
|
131
|
+
...requestContext,
|
132
|
+
};
|
133
|
+
const requestStartTime = performance.now();
|
134
|
+
// https://developer.chrome.com/docs/devtools/network/reference/?utm_source=devtools#timing-explanation
|
135
|
+
const timing = {
|
136
|
+
// socket assigned
|
137
|
+
queuing: 0,
|
138
|
+
// dns lookup time
|
139
|
+
// dnslookup: 0,
|
140
|
+
// socket connected
|
141
|
+
connected: 0,
|
142
|
+
// request headers sent
|
143
|
+
requestHeadersSent: 0,
|
144
|
+
// request sent, including headers and body
|
145
|
+
requestSent: 0,
|
146
|
+
// Time to first byte (TTFB), the response headers have been received
|
147
|
+
waiting: 0,
|
148
|
+
// the response body and trailers have been received
|
149
|
+
contentDownload: 0,
|
150
|
+
};
|
151
|
+
const orginalOpaque = args.opaque;
|
152
|
+
// using opaque to diagnostics channel, binding request and socket
|
153
|
+
const internalOpaque = {
|
154
|
+
[symbols.kRequestId]: requestId,
|
155
|
+
[symbols.kRequestStartTime]: requestStartTime,
|
156
|
+
[symbols.kEnableRequestTiming]: !!args.timing,
|
157
|
+
[symbols.kRequestTiming]: timing,
|
158
|
+
[symbols.kRequestOrginalOpaque]: orginalOpaque,
|
159
|
+
};
|
160
|
+
const reqMeta = {
|
161
|
+
requestId,
|
162
|
+
url: requestUrl.href,
|
163
|
+
args,
|
164
|
+
ctx: args.ctx,
|
165
|
+
retries: requestContext.retries,
|
166
|
+
};
|
167
|
+
const socketInfo = {
|
168
|
+
id: 0,
|
169
|
+
localAddress: '',
|
170
|
+
localPort: 0,
|
171
|
+
remoteAddress: '',
|
172
|
+
remotePort: 0,
|
173
|
+
remoteFamily: '',
|
174
|
+
bytesWritten: 0,
|
175
|
+
bytesRead: 0,
|
176
|
+
handledRequests: 0,
|
177
|
+
handledResponses: 0,
|
178
|
+
};
|
179
|
+
// keep urllib createCallbackResponse style
|
180
|
+
const resHeaders = {};
|
181
|
+
let res = {
|
182
|
+
status: -1,
|
183
|
+
statusCode: -1,
|
184
|
+
statusText: '',
|
185
|
+
headers: resHeaders,
|
186
|
+
size: 0,
|
187
|
+
aborted: false,
|
188
|
+
rt: 0,
|
189
|
+
keepAliveSocket: true,
|
190
|
+
requestUrls: [],
|
191
|
+
timing,
|
192
|
+
socket: socketInfo,
|
193
|
+
};
|
194
|
+
let headersTimeout = 5000;
|
195
|
+
let bodyTimeout = 5000;
|
196
|
+
if (args.timeout) {
|
197
|
+
if (Array.isArray(args.timeout)) {
|
198
|
+
headersTimeout = args.timeout[0] ?? headersTimeout;
|
199
|
+
bodyTimeout = args.timeout[1] ?? bodyTimeout;
|
278
200
|
}
|
279
|
-
else
|
280
|
-
|
281
|
-
args.stream = new Readable().wrap(args.stream);
|
201
|
+
else {
|
202
|
+
headersTimeout = bodyTimeout = args.timeout;
|
282
203
|
}
|
283
|
-
args.content = args.stream;
|
284
204
|
}
|
285
|
-
if (
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
const formData = new FormData();
|
290
|
-
const uploadFiles = [];
|
291
|
-
if (Array.isArray(args.files)) {
|
292
|
-
for (const [index, file] of args.files.entries()) {
|
293
|
-
const field = index === 0 ? 'file' : `file${index}`;
|
294
|
-
uploadFiles.push([field, file]);
|
295
|
-
}
|
205
|
+
if (originalHeaders) {
|
206
|
+
// convert headers to lower-case
|
207
|
+
for (const name in originalHeaders) {
|
208
|
+
headers[name.toLowerCase()] = originalHeaders[name];
|
296
209
|
}
|
297
|
-
|
298
|
-
|
210
|
+
}
|
211
|
+
// hidden user-agent
|
212
|
+
const hiddenUserAgent = 'user-agent' in headers && !headers['user-agent'];
|
213
|
+
if (hiddenUserAgent) {
|
214
|
+
delete headers['user-agent'];
|
215
|
+
}
|
216
|
+
else if (!headers['user-agent']) {
|
217
|
+
// need to set user-agent
|
218
|
+
headers['user-agent'] = HEADER_USER_AGENT;
|
219
|
+
}
|
220
|
+
// Alias to dataType = 'stream'
|
221
|
+
if (args.streaming || args.customResponse) {
|
222
|
+
args.dataType = 'stream';
|
223
|
+
}
|
224
|
+
if (args.dataType === 'json' && !headers.accept) {
|
225
|
+
headers.accept = 'application/json';
|
226
|
+
}
|
227
|
+
// gzip alias to compressed
|
228
|
+
if (args.gzip && args.compressed !== false) {
|
229
|
+
args.compressed = true;
|
230
|
+
}
|
231
|
+
if (args.compressed && !headers['accept-encoding']) {
|
232
|
+
headers['accept-encoding'] = 'gzip, br';
|
233
|
+
}
|
234
|
+
if (requestContext.retries > 0) {
|
235
|
+
headers['x-urllib-retry'] = `${requestContext.retries}/${args.retry}`;
|
236
|
+
}
|
237
|
+
if (args.auth && !headers.authorization) {
|
238
|
+
headers.authorization = `Basic ${Buffer.from(args.auth).toString('base64')}`;
|
239
|
+
}
|
240
|
+
try {
|
241
|
+
const requestOptions = {
|
242
|
+
method,
|
243
|
+
maxRedirections: args.maxRedirects ?? 10,
|
244
|
+
headersTimeout,
|
245
|
+
headers,
|
246
|
+
bodyTimeout,
|
247
|
+
opaque: internalOpaque,
|
248
|
+
dispatcher: args.dispatcher ?? this.#dispatcher,
|
249
|
+
};
|
250
|
+
if (typeof args.reset === 'boolean') {
|
251
|
+
requestOptions.reset = args.reset;
|
299
252
|
}
|
300
|
-
|
301
|
-
|
253
|
+
if (args.followRedirect === false) {
|
254
|
+
requestOptions.maxRedirections = 0;
|
302
255
|
}
|
303
|
-
|
304
|
-
|
305
|
-
|
256
|
+
const isGETOrHEAD = requestOptions.method === 'GET' || requestOptions.method === 'HEAD';
|
257
|
+
// alias to args.content
|
258
|
+
if (args.stream && !args.content) {
|
259
|
+
// convert old style stream to new stream
|
260
|
+
// https://nodejs.org/dist/latest-v18.x/docs/api/stream.html#readablewrapstream
|
261
|
+
if (isReadable(args.stream) && !(args.stream instanceof Readable)) {
|
262
|
+
debug('Request#%d convert old style stream to Readable', requestId);
|
263
|
+
args.stream = new Readable().wrap(args.stream);
|
306
264
|
}
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
for (const field in args.data) {
|
311
|
-
formData.append(field, args.data[field]);
|
265
|
+
else if (args.stream instanceof FormStream) {
|
266
|
+
debug('Request#%d convert formstream to Readable', requestId);
|
267
|
+
args.stream = new Readable().wrap(args.stream);
|
312
268
|
}
|
269
|
+
args.content = args.stream;
|
313
270
|
}
|
314
|
-
|
315
|
-
if (
|
316
|
-
|
317
|
-
// const fileName = encodeURIComponent(basename(file));
|
318
|
-
// formData.append(field, await fileFromPath(file, `utf-8''${fileName}`, { type: mime.lookup(fileName) || '' }));
|
319
|
-
const fileName = basename(file);
|
320
|
-
const fileReadable = createReadStream(file);
|
321
|
-
formData.append(field, new BlobFromStream(fileReadable, mime.lookup(fileName) || ''), fileName);
|
271
|
+
if (args.files) {
|
272
|
+
if (isGETOrHEAD) {
|
273
|
+
requestOptions.method = 'POST';
|
322
274
|
}
|
323
|
-
|
324
|
-
|
275
|
+
const formData = new FormData();
|
276
|
+
const uploadFiles = [];
|
277
|
+
if (Array.isArray(args.files)) {
|
278
|
+
for (const [index, file] of args.files.entries()) {
|
279
|
+
const field = index === 0 ? 'file' : `file${index}`;
|
280
|
+
uploadFiles.push([field, file]);
|
281
|
+
}
|
325
282
|
}
|
326
|
-
else if (
|
327
|
-
|
328
|
-
formData.append(field, new BlobFromStream(file, mime.lookup(fileName) || ''), fileName);
|
283
|
+
else if (args.files instanceof Readable || isReadable(args.files)) {
|
284
|
+
uploadFiles.push(['file', args.files]);
|
329
285
|
}
|
330
|
-
|
331
|
-
|
332
|
-
requestOptions.body = formData;
|
333
|
-
}
|
334
|
-
else {
|
335
|
-
// Node.js 14 does not support spec-compliant FormData
|
336
|
-
// https://github.com/octet-stream/form-data#usage
|
337
|
-
const encoder = new FormDataEncoder(formData);
|
338
|
-
Object.assign(headers, encoder.headers);
|
339
|
-
// fix "Content-Length":"NaN"
|
340
|
-
delete headers['Content-Length'];
|
341
|
-
requestOptions.body = Readable.from(encoder);
|
342
|
-
}
|
343
|
-
}
|
344
|
-
else if (args.content) {
|
345
|
-
if (!isGETOrHEAD) {
|
346
|
-
// handle content
|
347
|
-
requestOptions.body = args.content;
|
348
|
-
if (args.contentType) {
|
349
|
-
headers['content-type'] = args.contentType;
|
286
|
+
else if (typeof args.files === 'string' || Buffer.isBuffer(args.files)) {
|
287
|
+
uploadFiles.push(['file', args.files]);
|
350
288
|
}
|
351
|
-
else if (typeof args.
|
352
|
-
|
289
|
+
else if (typeof args.files === 'object') {
|
290
|
+
for (const field in args.files) {
|
291
|
+
uploadFiles.push([field, args.files[field]]);
|
292
|
+
}
|
353
293
|
}
|
354
|
-
|
355
|
-
|
356
|
-
else if (args.data) {
|
357
|
-
const isStringOrBufferOrReadable = typeof args.data === 'string'
|
358
|
-
|| Buffer.isBuffer(args.data)
|
359
|
-
|| isReadable(args.data);
|
360
|
-
if (isGETOrHEAD) {
|
361
|
-
if (!isStringOrBufferOrReadable) {
|
294
|
+
// set normal fields first
|
295
|
+
if (args.data) {
|
362
296
|
for (const field in args.data) {
|
363
|
-
|
297
|
+
formData.append(field, args.data[field]);
|
364
298
|
}
|
365
299
|
}
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
300
|
+
for (const [index, [field, file]] of uploadFiles.entries()) {
|
301
|
+
if (typeof file === 'string') {
|
302
|
+
// FIXME: support non-ascii filename
|
303
|
+
// const fileName = encodeURIComponent(basename(file));
|
304
|
+
// formData.append(field, await fileFromPath(file, `utf-8''${fileName}`, { type: mime.lookup(fileName) || '' }));
|
305
|
+
const fileName = basename(file);
|
306
|
+
const fileReadable = createReadStream(file);
|
307
|
+
formData.append(field, new BlobFromStream(fileReadable, mime.lookup(fileName) || ''), fileName);
|
308
|
+
}
|
309
|
+
else if (Buffer.isBuffer(file)) {
|
310
|
+
formData.append(field, new Blob([file]), `bufferfile${index}`);
|
311
|
+
}
|
312
|
+
else if (file instanceof Readable || isReadable(file)) {
|
313
|
+
const fileName = getFileName(file) || `streamfile${index}`;
|
314
|
+
formData.append(field, new BlobFromStream(file, mime.lookup(fileName) || ''), fileName);
|
315
|
+
}
|
316
|
+
}
|
317
|
+
if (FormDataNative) {
|
318
|
+
requestOptions.body = formData;
|
370
319
|
}
|
371
320
|
else {
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
321
|
+
// Node.js 14 does not support spec-compliant FormData
|
322
|
+
// https://github.com/octet-stream/form-data#usage
|
323
|
+
const encoder = new FormDataEncoder(formData);
|
324
|
+
Object.assign(headers, encoder.headers);
|
325
|
+
// fix "Content-Length":"NaN"
|
326
|
+
delete headers['Content-Length'];
|
327
|
+
requestOptions.body = Readable.from(encoder);
|
328
|
+
}
|
329
|
+
}
|
330
|
+
else if (args.content) {
|
331
|
+
if (!isGETOrHEAD) {
|
332
|
+
// handle content
|
333
|
+
requestOptions.body = args.content;
|
334
|
+
if (args.contentType) {
|
335
|
+
headers['content-type'] = args.contentType;
|
336
|
+
}
|
337
|
+
else if (typeof args.content === 'string' && !headers['content-type']) {
|
338
|
+
headers['content-type'] = 'text/plain;charset=UTF-8';
|
339
|
+
}
|
340
|
+
}
|
341
|
+
}
|
342
|
+
else if (args.data) {
|
343
|
+
const isStringOrBufferOrReadable = typeof args.data === 'string'
|
344
|
+
|| Buffer.isBuffer(args.data)
|
345
|
+
|| isReadable(args.data);
|
346
|
+
if (isGETOrHEAD) {
|
347
|
+
if (!isStringOrBufferOrReadable) {
|
348
|
+
for (const field in args.data) {
|
349
|
+
requestUrl.searchParams.append(field, args.data[field]);
|
378
350
|
}
|
379
351
|
}
|
352
|
+
}
|
353
|
+
else {
|
354
|
+
if (isStringOrBufferOrReadable) {
|
355
|
+
requestOptions.body = args.data;
|
356
|
+
}
|
380
357
|
else {
|
381
|
-
|
382
|
-
|
358
|
+
if (args.contentType === 'json'
|
359
|
+
|| args.contentType === 'application/json'
|
360
|
+
|| headers['content-type']?.startsWith('application/json')) {
|
361
|
+
requestOptions.body = JSON.stringify(args.data);
|
362
|
+
if (!headers['content-type']) {
|
363
|
+
headers['content-type'] = 'application/json';
|
364
|
+
}
|
365
|
+
}
|
366
|
+
else {
|
367
|
+
headers['content-type'] = 'application/x-www-form-urlencoded;charset=UTF-8';
|
368
|
+
requestOptions.body = new URLSearchParams(args.data).toString();
|
369
|
+
}
|
383
370
|
}
|
384
371
|
}
|
385
372
|
}
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
this.emit('request', reqMeta);
|
391
|
-
}
|
392
|
-
let response = await undiciRequest(requestUrl, requestOptions);
|
393
|
-
if (response.statusCode === 401 && response.headers['www-authenticate'] &&
|
394
|
-
!requestOptions.headers.authorization && args.digestAuth) {
|
395
|
-
// handle digest auth
|
396
|
-
const authenticateHeaders = response.headers['www-authenticate'];
|
397
|
-
const authenticate = Array.isArray(authenticateHeaders)
|
398
|
-
? authenticateHeaders.find(authHeader => authHeader.startsWith('Digest '))
|
399
|
-
: authenticateHeaders;
|
400
|
-
if (authenticate && authenticate.startsWith('Digest ')) {
|
401
|
-
debug('Request#%d %s: got digest auth header WWW-Authenticate: %s', requestId, requestUrl.href, authenticate);
|
402
|
-
requestOptions.headers.authorization = digestAuthHeader(requestOptions.method, `${requestUrl.pathname}${requestUrl.search}`, authenticate, args.digestAuth);
|
403
|
-
debug('Request#%d %s: auth with digest header: %s', requestId, url, requestOptions.headers.authorization);
|
404
|
-
if (Array.isArray(response.headers['set-cookie'])) {
|
405
|
-
// FIXME: merge exists cookie header
|
406
|
-
requestOptions.headers.cookie = response.headers['set-cookie'].join(';');
|
407
|
-
}
|
408
|
-
response = await undiciRequest(requestUrl, requestOptions);
|
373
|
+
debug('Request#%d %s %s, headers: %j, headersTimeout: %s, bodyTimeout: %s', requestId, requestOptions.method, requestUrl.href, headers, headersTimeout, bodyTimeout);
|
374
|
+
requestOptions.headers = headers;
|
375
|
+
if (this.listenerCount('request') > 0) {
|
376
|
+
this.emit('request', reqMeta);
|
409
377
|
}
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
378
|
+
let response = await undiciRequest(requestUrl, requestOptions);
|
379
|
+
if (response.statusCode === 401 && response.headers['www-authenticate'] &&
|
380
|
+
!requestOptions.headers.authorization && args.digestAuth) {
|
381
|
+
// handle digest auth
|
382
|
+
const authenticateHeaders = response.headers['www-authenticate'];
|
383
|
+
const authenticate = Array.isArray(authenticateHeaders)
|
384
|
+
? authenticateHeaders.find(authHeader => authHeader.startsWith('Digest '))
|
385
|
+
: authenticateHeaders;
|
386
|
+
if (authenticate && authenticate.startsWith('Digest ')) {
|
387
|
+
debug('Request#%d %s: got digest auth header WWW-Authenticate: %s', requestId, requestUrl.href, authenticate);
|
388
|
+
requestOptions.headers.authorization = digestAuthHeader(requestOptions.method, `${requestUrl.pathname}${requestUrl.search}`, authenticate, args.digestAuth);
|
389
|
+
debug('Request#%d %s: auth with digest header: %s', requestId, url, requestOptions.headers.authorization);
|
390
|
+
if (Array.isArray(response.headers['set-cookie'])) {
|
391
|
+
// FIXME: merge exists cookie header
|
392
|
+
requestOptions.headers.cookie = response.headers['set-cookie'].join(';');
|
393
|
+
}
|
394
|
+
response = await undiciRequest(requestUrl, requestOptions);
|
395
|
+
}
|
417
396
|
}
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
res.headers = response.headers;
|
426
|
-
res.status = res.statusCode = response.statusCode;
|
427
|
-
res.statusText = STATUS_CODES[res.status] || '';
|
428
|
-
if (res.headers['content-length']) {
|
429
|
-
res.size = parseInt(res.headers['content-length']);
|
430
|
-
}
|
431
|
-
let data = null;
|
432
|
-
if (args.dataType === 'stream') {
|
433
|
-
// streaming mode will disable retry
|
434
|
-
args.retry = 0;
|
435
|
-
// only auto decompress on request args.compressed = true
|
436
|
-
if (args.compressed === true && isCompressedContent) {
|
437
|
-
// gzip or br
|
438
|
-
const decoder = contentEncoding === 'gzip' ? createGunzip() : createBrotliDecompress();
|
439
|
-
res = Object.assign(pipeline(response.body, decoder, noop), res);
|
397
|
+
const context = response.context;
|
398
|
+
let lastUrl = '';
|
399
|
+
if (context?.history) {
|
400
|
+
for (const urlObject of context?.history) {
|
401
|
+
res.requestUrls.push(urlObject.href);
|
402
|
+
lastUrl = urlObject.href;
|
403
|
+
}
|
440
404
|
}
|
441
405
|
else {
|
442
|
-
res
|
406
|
+
res.requestUrls.push(requestUrl.href);
|
407
|
+
lastUrl = requestUrl.href;
|
443
408
|
}
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
409
|
+
const contentEncoding = response.headers['content-encoding'];
|
410
|
+
const isCompressedContent = contentEncoding === 'gzip' || contentEncoding === 'br';
|
411
|
+
res.headers = response.headers;
|
412
|
+
res.status = res.statusCode = response.statusCode;
|
413
|
+
res.statusText = STATUS_CODES[res.status] || '';
|
414
|
+
if (res.headers['content-length']) {
|
415
|
+
res.size = parseInt(res.headers['content-length']);
|
451
416
|
}
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
data = contentEncoding === 'gzip' ? gunzipSync(data) : brotliDecompressSync(data);
|
417
|
+
let data = null;
|
418
|
+
if (args.dataType === 'stream') {
|
419
|
+
// streaming mode will disable retry
|
420
|
+
args.retry = 0;
|
421
|
+
// only auto decompress on request args.compressed = true
|
422
|
+
if (args.compressed === true && isCompressedContent) {
|
423
|
+
// gzip or br
|
424
|
+
const decoder = contentEncoding === 'gzip' ? createGunzip() : createBrotliDecompress();
|
425
|
+
res = Object.assign(pipeline(response.body, decoder, noop), res);
|
462
426
|
}
|
463
|
-
|
464
|
-
|
465
|
-
err.name = 'UnzipError';
|
466
|
-
}
|
467
|
-
throw err;
|
427
|
+
else {
|
428
|
+
res = Object.assign(response.body, res);
|
468
429
|
}
|
469
430
|
}
|
470
|
-
if (args.
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
431
|
+
else if (args.writeStream) {
|
432
|
+
// streaming mode will disable retry
|
433
|
+
args.retry = 0;
|
434
|
+
if (args.compressed === true && isCompressedContent) {
|
435
|
+
const decoder = contentEncoding === 'gzip' ? createGunzip() : createBrotliDecompress();
|
436
|
+
await pipelinePromise(response.body, decoder, args.writeStream);
|
476
437
|
}
|
477
438
|
else {
|
478
|
-
|
439
|
+
await pipelinePromise(response.body, args.writeStream);
|
479
440
|
}
|
480
441
|
}
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
442
|
+
else {
|
443
|
+
// buffer
|
444
|
+
data = Buffer.from(await response.body.arrayBuffer());
|
445
|
+
if (isCompressedContent && data.length > 0) {
|
446
|
+
try {
|
447
|
+
data = contentEncoding === 'gzip' ? gunzipSync(data) : brotliDecompressSync(data);
|
448
|
+
}
|
449
|
+
catch (err) {
|
450
|
+
if (err.name === 'Error') {
|
451
|
+
err.name = 'UnzipError';
|
452
|
+
}
|
453
|
+
throw err;
|
454
|
+
}
|
455
|
+
}
|
456
|
+
if (args.dataType === 'text' || args.dataType === 'html') {
|
457
|
+
data = data.toString();
|
458
|
+
}
|
459
|
+
else if (args.dataType === 'json') {
|
460
|
+
if (data.length === 0) {
|
461
|
+
data = null;
|
462
|
+
}
|
463
|
+
else {
|
464
|
+
data = parseJSON(data.toString(), args.fixJSONCtlChars);
|
465
|
+
}
|
502
466
|
}
|
503
|
-
requestContext.retries++;
|
504
|
-
return await __classPrivateFieldGet(this, _HttpClient_instances, "m", _HttpClient_requestInternal).call(this, url, options, requestContext);
|
505
467
|
}
|
506
|
-
|
507
|
-
|
508
|
-
this
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
468
|
+
res.rt = performanceTime(requestStartTime);
|
469
|
+
// get real socket info from internalOpaque
|
470
|
+
this.#updateSocketInfo(socketInfo, internalOpaque);
|
471
|
+
const clientResponse = {
|
472
|
+
opaque: orginalOpaque,
|
473
|
+
data,
|
474
|
+
status: res.status,
|
475
|
+
statusCode: res.status,
|
476
|
+
statusText: res.statusText,
|
477
|
+
headers: res.headers,
|
478
|
+
url: lastUrl,
|
479
|
+
redirected: res.requestUrls.length > 1,
|
480
|
+
requestUrls: res.requestUrls,
|
516
481
|
res,
|
517
|
-
}
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
482
|
+
};
|
483
|
+
if (args.retry > 0 && requestContext.retries < args.retry) {
|
484
|
+
const isRetry = args.isRetry ?? defaultIsRetry;
|
485
|
+
if (isRetry(clientResponse)) {
|
486
|
+
if (args.retryDelay) {
|
487
|
+
await sleep(args.retryDelay);
|
488
|
+
}
|
489
|
+
requestContext.retries++;
|
490
|
+
return await this.#requestInternal(url, options, requestContext);
|
491
|
+
}
|
492
|
+
}
|
493
|
+
if (this.listenerCount('response') > 0) {
|
494
|
+
this.emit('response', {
|
495
|
+
requestId,
|
496
|
+
error: null,
|
497
|
+
ctx: args.ctx,
|
498
|
+
req: {
|
499
|
+
...reqMeta,
|
500
|
+
options: args,
|
501
|
+
},
|
502
|
+
res,
|
503
|
+
});
|
504
|
+
}
|
505
|
+
return clientResponse;
|
537
506
|
}
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
507
|
+
catch (e) {
|
508
|
+
debug('Request#%d throw error: %s', requestId, e);
|
509
|
+
let err = e;
|
510
|
+
if (err.name === 'HeadersTimeoutError') {
|
511
|
+
err = new HttpClientRequestTimeoutError(headersTimeout, { cause: e });
|
512
|
+
}
|
513
|
+
else if (err.name === 'BodyTimeoutError') {
|
514
|
+
err = new HttpClientRequestTimeoutError(bodyTimeout, { cause: e });
|
515
|
+
}
|
516
|
+
err.opaque = orginalOpaque;
|
517
|
+
err.status = res.status;
|
518
|
+
err.headers = res.headers;
|
519
|
+
err.res = res;
|
520
|
+
// make sure requestUrls not empty
|
521
|
+
if (res.requestUrls.length === 0) {
|
522
|
+
res.requestUrls.push(requestUrl.href);
|
523
|
+
}
|
524
|
+
res.rt = performanceTime(requestStartTime);
|
525
|
+
this.#updateSocketInfo(socketInfo, internalOpaque);
|
526
|
+
if (this.listenerCount('response') > 0) {
|
527
|
+
this.emit('response', {
|
528
|
+
requestId,
|
529
|
+
error: err,
|
530
|
+
ctx: args.ctx,
|
531
|
+
req: {
|
532
|
+
...reqMeta,
|
533
|
+
options: args,
|
534
|
+
},
|
535
|
+
res,
|
536
|
+
});
|
537
|
+
}
|
538
|
+
throw err;
|
551
539
|
}
|
552
|
-
throw err;
|
553
540
|
}
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
541
|
+
#updateSocketInfo(socketInfo, internalOpaque) {
|
542
|
+
const socket = internalOpaque[symbols.kRequestSocket];
|
543
|
+
if (socket) {
|
544
|
+
socketInfo.id = socket[symbols.kSocketId];
|
545
|
+
socketInfo.handledRequests = socket[symbols.kHandledRequests];
|
546
|
+
socketInfo.handledResponses = socket[symbols.kHandledResponses];
|
547
|
+
socketInfo.localAddress = socket.localAddress;
|
548
|
+
socketInfo.localPort = socket.localPort;
|
549
|
+
socketInfo.remoteAddress = socket.remoteAddress;
|
550
|
+
socketInfo.remotePort = socket.remotePort;
|
551
|
+
socketInfo.remoteFamily = socket.remoteFamily;
|
552
|
+
socketInfo.bytesRead = socket.bytesRead;
|
553
|
+
socketInfo.bytesWritten = socket.bytesWritten;
|
554
|
+
}
|
567
555
|
}
|
568
|
-
}
|
556
|
+
}
|
569
557
|
//# sourceMappingURL=HttpClient.js.map
|