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/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 stream from 'node:stream';
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 as FormDataNative,
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, sleep, digestAuthHeader, globalId, performanceTime, isReadable } from './utils.js';
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 HEADER_USER_AGENT = createUserAgent('node-urllib', 'VERSION');
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[undiciSymbols.kClients];
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
- for (const field in args.files) {
453
+ const files = args.files as Record<string, string | Readable | Buffer>;
454
+ for (const field in files) {
464
455
  // set custom fileName
465
- uploadFiles.push([ field, args.files[field], field ]);
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
- if (typeof diagnosticsChannel.subscribe === 'function') {
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
- function formatSocket(socket: Socket) {
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 kDestroy = Symbol('kDestroy');
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[kDestroy](err);
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[kHandler];
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
- // makre sure init global DiagnosticsChannel once
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 { socket } = message as DiagnosticsChannel.ClientConnectErrorMessage;
105
- if (!socket && error[symbols.kErrorSocket]) {
106
- socket = error[symbols.kErrorSocket];
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 (socket) {
109
- socket[symbols.kSocketId] = globalId('UndiciSocket');
110
- socket[symbols.kSocketConnectErrorTime] = new Date();
111
- socket[symbols.kHandledRequests] = 0;
112
- socket[symbols.kHandledResponses] = 0;
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 (socket.localAddress) {
115
- socket[symbols.kSocketLocalAddress] = socket.localAddress;
116
- socket[symbols.kSocketLocalPort] = socket.localPort;
110
+ if (sock.localAddress) {
111
+ sock[symbols.kSocketLocalAddress] = sock.localAddress;
112
+ sock[symbols.kSocketLocalPort] = sock.localPort;
117
113
  }
118
- socket[symbols.kSocketConnectProtocol] = connectParams.protocol;
119
- socket[symbols.kSocketConnectHost] = connectParams.host;
120
- socket[symbols.kSocketConnectPort] = connectParams.port;
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, socket[symbols.kSocketId], connectParams, (error as Error).message, formatSocket(socket));
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 caculate the connected time
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
@@ -1,4 +1,4 @@
1
- import LRU from 'ylru';
1
+ import { LRU } from 'ylru';
2
2
  import { HttpClient, HEADER_USER_AGENT } from './HttpClient.js';
3
3
  import { RequestOptions, RequestURL } from './Request.js';
4
4
 
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');