urllib 3.11.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 +16 -13
- package/src/Request.ts +4 -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 -445
- package/src/cjs/HttpClient.js.map +1 -1
- package/src/cjs/Request.d.ts +4 -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 -440
- package/src/esm/HttpClient.js.map +1 -1
- package/src/esm/Request.d.ts +4 -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,480 +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
|
-
|
271
|
-
//
|
272
|
-
|
273
|
-
|
274
|
-
|
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;
|
275
200
|
}
|
276
|
-
else
|
277
|
-
|
278
|
-
args.stream = new Readable().wrap(args.stream);
|
201
|
+
else {
|
202
|
+
headersTimeout = bodyTimeout = args.timeout;
|
279
203
|
}
|
280
|
-
args.content = args.stream;
|
281
204
|
}
|
282
|
-
if (
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
const formData = new FormData();
|
287
|
-
const uploadFiles = [];
|
288
|
-
if (Array.isArray(args.files)) {
|
289
|
-
for (const [index, file] of args.files.entries()) {
|
290
|
-
const field = index === 0 ? 'file' : `file${index}`;
|
291
|
-
uploadFiles.push([field, file]);
|
292
|
-
}
|
205
|
+
if (originalHeaders) {
|
206
|
+
// convert headers to lower-case
|
207
|
+
for (const name in originalHeaders) {
|
208
|
+
headers[name.toLowerCase()] = originalHeaders[name];
|
293
209
|
}
|
294
|
-
|
295
|
-
|
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;
|
296
252
|
}
|
297
|
-
|
298
|
-
|
253
|
+
if (args.followRedirect === false) {
|
254
|
+
requestOptions.maxRedirections = 0;
|
299
255
|
}
|
300
|
-
|
301
|
-
|
302
|
-
|
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);
|
303
264
|
}
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
for (const field in args.data) {
|
308
|
-
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);
|
309
268
|
}
|
269
|
+
args.content = args.stream;
|
310
270
|
}
|
311
|
-
|
312
|
-
if (
|
313
|
-
|
314
|
-
// const fileName = encodeURIComponent(basename(file));
|
315
|
-
// formData.append(field, await fileFromPath(file, `utf-8''${fileName}`, { type: mime.lookup(fileName) || '' }));
|
316
|
-
const fileName = basename(file);
|
317
|
-
const fileReadable = createReadStream(file);
|
318
|
-
formData.append(field, new BlobFromStream(fileReadable, mime.lookup(fileName) || ''), fileName);
|
271
|
+
if (args.files) {
|
272
|
+
if (isGETOrHEAD) {
|
273
|
+
requestOptions.method = 'POST';
|
319
274
|
}
|
320
|
-
|
321
|
-
|
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
|
+
}
|
322
282
|
}
|
323
|
-
else if (
|
324
|
-
|
325
|
-
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]);
|
326
285
|
}
|
327
|
-
|
328
|
-
|
329
|
-
requestOptions.body = formData;
|
330
|
-
}
|
331
|
-
else {
|
332
|
-
// Node.js 14 does not support spec-compliant FormData
|
333
|
-
// https://github.com/octet-stream/form-data#usage
|
334
|
-
const encoder = new FormDataEncoder(formData);
|
335
|
-
Object.assign(headers, encoder.headers);
|
336
|
-
// fix "Content-Length":"NaN"
|
337
|
-
delete headers['Content-Length'];
|
338
|
-
requestOptions.body = Readable.from(encoder);
|
339
|
-
}
|
340
|
-
}
|
341
|
-
else if (args.content) {
|
342
|
-
if (!isGETOrHEAD) {
|
343
|
-
// handle content
|
344
|
-
requestOptions.body = args.content;
|
345
|
-
if (args.contentType) {
|
346
|
-
headers['content-type'] = args.contentType;
|
286
|
+
else if (typeof args.files === 'string' || Buffer.isBuffer(args.files)) {
|
287
|
+
uploadFiles.push(['file', args.files]);
|
347
288
|
}
|
348
|
-
else if (typeof args.
|
349
|
-
|
289
|
+
else if (typeof args.files === 'object') {
|
290
|
+
for (const field in args.files) {
|
291
|
+
uploadFiles.push([field, args.files[field]]);
|
292
|
+
}
|
350
293
|
}
|
351
|
-
|
352
|
-
|
353
|
-
else if (args.data) {
|
354
|
-
const isStringOrBufferOrReadable = typeof args.data === 'string'
|
355
|
-
|| Buffer.isBuffer(args.data)
|
356
|
-
|| isReadable(args.data);
|
357
|
-
if (isGETOrHEAD) {
|
358
|
-
if (!isStringOrBufferOrReadable) {
|
294
|
+
// set normal fields first
|
295
|
+
if (args.data) {
|
359
296
|
for (const field in args.data) {
|
360
|
-
|
297
|
+
formData.append(field, args.data[field]);
|
361
298
|
}
|
362
299
|
}
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
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;
|
367
319
|
}
|
368
320
|
else {
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
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]);
|
375
350
|
}
|
376
351
|
}
|
352
|
+
}
|
353
|
+
else {
|
354
|
+
if (isStringOrBufferOrReadable) {
|
355
|
+
requestOptions.body = args.data;
|
356
|
+
}
|
377
357
|
else {
|
378
|
-
|
379
|
-
|
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
|
+
}
|
380
370
|
}
|
381
371
|
}
|
382
372
|
}
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
this.emit('request', reqMeta);
|
388
|
-
}
|
389
|
-
let response = await undiciRequest(requestUrl, requestOptions);
|
390
|
-
if (response.statusCode === 401 && response.headers['www-authenticate'] &&
|
391
|
-
!requestOptions.headers.authorization && args.digestAuth) {
|
392
|
-
// handle digest auth
|
393
|
-
const authenticateHeaders = response.headers['www-authenticate'];
|
394
|
-
const authenticate = Array.isArray(authenticateHeaders)
|
395
|
-
? authenticateHeaders.find(authHeader => authHeader.startsWith('Digest '))
|
396
|
-
: authenticateHeaders;
|
397
|
-
if (authenticate && authenticate.startsWith('Digest ')) {
|
398
|
-
debug('Request#%d %s: got digest auth header WWW-Authenticate: %s', requestId, requestUrl.href, authenticate);
|
399
|
-
requestOptions.headers.authorization = digestAuthHeader(requestOptions.method, `${requestUrl.pathname}${requestUrl.search}`, authenticate, args.digestAuth);
|
400
|
-
debug('Request#%d %s: auth with digest header: %s', requestId, url, requestOptions.headers.authorization);
|
401
|
-
if (Array.isArray(response.headers['set-cookie'])) {
|
402
|
-
// FIXME: merge exists cookie header
|
403
|
-
requestOptions.headers.cookie = response.headers['set-cookie'].join(';');
|
404
|
-
}
|
405
|
-
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);
|
406
377
|
}
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
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
|
+
}
|
414
396
|
}
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
res.headers = response.headers;
|
423
|
-
res.status = res.statusCode = response.statusCode;
|
424
|
-
res.statusText = STATUS_CODES[res.status] || '';
|
425
|
-
if (res.headers['content-length']) {
|
426
|
-
res.size = parseInt(res.headers['content-length']);
|
427
|
-
}
|
428
|
-
let data = null;
|
429
|
-
if (args.dataType === 'stream') {
|
430
|
-
// streaming mode will disable retry
|
431
|
-
args.retry = 0;
|
432
|
-
// only auto decompress on request args.compressed = true
|
433
|
-
if (args.compressed === true && isCompressedContent) {
|
434
|
-
// gzip or br
|
435
|
-
const decoder = contentEncoding === 'gzip' ? createGunzip() : createBrotliDecompress();
|
436
|
-
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
|
+
}
|
437
404
|
}
|
438
405
|
else {
|
439
|
-
res
|
406
|
+
res.requestUrls.push(requestUrl.href);
|
407
|
+
lastUrl = requestUrl.href;
|
440
408
|
}
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
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']);
|
448
416
|
}
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
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);
|
459
426
|
}
|
460
|
-
|
461
|
-
|
462
|
-
err.name = 'UnzipError';
|
463
|
-
}
|
464
|
-
throw err;
|
427
|
+
else {
|
428
|
+
res = Object.assign(response.body, res);
|
465
429
|
}
|
466
430
|
}
|
467
|
-
if (args.
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
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);
|
473
437
|
}
|
474
438
|
else {
|
475
|
-
|
439
|
+
await pipelinePromise(response.body, args.writeStream);
|
476
440
|
}
|
477
441
|
}
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
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
|
+
}
|
499
466
|
}
|
500
|
-
requestContext.retries++;
|
501
|
-
return await __classPrivateFieldGet(this, _HttpClient_instances, "m", _HttpClient_requestInternal).call(this, url, options, requestContext);
|
502
467
|
}
|
503
|
-
|
504
|
-
|
505
|
-
this
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
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,
|
513
481
|
res,
|
514
|
-
}
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
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;
|
534
506
|
}
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
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;
|
548
539
|
}
|
549
|
-
throw err;
|
550
540
|
}
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
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
|
+
}
|
564
555
|
}
|
565
|
-
}
|
556
|
+
}
|
566
557
|
//# sourceMappingURL=HttpClient.js.map
|