urllib 3.1.3 โ†’ 3.2.1

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/History.md CHANGED
@@ -1,4 +1,19 @@
1
1
 
2
+ 3.2.1 / 2022-09-27
3
+ ==================
4
+
5
+ **fixes**
6
+ * [[`456c73b`](http://github.com/node-modules/urllib/commit/456c73b3dc02e1323cb8db99b60ffd191e5d7abb)] - ๐Ÿ› FIX: http default protocol for URL argument (#404) (fengmk2 <<fengmk2@gmail.com>>)
7
+
8
+ 3.2.0 / 2022-09-26
9
+ ==================
10
+
11
+ **others**
12
+ * [[`315f4a0`](http://github.com/node-modules/urllib/commit/315f4a0bf69abc2b29247fcf1a606c44412a86f3)] - ๐Ÿ“ฆ NEW: Support timing and socket info (#400) (fengmk2 <<fengmk2@gmail.com>>)
13
+ * [[`414692b`](http://github.com/node-modules/urllib/commit/414692b183fb205acfb163f8aef4f853ce1b97e1)] - ๐Ÿงช๏ธ TEST: Add diagnostics_channel test cases (#398) (fengmk2 <<fengmk2@gmail.com>>)
14
+ * [[`65abb60`](http://github.com/node-modules/urllib/commit/65abb6074e5098834dd964a2bedaffd64a703ef9)] - ๐Ÿค– TEST: Use vitest instead of jest (#399) (fengmk2 <<fengmk2@gmail.com>>)
15
+ * [[`5f0c4a8`](http://github.com/node-modules/urllib/commit/5f0c4a8da84ef9954942bcd78721a5445e0cb095)] - ๐Ÿ“– DOC: Update contributors (fengmk2 <<fengmk2@gmail.com>>)
16
+
2
17
  3.1.3 / 2022-09-09
3
18
  ==================
4
19
 
package/README.md CHANGED
@@ -251,7 +251,7 @@ Response is normal object, it contains:
251
251
  ## Run test with debug log
252
252
 
253
253
  ```bash
254
- NODE_DEBUG=urllib npm test
254
+ NODE_DEBUG=urllib:* npm test
255
255
  ```
256
256
 
257
257
  ## Mocking Request
@@ -323,12 +323,12 @@ Fork [undici benchmarks script](https://github.com/fengmk2/undici/blob/urllib-be
323
323
  | :---: | :---: | :---: | :---: | :---: | :---: |
324
324
  |[<img src="https://avatars.githubusercontent.com/u/14790466?v=4" width="100px;"/><br/><sub><b>greenkeeperio-bot</b></sub>](https://github.com/greenkeeperio-bot)<br/>|[<img src="https://avatars.githubusercontent.com/u/227713?v=4" width="100px;"/><br/><sub><b>atian25</b></sub>](https://github.com/atian25)<br/>|[<img src="https://avatars.githubusercontent.com/u/5381764?v=4" width="100px;"/><br/><sub><b>paambaati</b></sub>](https://github.com/paambaati)<br/>|[<img src="https://avatars.githubusercontent.com/u/1433247?v=4" width="100px;"/><br/><sub><b>denghongcai</b></sub>](https://github.com/denghongcai)<br/>|[<img src="https://avatars.githubusercontent.com/u/2842176?v=4" width="100px;"/><br/><sub><b>XadillaX</b></sub>](https://github.com/XadillaX)<br/>|[<img src="https://avatars.githubusercontent.com/u/1147375?v=4" width="100px;"/><br/><sub><b>alsotang</b></sub>](https://github.com/alsotang)<br/>|
325
325
  |[<img src="https://avatars.githubusercontent.com/u/546535?v=4" width="100px;"/><br/><sub><b>leoner</b></sub>](https://github.com/leoner)<br/>|[<img src="https://avatars.githubusercontent.com/u/19908330?v=4" width="100px;"/><br/><sub><b>hyj1991</b></sub>](https://github.com/hyj1991)<br/>|[<img src="https://avatars.githubusercontent.com/u/1747852?v=4" width="100px;"/><br/><sub><b>isayme</b></sub>](https://github.com/isayme)<br/>|[<img src="https://avatars.githubusercontent.com/u/6897780?v=4" width="100px;"/><br/><sub><b>killagu</b></sub>](https://github.com/killagu)<br/>|[<img src="https://avatars.githubusercontent.com/u/252317?v=4" width="100px;"/><br/><sub><b>cyjake</b></sub>](https://github.com/cyjake)<br/>|[<img src="https://avatars.githubusercontent.com/u/5856440?v=4" width="100px;"/><br/><sub><b>whxaxes</b></sub>](https://github.com/whxaxes)<br/>|
326
- |[<img src="https://avatars.githubusercontent.com/u/309219?v=4" width="100px;"/><br/><sub><b>chadxz</b></sub>](https://github.com/chadxz)<br/>|[<img src="https://avatars.githubusercontent.com/u/5139554?v=4" width="100px;"/><br/><sub><b>danielwpz</b></sub>](https://github.com/danielwpz)<br/>|[<img src="https://avatars.githubusercontent.com/u/5127897?v=4" width="100px;"/><br/><sub><b>danielsss</b></sub>](https://github.com/danielsss)<br/>|[<img src="https://avatars.githubusercontent.com/u/3367820?v=4" width="100px;"/><br/><sub><b>Jeff-Tian</b></sub>](https://github.com/Jeff-Tian)<br/>|[<img src="https://avatars.githubusercontent.com/u/32407?v=4" width="100px;"/><br/><sub><b>jedahan</b></sub>](https://github.com/jedahan)<br/>|[<img src="https://avatars.githubusercontent.com/u/17075261?v=4" width="100px;"/><br/><sub><b>nick-ng</b></sub>](https://github.com/nick-ng)<br/>|
327
- |[<img src="https://avatars.githubusercontent.com/u/1706595?v=4" width="100px;"/><br/><sub><b>rishavsharan</b></sub>](https://github.com/rishavsharan)<br/>|[<img src="https://avatars.githubusercontent.com/u/1886161?v=4" width="100px;"/><br/><sub><b>willizm</b></sub>](https://github.com/willizm)<br/>|[<img src="https://avatars.githubusercontent.com/u/7227589?v=4" width="100px;"/><br/><sub><b>davidkhala</b></sub>](https://github.com/davidkhala)<br/>|[<img src="https://avatars.githubusercontent.com/u/535479?v=4" width="100px;"/><br/><sub><b>aleafs</b></sub>](https://github.com/aleafs)<br/>|[<img src="https://avatars.githubusercontent.com/u/3689968?v=4" width="100px;"/><br/><sub><b>Amunu</b></sub>](https://github.com/Amunu)<br/>|[<img src="https://avatars.githubusercontent.com/in/9426?v=4" width="100px;"/><br/><sub><b>azure-pipelines[bot]</b></sub>](https://github.com/apps/azure-pipelines)<br/>|
328
- |[<img src="https://avatars.githubusercontent.com/u/1281323?v=4" width="100px;"/><br/><sub><b>changzhiwin</b></sub>](https://github.com/changzhiwin)<br/>|[<img src="https://avatars.githubusercontent.com/u/929503?v=4" width="100px;"/><br/><sub><b>yuzhigang33</b></sub>](https://github.com/yuzhigang33)<br/>|[<img src="https://avatars.githubusercontent.com/u/981128?v=4" width="100px;"/><br/><sub><b>fishbar</b></sub>](https://github.com/fishbar)<br/>|[<img src="https://avatars.githubusercontent.com/u/1207064?v=4" width="100px;"/><br/><sub><b>gxcsoccer</b></sub>](https://github.com/gxcsoccer)<br/>|[<img src="https://avatars.githubusercontent.com/u/17476119?v=4" width="100px;"/><br/><sub><b>mars-coder</b></sub>](https://github.com/mars-coder)<br/>|[<img src="https://avatars.githubusercontent.com/u/929179?v=4" width="100px;"/><br/><sub><b>rockdai</b></sub>](https://github.com/rockdai)<br/>|
329
- [<img src="https://avatars.githubusercontent.com/u/2196373?v=4" width="100px;"/><br/><sub><b>dickeylth</b></sub>](https://github.com/dickeylth)<br/>|[<img src="https://avatars.githubusercontent.com/u/13050025?v=4" width="100px;"/><br/><sub><b>aladdin-add</b></sub>](https://github.com/aladdin-add)<br/>
326
+ |[<img src="https://avatars.githubusercontent.com/u/309219?v=4" width="100px;"/><br/><sub><b>chadxz</b></sub>](https://github.com/chadxz)<br/>|[<img src="https://avatars.githubusercontent.com/u/2055702?v=4" width="100px;"/><br/><sub><b>adapt0</b></sub>](https://github.com/adapt0)<br/>|[<img src="https://avatars.githubusercontent.com/u/5139554?v=4" width="100px;"/><br/><sub><b>danielwpz</b></sub>](https://github.com/danielwpz)<br/>|[<img src="https://avatars.githubusercontent.com/u/5127897?v=4" width="100px;"/><br/><sub><b>danielsss</b></sub>](https://github.com/danielsss)<br/>|[<img src="https://avatars.githubusercontent.com/u/3367820?v=4" width="100px;"/><br/><sub><b>Jeff-Tian</b></sub>](https://github.com/Jeff-Tian)<br/>|[<img src="https://avatars.githubusercontent.com/u/32407?v=4" width="100px;"/><br/><sub><b>jedahan</b></sub>](https://github.com/jedahan)<br/>|
327
+ |[<img src="https://avatars.githubusercontent.com/u/17075261?v=4" width="100px;"/><br/><sub><b>nick-ng</b></sub>](https://github.com/nick-ng)<br/>|[<img src="https://avatars.githubusercontent.com/u/1706595?v=4" width="100px;"/><br/><sub><b>rishavsharan</b></sub>](https://github.com/rishavsharan)<br/>|[<img src="https://avatars.githubusercontent.com/u/1886161?v=4" width="100px;"/><br/><sub><b>willizm</b></sub>](https://github.com/willizm)<br/>|[<img src="https://avatars.githubusercontent.com/u/7227589?v=4" width="100px;"/><br/><sub><b>davidkhala</b></sub>](https://github.com/davidkhala)<br/>|[<img src="https://avatars.githubusercontent.com/u/535479?v=4" width="100px;"/><br/><sub><b>aleafs</b></sub>](https://github.com/aleafs)<br/>|[<img src="https://avatars.githubusercontent.com/u/3689968?v=4" width="100px;"/><br/><sub><b>Amunu</b></sub>](https://github.com/Amunu)<br/>|
328
+ |[<img src="https://avatars.githubusercontent.com/in/9426?v=4" width="100px;"/><br/><sub><b>azure-pipelines[bot]</b></sub>](https://github.com/apps/azure-pipelines)<br/>|[<img src="https://avatars.githubusercontent.com/u/1281323?v=4" width="100px;"/><br/><sub><b>changzhiwin</b></sub>](https://github.com/changzhiwin)<br/>|[<img src="https://avatars.githubusercontent.com/u/929503?v=4" width="100px;"/><br/><sub><b>yuzhigang33</b></sub>](https://github.com/yuzhigang33)<br/>|[<img src="https://avatars.githubusercontent.com/u/981128?v=4" width="100px;"/><br/><sub><b>fishbar</b></sub>](https://github.com/fishbar)<br/>|[<img src="https://avatars.githubusercontent.com/u/1207064?v=4" width="100px;"/><br/><sub><b>gxcsoccer</b></sub>](https://github.com/gxcsoccer)<br/>|[<img src="https://avatars.githubusercontent.com/u/17476119?v=4" width="100px;"/><br/><sub><b>mars-coder</b></sub>](https://github.com/mars-coder)<br/>|
329
+ [<img src="https://avatars.githubusercontent.com/u/929179?v=4" width="100px;"/><br/><sub><b>rockdai</b></sub>](https://github.com/rockdai)<br/>|[<img src="https://avatars.githubusercontent.com/u/2196373?v=4" width="100px;"/><br/><sub><b>dickeylth</b></sub>](https://github.com/dickeylth)<br/>|[<img src="https://avatars.githubusercontent.com/u/13050025?v=4" width="100px;"/><br/><sub><b>aladdin-add</b></sub>](https://github.com/aladdin-add)<br/>
330
330
 
331
- This project follows the git-contributor [spec](https://github.com/xudafeng/git-contributor), auto updated at `Tue Jul 05 2022 16:17:31 GMT+0800`.
331
+ This project follows the git-contributor [spec](https://github.com/xudafeng/git-contributor), auto updated at `Fri Sep 09 2022 21:45:49 GMT+0800`.
332
332
 
333
333
  <!-- GITCONTRIBUTOR_END -->
334
334
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "urllib",
3
- "version": "3.1.3",
3
+ "version": "3.2.1",
4
4
  "publishConfig": {
5
5
  "tag": "latest"
6
6
  },
@@ -49,8 +49,9 @@
49
49
  "build:cjs:test": "cd test/cjs && rm -rf node_modules && npm link ../.. && node index.js",
50
50
  "build:esm:test": "cd test/esm && rm -rf node_modules && npm link ../.. && node index.js",
51
51
  "build:test": "npm run build && npm run build:cjs:test && npm run build:esm:test",
52
- "test": "tsc --version && jest --coverage",
53
- "ci": "npm run lint && npm run test && npm run build:test",
52
+ "test": "vitest run",
53
+ "cov": "vitest run --coverage --no-threads",
54
+ "ci": "npm run lint && npm run cov && npm run build:test",
54
55
  "contributor": "git-contributor",
55
56
  "prepack": "npm run build && rm -rf src/*.tsbuildinfo"
56
57
  },
@@ -67,10 +68,10 @@
67
68
  "devDependencies": {
68
69
  "@types/busboy": "^1.5.0",
69
70
  "@types/default-user-agent": "^1.0.0",
70
- "@types/jest": "28",
71
71
  "@types/mime-types": "^2.1.1",
72
72
  "@types/pump": "^1.1.1",
73
73
  "@types/selfsigned": "^2.0.1",
74
+ "@vitest/coverage-c8": "^0.23.4",
74
75
  "busboy": "^1.6.0",
75
76
  "coffee": "5",
76
77
  "egg-ci": "2",
@@ -78,14 +79,12 @@
78
79
  "eslint-config-egg": "^12.0.0",
79
80
  "git-contributor": "1",
80
81
  "iconv-lite": "^0.6.3",
81
- "jest": "28",
82
- "jest-summary-reporter": "^0.0.2",
83
82
  "selfsigned": "^2.0.1",
84
- "ts-jest": "28",
85
- "typescript": "4"
83
+ "typescript": "^4.8.3",
84
+ "vitest": "^0.23.4"
86
85
  },
87
86
  "engines": {
88
- "node": ">= 14.0.0"
87
+ "node": ">= 14.17.0"
89
88
  },
90
89
  "ci": {
91
90
  "version": "14, 16, 18"
package/src/HttpClient.ts CHANGED
@@ -26,9 +26,12 @@ import mime from 'mime-types';
26
26
  import pump from 'pump';
27
27
  import { HttpAgent, CheckAddressFunction } from './HttpAgent';
28
28
  import { RequestURL, RequestOptions, HttpMethod } from './Request';
29
- import { HttpClientResponseMeta, HttpClientResponse, ReadableWithMeta } from './Response';
30
- import { parseJSON, sleep, digestAuthHeader } from './utils';
29
+ import { HttpClientResponseMeta, HttpClientResponse, ReadableWithMeta, BaseResponseMeta, SocketInfo } from './Response';
30
+ import { parseJSON, sleep, digestAuthHeader, globalId, performanceTime } from './utils';
31
+ import symbols from './symbols';
32
+ import { initDiagnosticsChannel } from './diagnosticsChannel';
31
33
 
34
+ const PROTO_RE = /^https?:\/\//i;
32
35
  const FormData = FormDataNative ?? FormDataNode;
33
36
  // impl isReadable on Node.js 14
34
37
  const isReadable = stream.isReadable ?? function isReadable(stream: any) {
@@ -48,10 +51,7 @@ function noop() {
48
51
  // noop
49
52
  }
50
53
 
51
- const MAX_REQURE_ID_VALUE = Math.pow(2, 31) - 10;
52
- let globalRequestId = 0;
53
-
54
- const debug = debuglog('urllib');
54
+ const debug = debuglog('urllib:HttpClient');
55
55
 
56
56
  export type ClientOptions = {
57
57
  defaultArgs?: RequestOptions;
@@ -127,10 +127,6 @@ function defaultIsRetry(response: HttpClientResponse) {
127
127
  return response.status >= 500;
128
128
  }
129
129
 
130
- function performanceTime(startTime: number) {
131
- return Math.floor((performance.now() - startTime) * 1000) / 1000;
132
- }
133
-
134
130
  type RequestContext = {
135
131
  retries: number;
136
132
  };
@@ -149,6 +145,7 @@ export class HttpClient extends EventEmitter {
149
145
  connect: clientOptions.connect,
150
146
  });
151
147
  }
148
+ initDiagnosticsChannel();
152
149
  }
153
150
 
154
151
  async request(url: RequestURL, options?: RequestOptions) {
@@ -156,12 +153,17 @@ export class HttpClient extends EventEmitter {
156
153
  }
157
154
 
158
155
  async #requestInternal(url: RequestURL, options?: RequestOptions, requestContext?: RequestContext): Promise<HttpClientResponse> {
159
- if (globalRequestId >= MAX_REQURE_ID_VALUE) {
160
- globalRequestId = 0;
156
+ const requestId = globalId('HttpClientRequest');
157
+ let requestUrl: URL;
158
+ if (typeof url === 'string') {
159
+ if (!PROTO_RE.test(url)) {
160
+ // Support `request('www.server.com')`
161
+ url = 'http://' + url;
162
+ }
163
+ requestUrl = new URL(url);
164
+ } else {
165
+ requestUrl = url;
161
166
  }
162
- const requestId = ++globalRequestId;
163
-
164
- const requestUrl = typeof url === 'string' ? new URL(url) : url;
165
167
  const args = {
166
168
  retry: 0,
167
169
  ...this.#defaultArgs,
@@ -173,6 +175,32 @@ export class HttpClient extends EventEmitter {
173
175
  };
174
176
  const requestStartTime = performance.now();
175
177
 
178
+ // https://developer.chrome.com/docs/devtools/network/reference/?utm_source=devtools#timing-explanation
179
+ const timing = {
180
+ // socket assigned
181
+ queuing: 0,
182
+ // dns lookup time
183
+ // dnslookup: 0,
184
+ // socket connected
185
+ connected: 0,
186
+ // request headers sent
187
+ requestHeadersSent: 0,
188
+ // request sent, including headers and body
189
+ requestSent: 0,
190
+ // Time to first byte (TTFB), the response headers have been received
191
+ waiting: 0,
192
+ // the response body and trailers have been received
193
+ contentDownload: 0,
194
+ };
195
+ const orginalOpaque = args.opaque;
196
+ // using opaque to diagnostics channel, binding request and socket
197
+ const internalOpaque = {
198
+ [symbols.kRequestId]: requestId,
199
+ [symbols.kRequestStartTime]: requestStartTime,
200
+ [symbols.kEnableRequestTiming]: !!args.timing,
201
+ [symbols.kRequestTiming]: timing,
202
+ [symbols.kRequestOrginalOpaque]: orginalOpaque,
203
+ };
176
204
  const reqMeta = {
177
205
  requestId,
178
206
  url: requestUrl.href,
@@ -180,6 +208,18 @@ export class HttpClient extends EventEmitter {
180
208
  ctx: args.ctx,
181
209
  retries: requestContext.retries,
182
210
  };
211
+ const socketInfo = {
212
+ id: 0,
213
+ localAddress: '',
214
+ localPort: 0,
215
+ remoteAddress: '',
216
+ remotePort: 0,
217
+ remoteFamily: '',
218
+ bytesWritten: 0,
219
+ bytesRead: 0,
220
+ handledRequests: 0,
221
+ handledResponses: 0,
222
+ };
183
223
  // keep urllib createCallbackResponse style
184
224
  const resHeaders: IncomingHttpHeaders = {};
185
225
  const res: HttpClientResponseMeta = {
@@ -191,10 +231,8 @@ export class HttpClient extends EventEmitter {
191
231
  rt: 0,
192
232
  keepAliveSocket: true,
193
233
  requestUrls: [],
194
- timing: {
195
- waiting: 0,
196
- contentDownload: 0,
197
- },
234
+ timing,
235
+ socket: socketInfo,
198
236
  };
199
237
 
200
238
  let headersTimeout = 5000;
@@ -245,7 +283,6 @@ export class HttpClient extends EventEmitter {
245
283
  headers.authorization = `Basic ${Buffer.from(args.auth).toString('base64')}`;
246
284
  }
247
285
 
248
- let opaque = args.opaque;
249
286
  try {
250
287
  const requestOptions: UndiciRquestOptions = {
251
288
  method,
@@ -253,7 +290,7 @@ export class HttpClient extends EventEmitter {
253
290
  maxRedirections: args.maxRedirects ?? 10,
254
291
  headersTimeout,
255
292
  bodyTimeout,
256
- opaque,
293
+ opaque: internalOpaque,
257
294
  dispatcher: this.#dispatcher,
258
295
  };
259
296
  if (args.followRedirect === false) {
@@ -383,11 +420,6 @@ export class HttpClient extends EventEmitter {
383
420
  }
384
421
  }
385
422
 
386
- opaque = response.opaque;
387
- if (args.timing) {
388
- res.timing.waiting = performanceTime(requestStartTime);
389
- }
390
-
391
423
  const context = response.context as { history: URL[] };
392
424
  let lastUrl = '';
393
425
  if (context?.history) {
@@ -413,10 +445,12 @@ export class HttpClient extends EventEmitter {
413
445
  if (args.dataType === 'stream') {
414
446
  // streaming mode will disable retry
415
447
  args.retry = 0;
416
- const meta = {
448
+ const meta: BaseResponseMeta = {
417
449
  status: res.status,
418
450
  statusCode: res.statusCode,
419
451
  headers: res.headers,
452
+ timing,
453
+ socket: socketInfo,
420
454
  };
421
455
  if (isCompressedContent) {
422
456
  // gzip or br
@@ -458,12 +492,11 @@ export class HttpClient extends EventEmitter {
458
492
  }
459
493
  }
460
494
  res.rt = performanceTime(requestStartTime);
461
- if (args.timing) {
462
- res.timing.contentDownload = res.rt;
463
- }
495
+ // get real socket info from internalOpaque
496
+ this.#updateSocketInfo(socketInfo, internalOpaque);
464
497
 
465
498
  const clientResponse: HttpClientResponse = {
466
- opaque,
499
+ opaque: orginalOpaque,
467
500
  data,
468
501
  status: res.status,
469
502
  statusCode: res.status,
@@ -507,7 +540,7 @@ export class HttpClient extends EventEmitter {
507
540
  } else if (err.name === 'BodyTimeoutError') {
508
541
  err = new HttpClientRequestTimeoutError(bodyTimeout, { cause: e });
509
542
  }
510
- err.opaque = opaque;
543
+ err.opaque = orginalOpaque;
511
544
  err.status = res.status;
512
545
  err.headers = res.headers;
513
546
  err.res = res;
@@ -516,9 +549,7 @@ export class HttpClient extends EventEmitter {
516
549
  res.requestUrls.push(requestUrl.href);
517
550
  }
518
551
  res.rt = performanceTime(requestStartTime);
519
- if (args.timing) {
520
- res.timing.contentDownload = res.rt;
521
- }
552
+ this.#updateSocketInfo(socketInfo, internalOpaque);
522
553
 
523
554
  if (this.listenerCount('response') > 0) {
524
555
  this.emit('response', {
@@ -532,4 +563,20 @@ export class HttpClient extends EventEmitter {
532
563
  throw err;
533
564
  }
534
565
  }
566
+
567
+ #updateSocketInfo(socketInfo: SocketInfo, internalOpaque: any) {
568
+ const socket = internalOpaque[symbols.kRequestSocket];
569
+ if (socket) {
570
+ socketInfo.id = socket[symbols.kSocketId];
571
+ socketInfo.handledRequests = socket[symbols.kHandledRequests];
572
+ socketInfo.handledResponses = socket[symbols.kHandledResponses];
573
+ socketInfo.localAddress = socket.localAddress;
574
+ socketInfo.localPort = socket.localPort;
575
+ socketInfo.remoteAddress = socket.remoteAddress;
576
+ socketInfo.remotePort = socket.remotePort;
577
+ socketInfo.remoteFamily = socket.remoteFamily;
578
+ socketInfo.bytesRead = socket.bytesRead;
579
+ socketInfo.bytesWritten = socket.bytesWritten;
580
+ }
581
+ }
535
582
  }
package/src/Request.ts CHANGED
@@ -96,7 +96,6 @@ export type RequestOptions = {
96
96
  * */
97
97
  gzip?: boolean;
98
98
  /**
99
- * @deprecated
100
99
  * Enable timing or not, default is false.
101
100
  * */
102
101
  timing?: boolean;
package/src/Response.ts CHANGED
@@ -1,37 +1,61 @@
1
1
  import { Readable } from 'stream';
2
2
  import { IncomingHttpHeaders } from 'http';
3
3
 
4
- export type HttpClientResponseMeta = {
4
+ export type SocketInfo = {
5
+ id: number;
6
+ localAddress: string;
7
+ localPort: number;
8
+ remoteAddress: string;
9
+ remotePort: number;
10
+ remoteFamily: string;
11
+ bytesWritten: number;
12
+ bytesRead: number;
13
+ handledRequests: number;
14
+ handledResponses: number;
15
+ };
16
+
17
+ /**
18
+ * https://eggjs.org/en/core/httpclient.html#timing-boolean
19
+ */
20
+ export type Timing = {
21
+ // socket assigned
22
+ queuing: number;
23
+ // dns lookup time
24
+ // dnslookup: number;
25
+ // socket connected
26
+ connected: number;
27
+ // request headers sent
28
+ requestHeadersSent: number;
29
+ // request sent, including headers and body
30
+ requestSent: number;
31
+ // Time to first byte (TTFB), the response headers have been received
32
+ waiting: number;
33
+ // the response body and trailers have been received
34
+ contentDownload: number;
35
+ };
36
+
37
+ export type BaseResponseMeta = {
5
38
  status: number;
6
39
  statusCode: number;
7
40
  headers: IncomingHttpHeaders;
41
+ timing: Timing;
42
+ // SocketInfo
43
+ socket: SocketInfo;
44
+ };
45
+
46
+ export type HttpClientResponseMeta = BaseResponseMeta & {
8
47
  size: number;
9
48
  aborted: boolean;
10
49
  rt: number;
11
50
  keepAliveSocket: boolean;
12
51
  requestUrls: string[];
13
- /**
14
- * https://eggjs.org/en/core/httpclient.html#timing-boolean
15
- */
16
- timing: {
17
- contentDownload: number;
18
- waiting: number;
19
- };
20
- // remoteAddress: remoteAddress,
21
- // remotePort: remotePort,
22
- // socketHandledRequests: socketHandledRequests,
23
- // socketHandledResponses: socketHandledResponses,
24
52
  };
25
53
 
26
- export type ReadableWithMeta = Readable & {
27
- status: number;
28
- statusCode: number;
29
- headers: IncomingHttpHeaders;
30
- };
54
+ export type ReadableWithMeta = Readable & BaseResponseMeta;
31
55
 
32
56
  export type HttpClientResponse = {
33
57
  opaque: unknown;
34
- data: any
58
+ data: any;
35
59
  status: number;
36
60
  // alias to status, keep compatibility
37
61
  statusCode: number;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  var _a, _b, _c;
3
- var _BlobFromStream_stream, _BlobFromStream_type, _HttpClient_instances, _HttpClient_defaultArgs, _HttpClient_dispatcher, _HttpClient_requestInternal;
3
+ var _BlobFromStream_stream, _BlobFromStream_type, _HttpClient_instances, _HttpClient_defaultArgs, _HttpClient_dispatcher, _HttpClient_requestInternal, _HttpClient_updateSocketInfo;
4
4
  Object.defineProperty(exports, "__esModule", { value: true });
5
5
  exports.HttpClient = exports.HEADER_USER_AGENT = void 0;
6
6
  const tslib_1 = require("tslib");
@@ -21,6 +21,9 @@ const mime_types_1 = tslib_1.__importDefault(require("mime-types"));
21
21
  const pump_1 = tslib_1.__importDefault(require("pump"));
22
22
  const HttpAgent_1 = require("./HttpAgent");
23
23
  const utils_1 = require("./utils");
24
+ const symbols_1 = tslib_1.__importDefault(require("./symbols"));
25
+ const diagnosticsChannel_1 = require("./diagnosticsChannel");
26
+ const PROTO_RE = /^https?:\/\//i;
24
27
  const FormData = undici_1.FormData !== null && undici_1.FormData !== void 0 ? undici_1.FormData : formdata_node_1.FormData;
25
28
  // impl isReadable on Node.js 14
26
29
  const isReadable = (_a = stream_2.default.isReadable) !== null && _a !== void 0 ? _a : function isReadable(stream) {
@@ -39,9 +42,7 @@ const pipelinePromise = (_c = (_b = stream_2.default.promises) === null || _b ==
39
42
  function noop() {
40
43
  // noop
41
44
  }
42
- const MAX_REQURE_ID_VALUE = Math.pow(2, 31) - 10;
43
- let globalRequestId = 0;
44
- const debug = (0, util_1.debuglog)('urllib');
45
+ const debug = (0, util_1.debuglog)('urllib:HttpClient');
45
46
  // https://github.com/octet-stream/form-data
46
47
  class BlobFromStream {
47
48
  constructor(stream, type) {
@@ -79,9 +80,6 @@ function getFileName(stream) {
79
80
  function defaultIsRetry(response) {
80
81
  return response.status >= 500;
81
82
  }
82
- function performanceTime(startTime) {
83
- return Math.floor((perf_hooks_1.performance.now() - startTime) * 1000) / 1000;
84
- }
85
83
  class HttpClient extends events_1.EventEmitter {
86
84
  constructor(clientOptions) {
87
85
  super();
@@ -96,6 +94,7 @@ class HttpClient extends events_1.EventEmitter {
96
94
  connect: clientOptions.connect,
97
95
  }), "f");
98
96
  }
97
+ (0, diagnosticsChannel_1.initDiagnosticsChannel)();
99
98
  }
100
99
  async request(url, options) {
101
100
  return await tslib_1.__classPrivateFieldGet(this, _HttpClient_instances, "m", _HttpClient_requestInternal).call(this, url, options);
@@ -104,11 +103,18 @@ class HttpClient extends events_1.EventEmitter {
104
103
  exports.HttpClient = HttpClient;
105
104
  _HttpClient_defaultArgs = new WeakMap(), _HttpClient_dispatcher = new WeakMap(), _HttpClient_instances = new WeakSet(), _HttpClient_requestInternal = async function _HttpClient_requestInternal(url, options, requestContext) {
106
105
  var _a, _b, _c, _d, _e, _f;
107
- if (globalRequestId >= MAX_REQURE_ID_VALUE) {
108
- globalRequestId = 0;
106
+ const requestId = (0, utils_1.globalId)('HttpClientRequest');
107
+ let requestUrl;
108
+ if (typeof url === 'string') {
109
+ if (!PROTO_RE.test(url)) {
110
+ // Support `request('www.server.com')`
111
+ url = 'http://' + url;
112
+ }
113
+ requestUrl = new URL(url);
114
+ }
115
+ else {
116
+ requestUrl = url;
109
117
  }
110
- const requestId = ++globalRequestId;
111
- const requestUrl = typeof url === 'string' ? new URL(url) : url;
112
118
  const args = {
113
119
  retry: 0,
114
120
  ...tslib_1.__classPrivateFieldGet(this, _HttpClient_defaultArgs, "f"),
@@ -119,6 +125,32 @@ _HttpClient_defaultArgs = new WeakMap(), _HttpClient_dispatcher = new WeakMap(),
119
125
  ...requestContext,
120
126
  };
121
127
  const requestStartTime = perf_hooks_1.performance.now();
128
+ // https://developer.chrome.com/docs/devtools/network/reference/?utm_source=devtools#timing-explanation
129
+ const timing = {
130
+ // socket assigned
131
+ queuing: 0,
132
+ // dns lookup time
133
+ // dnslookup: 0,
134
+ // socket connected
135
+ connected: 0,
136
+ // request headers sent
137
+ requestHeadersSent: 0,
138
+ // request sent, including headers and body
139
+ requestSent: 0,
140
+ // Time to first byte (TTFB), the response headers have been received
141
+ waiting: 0,
142
+ // the response body and trailers have been received
143
+ contentDownload: 0,
144
+ };
145
+ const orginalOpaque = args.opaque;
146
+ // using opaque to diagnostics channel, binding request and socket
147
+ const internalOpaque = {
148
+ [symbols_1.default.kRequestId]: requestId,
149
+ [symbols_1.default.kRequestStartTime]: requestStartTime,
150
+ [symbols_1.default.kEnableRequestTiming]: !!args.timing,
151
+ [symbols_1.default.kRequestTiming]: timing,
152
+ [symbols_1.default.kRequestOrginalOpaque]: orginalOpaque,
153
+ };
122
154
  const reqMeta = {
123
155
  requestId,
124
156
  url: requestUrl.href,
@@ -126,6 +158,18 @@ _HttpClient_defaultArgs = new WeakMap(), _HttpClient_dispatcher = new WeakMap(),
126
158
  ctx: args.ctx,
127
159
  retries: requestContext.retries,
128
160
  };
161
+ const socketInfo = {
162
+ id: 0,
163
+ localAddress: '',
164
+ localPort: 0,
165
+ remoteAddress: '',
166
+ remotePort: 0,
167
+ remoteFamily: '',
168
+ bytesWritten: 0,
169
+ bytesRead: 0,
170
+ handledRequests: 0,
171
+ handledResponses: 0,
172
+ };
129
173
  // keep urllib createCallbackResponse style
130
174
  const resHeaders = {};
131
175
  const res = {
@@ -137,10 +181,8 @@ _HttpClient_defaultArgs = new WeakMap(), _HttpClient_dispatcher = new WeakMap(),
137
181
  rt: 0,
138
182
  keepAliveSocket: true,
139
183
  requestUrls: [],
140
- timing: {
141
- waiting: 0,
142
- contentDownload: 0,
143
- },
184
+ timing,
185
+ socket: socketInfo,
144
186
  };
145
187
  let headersTimeout = 5000;
146
188
  let bodyTimeout = 5000;
@@ -190,7 +232,6 @@ _HttpClient_defaultArgs = new WeakMap(), _HttpClient_dispatcher = new WeakMap(),
190
232
  if (args.auth && !headers.authorization) {
191
233
  headers.authorization = `Basic ${Buffer.from(args.auth).toString('base64')}`;
192
234
  }
193
- let opaque = args.opaque;
194
235
  try {
195
236
  const requestOptions = {
196
237
  method,
@@ -198,7 +239,7 @@ _HttpClient_defaultArgs = new WeakMap(), _HttpClient_dispatcher = new WeakMap(),
198
239
  maxRedirections: (_d = args.maxRedirects) !== null && _d !== void 0 ? _d : 10,
199
240
  headersTimeout,
200
241
  bodyTimeout,
201
- opaque,
242
+ opaque: internalOpaque,
202
243
  dispatcher: tslib_1.__classPrivateFieldGet(this, _HttpClient_dispatcher, "f"),
203
244
  };
204
245
  if (args.followRedirect === false) {
@@ -332,10 +373,6 @@ _HttpClient_defaultArgs = new WeakMap(), _HttpClient_dispatcher = new WeakMap(),
332
373
  response = await (0, undici_1.request)(requestUrl, requestOptions);
333
374
  }
334
375
  }
335
- opaque = response.opaque;
336
- if (args.timing) {
337
- res.timing.waiting = performanceTime(requestStartTime);
338
- }
339
376
  const context = response.context;
340
377
  let lastUrl = '';
341
378
  if (context === null || context === void 0 ? void 0 : context.history) {
@@ -364,6 +401,8 @@ _HttpClient_defaultArgs = new WeakMap(), _HttpClient_dispatcher = new WeakMap(),
364
401
  status: res.status,
365
402
  statusCode: res.statusCode,
366
403
  headers: res.headers,
404
+ timing,
405
+ socket: socketInfo,
367
406
  };
368
407
  if (isCompressedContent) {
369
408
  // gzip or br
@@ -411,12 +450,11 @@ _HttpClient_defaultArgs = new WeakMap(), _HttpClient_dispatcher = new WeakMap(),
411
450
  }
412
451
  }
413
452
  }
414
- res.rt = performanceTime(requestStartTime);
415
- if (args.timing) {
416
- res.timing.contentDownload = res.rt;
417
- }
453
+ res.rt = (0, utils_1.performanceTime)(requestStartTime);
454
+ // get real socket info from internalOpaque
455
+ tslib_1.__classPrivateFieldGet(this, _HttpClient_instances, "m", _HttpClient_updateSocketInfo).call(this, socketInfo, internalOpaque);
418
456
  const clientResponse = {
419
- opaque,
457
+ opaque: orginalOpaque,
420
458
  data,
421
459
  status: res.status,
422
460
  statusCode: res.status,
@@ -459,7 +497,7 @@ _HttpClient_defaultArgs = new WeakMap(), _HttpClient_dispatcher = new WeakMap(),
459
497
  else if (err.name === 'BodyTimeoutError') {
460
498
  err = new HttpClientRequestTimeoutError(bodyTimeout, { cause: e });
461
499
  }
462
- err.opaque = opaque;
500
+ err.opaque = orginalOpaque;
463
501
  err.status = res.status;
464
502
  err.headers = res.headers;
465
503
  err.res = res;
@@ -467,10 +505,8 @@ _HttpClient_defaultArgs = new WeakMap(), _HttpClient_dispatcher = new WeakMap(),
467
505
  if (res.requestUrls.length === 0) {
468
506
  res.requestUrls.push(requestUrl.href);
469
507
  }
470
- res.rt = performanceTime(requestStartTime);
471
- if (args.timing) {
472
- res.timing.contentDownload = res.rt;
473
- }
508
+ res.rt = (0, utils_1.performanceTime)(requestStartTime);
509
+ tslib_1.__classPrivateFieldGet(this, _HttpClient_instances, "m", _HttpClient_updateSocketInfo).call(this, socketInfo, internalOpaque);
474
510
  if (this.listenerCount('response') > 0) {
475
511
  this.emit('response', {
476
512
  requestId,
@@ -482,5 +518,19 @@ _HttpClient_defaultArgs = new WeakMap(), _HttpClient_dispatcher = new WeakMap(),
482
518
  }
483
519
  throw err;
484
520
  }
521
+ }, _HttpClient_updateSocketInfo = function _HttpClient_updateSocketInfo(socketInfo, internalOpaque) {
522
+ const socket = internalOpaque[symbols_1.default.kRequestSocket];
523
+ if (socket) {
524
+ socketInfo.id = socket[symbols_1.default.kSocketId];
525
+ socketInfo.handledRequests = socket[symbols_1.default.kHandledRequests];
526
+ socketInfo.handledResponses = socket[symbols_1.default.kHandledResponses];
527
+ socketInfo.localAddress = socket.localAddress;
528
+ socketInfo.localPort = socket.localPort;
529
+ socketInfo.remoteAddress = socket.remoteAddress;
530
+ socketInfo.remotePort = socket.remotePort;
531
+ socketInfo.remoteFamily = socket.remoteFamily;
532
+ socketInfo.bytesRead = socket.bytesRead;
533
+ socketInfo.bytesWritten = socket.bytesWritten;
534
+ }
485
535
  };
486
536
  //# sourceMappingURL=HttpClient.js.map