urllib 3.26.0 → 4.1.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/README.md +79 -47
- package/dist/commonjs/HttpAgent.js +1 -0
- package/dist/commonjs/HttpClient.d.ts +1 -0
- package/dist/commonjs/HttpClient.js +20 -43
- package/dist/commonjs/HttpClientError.js +1 -0
- package/dist/commonjs/IncomingHttpHeaders.js +1 -0
- package/dist/commonjs/Request.js +1 -0
- package/dist/commonjs/Response.js +1 -0
- package/dist/commonjs/diagnosticsChannel.js +23 -30
- package/dist/commonjs/index.js +3 -5
- package/dist/commonjs/symbols.js +1 -0
- package/dist/commonjs/utils.d.ts +0 -1
- package/dist/commonjs/utils.js +1 -6
- package/dist/esm/HttpAgent.js +1 -0
- package/dist/esm/HttpClient.d.ts +1 -0
- package/dist/esm/HttpClient.js +17 -40
- package/dist/esm/HttpClientError.js +1 -0
- package/dist/esm/IncomingHttpHeaders.js +1 -0
- package/dist/esm/Request.js +1 -0
- package/dist/esm/Response.js +1 -0
- package/dist/esm/diagnosticsChannel.js +23 -30
- package/dist/esm/index.js +2 -1
- package/dist/esm/symbols.js +1 -0
- package/dist/esm/utils.d.ts +0 -1
- package/dist/esm/utils.js +1 -5
- package/package.json +15 -23
- package/src/HttpClient.ts +18 -40
- package/src/diagnosticsChannel.ts +31 -35
- package/src/index.ts +1 -1
- package/src/utils.ts +1 -7
package/src/HttpClient.ts
CHANGED
@@ -11,34 +11,33 @@ import {
|
|
11
11
|
} from 'node:zlib';
|
12
12
|
import { Blob } from 'node:buffer';
|
13
13
|
import { Readable, pipeline } from 'node:stream';
|
14
|
-
import
|
14
|
+
import { pipeline as pipelinePromise } from 'node:stream/promises';
|
15
15
|
import { basename } from 'node:path';
|
16
16
|
import { createReadStream } from 'node:fs';
|
17
17
|
import { format as urlFormat } from 'node:url';
|
18
18
|
import { performance } from 'node:perf_hooks';
|
19
19
|
import querystring from 'node:querystring';
|
20
|
+
import { setTimeout as sleep } from 'node:timers/promises';
|
20
21
|
import {
|
21
|
-
FormData
|
22
|
+
FormData,
|
22
23
|
request as undiciRequest,
|
23
24
|
Dispatcher,
|
24
25
|
Agent,
|
25
26
|
getGlobalDispatcher,
|
26
27
|
Pool,
|
27
28
|
} from 'undici';
|
29
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
30
|
+
// @ts-ignore
|
28
31
|
import undiciSymbols from 'undici/lib/core/symbols.js';
|
29
|
-
import { FormData as FormDataNode } from 'formdata-node';
|
30
|
-
import { FormDataEncoder } from 'form-data-encoder';
|
31
|
-
import createUserAgent from 'default-user-agent';
|
32
32
|
import mime from 'mime-types';
|
33
33
|
import qs from 'qs';
|
34
|
-
import pump from 'pump';
|
35
34
|
// Compatible with old style formstream
|
36
35
|
import FormStream from 'formstream';
|
37
36
|
import { HttpAgent, CheckAddressFunction } from './HttpAgent.js';
|
38
37
|
import type { IncomingHttpHeaders } from './IncomingHttpHeaders.js';
|
39
38
|
import { RequestURL, RequestOptions, HttpMethod, RequestMeta } from './Request.js';
|
40
39
|
import { RawResponseWithMeta, HttpClientResponse, SocketInfo } from './Response.js';
|
41
|
-
import { parseJSON,
|
40
|
+
import { parseJSON, digestAuthHeader, globalId, performanceTime, isReadable } from './utils.js';
|
42
41
|
import symbols from './symbols.js';
|
43
42
|
import { initDiagnosticsChannel } from './diagnosticsChannel.js';
|
44
43
|
import { HttpClientConnectTimeoutError, HttpClientRequestTimeoutError } from './HttpClientError.js';
|
@@ -49,24 +48,12 @@ type PropertyShouldBe<T, K extends keyof T, V> = Omit<T, K> & { [P in K]: V };
|
|
49
48
|
type IUndiciRequestOption = PropertyShouldBe<UndiciRequestOption, 'headers', IncomingHttpHeaders>;
|
50
49
|
|
51
50
|
const PROTO_RE = /^https?:\/\//i;
|
52
|
-
const FormData = FormDataNative ?? FormDataNode;
|
53
|
-
// impl promise pipeline on Node.js 14
|
54
|
-
const pipelinePromise = stream.promises?.pipeline ?? function pipeline(...args: any[]) {
|
55
|
-
return new Promise<void>((resolve, reject) => {
|
56
|
-
pump(...args, (err?: Error) => {
|
57
|
-
if (err) return reject(err);
|
58
|
-
resolve();
|
59
|
-
});
|
60
|
-
});
|
61
|
-
};
|
62
51
|
|
63
52
|
function noop() {
|
64
53
|
// noop
|
65
54
|
}
|
66
55
|
|
67
56
|
const debug = debuglog('urllib:HttpClient');
|
68
|
-
// Node.js 14 or 16
|
69
|
-
const isNode14Or16 = /v1[46]\./.test(process.version);
|
70
57
|
|
71
58
|
export type ClientOptions = {
|
72
59
|
defaultArgs?: RequestOptions;
|
@@ -127,7 +114,10 @@ class BlobFromStream {
|
|
127
114
|
}
|
128
115
|
}
|
129
116
|
|
130
|
-
export const
|
117
|
+
export const VERSION = 'VERSION';
|
118
|
+
// 'node-urllib/4.0.0 Node.js/18.19.0 (darwin; x64)'
|
119
|
+
export const HEADER_USER_AGENT =
|
120
|
+
`node-urllib/${VERSION} Node.js/${process.version.substring(1)} (${process.platform}; ${process.arch})`;
|
131
121
|
|
132
122
|
function getFileName(stream: Readable) {
|
133
123
|
const filePath: string = (stream as any).path;
|
@@ -216,13 +206,13 @@ export class HttpClient extends EventEmitter {
|
|
216
206
|
getDispatcherPoolStats() {
|
217
207
|
const agent = this.getDispatcher();
|
218
208
|
// origin => Pool Instance
|
219
|
-
const clients: Map<string, WeakRef<Pool>> | undefined = agent
|
209
|
+
const clients: Map<string, WeakRef<Pool>> | undefined = Reflect.get(agent, undiciSymbols.kClients);
|
220
210
|
const poolStatsMap: Record<string, PoolStat> = {};
|
221
211
|
if (!clients) {
|
222
212
|
return poolStatsMap;
|
223
213
|
}
|
224
214
|
for (const [ key, ref ] of clients) {
|
225
|
-
const pool = ref.deref();
|
215
|
+
const pool = typeof ref.deref === 'function' ? ref.deref() : ref as unknown as Pool;
|
226
216
|
const stats = pool?.stats;
|
227
217
|
if (!stats) continue;
|
228
218
|
poolStatsMap[key] = {
|
@@ -460,9 +450,11 @@ export class HttpClient extends EventEmitter {
|
|
460
450
|
} else if (typeof args.files === 'string' || Buffer.isBuffer(args.files)) {
|
461
451
|
uploadFiles.push([ 'file', args.files ]);
|
462
452
|
} else if (typeof args.files === 'object') {
|
463
|
-
|
453
|
+
const files = args.files as Record<string, string | Readable | Buffer>;
|
454
|
+
for (const field in files) {
|
464
455
|
// set custom fileName
|
465
|
-
|
456
|
+
const file = files[field];
|
457
|
+
uploadFiles.push([ field, file, field ]);
|
466
458
|
}
|
467
459
|
}
|
468
460
|
// set normal fields first
|
@@ -487,18 +479,7 @@ export class HttpClient extends EventEmitter {
|
|
487
479
|
isStreamingRequest = true;
|
488
480
|
}
|
489
481
|
}
|
490
|
-
|
491
|
-
if (FormDataNative) {
|
492
|
-
requestOptions.body = formData;
|
493
|
-
} else {
|
494
|
-
// Node.js 14 does not support spec-compliant FormData
|
495
|
-
// https://github.com/octet-stream/form-data#usage
|
496
|
-
const encoder = new FormDataEncoder(formData as any);
|
497
|
-
Object.assign(headers, encoder.headers);
|
498
|
-
// fix "Content-Length":"NaN"
|
499
|
-
delete headers['Content-Length'];
|
500
|
-
requestOptions.body = Readable.from(encoder);
|
501
|
-
}
|
482
|
+
requestOptions.body = formData;
|
502
483
|
} else if (args.content) {
|
503
484
|
if (!isGETOrHEAD) {
|
504
485
|
// handle content
|
@@ -516,7 +497,7 @@ export class HttpClient extends EventEmitter {
|
|
516
497
|
|| isReadable(args.data);
|
517
498
|
if (isGETOrHEAD) {
|
518
499
|
if (!isStringOrBufferOrReadable) {
|
519
|
-
let query;
|
500
|
+
let query: string;
|
520
501
|
if (args.nestedQuerystring) {
|
521
502
|
query = qs.stringify(args.data);
|
522
503
|
} else {
|
@@ -617,9 +598,6 @@ export class HttpClient extends EventEmitter {
|
|
617
598
|
res = Object.assign(response.body, res);
|
618
599
|
}
|
619
600
|
} else if (args.writeStream) {
|
620
|
-
if (isNode14Or16 && args.writeStream.destroyed) {
|
621
|
-
throw new Error('writeStream is destroyed');
|
622
|
-
}
|
623
601
|
if (args.compressed === true && isCompressedContent) {
|
624
602
|
const decoder = contentEncoding === 'gzip' ? createGunzip() : createBrotliDecompress();
|
625
603
|
await pipelinePromise(response.body, decoder, args.writeStream);
|
@@ -20,15 +20,14 @@ let initedDiagnosticsChannel = false;
|
|
20
20
|
// -> undici:request:trailers => { request, trailers }
|
21
21
|
|
22
22
|
function subscribe(name: string, listener: (message: unknown, channelName: string | symbol) => void) {
|
23
|
-
|
24
|
-
diagnosticsChannel.subscribe(name, listener);
|
25
|
-
} else {
|
26
|
-
// TODO: support Node.js 14, will be removed on the next major version
|
27
|
-
diagnosticsChannel.channel(name).subscribe(listener);
|
28
|
-
}
|
23
|
+
diagnosticsChannel.subscribe(name, listener);
|
29
24
|
}
|
30
25
|
|
31
|
-
|
26
|
+
type SocketExtend = Socket & {
|
27
|
+
[key: symbol]: string | number | Date | undefined;
|
28
|
+
};
|
29
|
+
|
30
|
+
function formatSocket(socket: SocketExtend) {
|
32
31
|
if (!socket) return socket;
|
33
32
|
return {
|
34
33
|
localAddress: socket[symbols.kSocketLocalAddress],
|
@@ -41,8 +40,7 @@ function formatSocket(socket: Socket) {
|
|
41
40
|
}
|
42
41
|
|
43
42
|
// make sure error contains socket info
|
44
|
-
const
|
45
|
-
Socket.prototype[kDestroy] = Socket.prototype.destroy;
|
43
|
+
const destroySocket = Socket.prototype.destroy;
|
46
44
|
Socket.prototype.destroy = function(err?: any) {
|
47
45
|
if (err) {
|
48
46
|
Object.defineProperty(err, symbols.kErrorSocket, {
|
@@ -51,12 +49,12 @@ Socket.prototype.destroy = function(err?: any) {
|
|
51
49
|
value: this,
|
52
50
|
});
|
53
51
|
}
|
54
|
-
return this
|
52
|
+
return destroySocket.call(this, err);
|
55
53
|
};
|
56
54
|
|
57
55
|
function getRequestOpaque(request: DiagnosticsChannel.Request, kHandler?: symbol) {
|
58
56
|
if (!kHandler) return;
|
59
|
-
const handler = request
|
57
|
+
const handler = Reflect.get(request, kHandler);
|
60
58
|
// maxRedirects = 0 will get [Symbol(handler)]: RequestHandler {
|
61
59
|
// responseHeaders: null,
|
62
60
|
// opaque: {
|
@@ -70,7 +68,7 @@ function getRequestOpaque(request: DiagnosticsChannel.Request, kHandler?: symbol
|
|
70
68
|
}
|
71
69
|
|
72
70
|
export function initDiagnosticsChannel() {
|
73
|
-
//
|
71
|
+
// make sure init global DiagnosticsChannel once
|
74
72
|
if (initedDiagnosticsChannel) return;
|
75
73
|
initedDiagnosticsChannel = true;
|
76
74
|
|
@@ -97,29 +95,27 @@ export function initDiagnosticsChannel() {
|
|
97
95
|
opaque[symbols.kRequestTiming].queuing = performanceTime(opaque[symbols.kRequestStartTime]);
|
98
96
|
});
|
99
97
|
|
100
|
-
// diagnosticsChannel.channel('undici:client:beforeConnect')
|
101
|
-
|
102
98
|
subscribe('undici:client:connectError', (message, name) => {
|
103
|
-
const { error, connectParams } = message as DiagnosticsChannel.ClientConnectErrorMessage & { error: any };
|
104
|
-
let
|
105
|
-
if (!
|
106
|
-
|
99
|
+
const { error, connectParams, socket } = message as DiagnosticsChannel.ClientConnectErrorMessage & { error: any, socket: SocketExtend };
|
100
|
+
let sock = socket;
|
101
|
+
if (!sock && error[symbols.kErrorSocket]) {
|
102
|
+
sock = error[symbols.kErrorSocket];
|
107
103
|
}
|
108
|
-
if (
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
104
|
+
if (sock) {
|
105
|
+
sock[symbols.kSocketId] = globalId('UndiciSocket');
|
106
|
+
sock[symbols.kSocketConnectErrorTime] = new Date();
|
107
|
+
sock[symbols.kHandledRequests] = 0;
|
108
|
+
sock[symbols.kHandledResponses] = 0;
|
113
109
|
// copy local address to symbol, avoid them be reset after request error throw
|
114
|
-
if (
|
115
|
-
|
116
|
-
|
110
|
+
if (sock.localAddress) {
|
111
|
+
sock[symbols.kSocketLocalAddress] = sock.localAddress;
|
112
|
+
sock[symbols.kSocketLocalPort] = sock.localPort;
|
117
113
|
}
|
118
|
-
|
119
|
-
|
120
|
-
|
114
|
+
sock[symbols.kSocketConnectProtocol] = connectParams.protocol;
|
115
|
+
sock[symbols.kSocketConnectHost] = connectParams.host;
|
116
|
+
sock[symbols.kSocketConnectPort] = connectParams.port;
|
121
117
|
debug('[%s] Socket#%d connectError, connectParams: %o, error: %s, (sock: %o)',
|
122
|
-
name,
|
118
|
+
name, sock[symbols.kSocketId], connectParams, (error as Error).message, formatSocket(sock));
|
123
119
|
} else {
|
124
120
|
debug('[%s] connectError, connectParams: %o, error: %o',
|
125
121
|
name, connectParams, error);
|
@@ -128,7 +124,7 @@ export function initDiagnosticsChannel() {
|
|
128
124
|
|
129
125
|
// This message is published after a connection is established.
|
130
126
|
subscribe('undici:client:connected', (message, name) => {
|
131
|
-
const { socket, connectParams } = message as DiagnosticsChannel.ClientConnectedMessage;
|
127
|
+
const { socket, connectParams } = message as DiagnosticsChannel.ClientConnectedMessage & { socket: SocketExtend };
|
132
128
|
socket[symbols.kSocketId] = globalId('UndiciSocket');
|
133
129
|
socket[symbols.kSocketStartTime] = performance.now();
|
134
130
|
socket[symbols.kSocketConnectedTime] = new Date();
|
@@ -145,11 +141,11 @@ export function initDiagnosticsChannel() {
|
|
145
141
|
|
146
142
|
// This message is published right before the first byte of the request is written to the socket.
|
147
143
|
subscribe('undici:client:sendHeaders', (message, name) => {
|
148
|
-
const { request, socket } = message as DiagnosticsChannel.ClientSendHeadersMessage;
|
144
|
+
const { request, socket } = message as DiagnosticsChannel.ClientSendHeadersMessage & { socket: SocketExtend };
|
149
145
|
const opaque = getRequestOpaque(request, kHandler);
|
150
146
|
if (!opaque || !opaque[symbols.kRequestId]) return;
|
151
147
|
|
152
|
-
socket[symbols.kHandledRequests]++;
|
148
|
+
(socket[symbols.kHandledRequests] as number)++;
|
153
149
|
// attach socket to opaque
|
154
150
|
opaque[symbols.kRequestSocket] = socket;
|
155
151
|
debug('[%s] Request#%d send headers on Socket#%d (handled %d requests, sock: %o)',
|
@@ -158,11 +154,11 @@ export function initDiagnosticsChannel() {
|
|
158
154
|
|
159
155
|
if (!opaque[symbols.kEnableRequestTiming]) return;
|
160
156
|
opaque[symbols.kRequestTiming].requestHeadersSent = performanceTime(opaque[symbols.kRequestStartTime]);
|
161
|
-
// first socket need to
|
157
|
+
// first socket need to calculate the connected time
|
162
158
|
if (socket[symbols.kHandledRequests] === 1) {
|
163
159
|
// kSocketStartTime - kRequestStartTime = connected time
|
164
160
|
opaque[symbols.kRequestTiming].connected =
|
165
|
-
performanceTime(opaque[symbols.kRequestStartTime], socket[symbols.kSocketStartTime]);
|
161
|
+
performanceTime(opaque[symbols.kRequestStartTime], socket[symbols.kSocketStartTime] as number);
|
166
162
|
}
|
167
163
|
});
|
168
164
|
|
package/src/index.ts
CHANGED
package/src/utils.ts
CHANGED
@@ -3,7 +3,7 @@ import { Readable } from 'node:stream';
|
|
3
3
|
import { performance } from 'node:perf_hooks';
|
4
4
|
import type { FixJSONCtlChars } from './Request.js';
|
5
5
|
|
6
|
-
const JSONCtlCharsMap = {
|
6
|
+
const JSONCtlCharsMap: Record<string, string> = {
|
7
7
|
'"': '\\"', // \u0022
|
8
8
|
'\\': '\\\\', // \u005c
|
9
9
|
'\b': '\\b', // \u0008
|
@@ -49,12 +49,6 @@ export function parseJSON(data: string, fixJSONCtlChars?: FixJSONCtlChars) {
|
|
49
49
|
return data;
|
50
50
|
}
|
51
51
|
|
52
|
-
export function sleep(ms: number) {
|
53
|
-
return new Promise<void>(resolve => {
|
54
|
-
setTimeout(resolve, ms);
|
55
|
-
});
|
56
|
-
}
|
57
|
-
|
58
52
|
function md5(s: string) {
|
59
53
|
const sum = createHash('md5');
|
60
54
|
sum.update(s, 'utf8');
|