got 14.6.2 → 14.6.3
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/dist/source/core/diagnostics-channel.d.ts +1 -1
- package/dist/source/core/errors.d.ts +1 -1
- package/dist/source/core/index.d.ts +1 -1
- package/dist/source/core/index.js +1 -22
- package/dist/source/core/options.d.ts +2 -2
- package/dist/source/core/response.d.ts +1 -1
- package/dist/source/core/utils/defer-to-connect.d.ts +9 -0
- package/dist/source/core/utils/defer-to-connect.js +44 -0
- package/dist/source/core/utils/timer.d.ts +31 -0
- package/dist/source/core/utils/timer.js +162 -0
- package/package.json +1 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Timings } from '
|
|
1
|
+
import type { Timings } from './utils/timer.js';
|
|
2
2
|
import type Options from './options.js';
|
|
3
3
|
import type { TimeoutError as TimedOutTimeoutError } from './timed-out.js';
|
|
4
4
|
import type { PlainResponse, Response } from './response.js';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Duplex } from 'node:stream';
|
|
2
2
|
import { type ClientRequest } from 'node:http';
|
|
3
3
|
import type { Socket } from 'node:net';
|
|
4
|
-
import { type Timings } from '
|
|
4
|
+
import { type Timings } from './utils/timer.js';
|
|
5
5
|
import Options, { type OptionsInit } from './options.js';
|
|
6
6
|
import { type PlainResponse, type Response } from './response.js';
|
|
7
7
|
import { RequestError } from './errors.js';
|
|
@@ -3,11 +3,11 @@ import { Buffer } from 'node:buffer';
|
|
|
3
3
|
import { Duplex } from 'node:stream';
|
|
4
4
|
import http, { ServerResponse } from 'node:http';
|
|
5
5
|
import { byteLength } from 'byte-counter';
|
|
6
|
-
import timer from '@szmarczak/http-timer';
|
|
7
6
|
import CacheableRequest, { CacheError as CacheableCacheError, } from 'cacheable-request';
|
|
8
7
|
import decompressResponse from 'decompress-response';
|
|
9
8
|
import is, { isBuffer } from '@sindresorhus/is';
|
|
10
9
|
import { FormDataEncoder, isFormData as isFormDataLike } from 'form-data-encoder';
|
|
10
|
+
import timer from './utils/timer.js';
|
|
11
11
|
import getBodySize from './utils/get-body-size.js';
|
|
12
12
|
import isFormData from './utils/is-form-data.js';
|
|
13
13
|
import proxyEvents from './utils/proxy-events.js';
|
|
@@ -596,27 +596,6 @@ export default class Request extends Duplex {
|
|
|
596
596
|
headers: response.headers,
|
|
597
597
|
isFromCache: typedResponse.isFromCache,
|
|
598
598
|
});
|
|
599
|
-
// Workaround for http-timer bug: when connecting to an IP address (no DNS lookup),
|
|
600
|
-
// http-timer sets lookup = connect instead of lookup = socket, resulting in
|
|
601
|
-
// dns = lookup - socket being a small positive number instead of 0.
|
|
602
|
-
// See https://github.com/sindresorhus/got/issues/2279
|
|
603
|
-
const { timings } = response;
|
|
604
|
-
if (timings?.lookup !== undefined && timings.socket !== undefined && timings.connect !== undefined && timings.lookup === timings.connect && timings.phases.dns !== 0) {
|
|
605
|
-
// Fix the DNS phase to be 0 and set lookup to socket time
|
|
606
|
-
timings.phases.dns = 0;
|
|
607
|
-
timings.lookup = timings.socket;
|
|
608
|
-
// Recalculate TCP time to be the full time from socket to connect
|
|
609
|
-
timings.phases.tcp = timings.connect - timings.socket;
|
|
610
|
-
}
|
|
611
|
-
// Workaround for http-timer limitation with HTTP/2:
|
|
612
|
-
// When using HTTP/2, the socket is a proxy that http-timer discards,
|
|
613
|
-
// so lookup, connect, and secureConnect events are never captured.
|
|
614
|
-
// This results in phases.request being NaN (undefined - undefined).
|
|
615
|
-
// Set it to undefined to be consistent with other unavailable timings.
|
|
616
|
-
// See https://github.com/sindresorhus/got/issues/1958
|
|
617
|
-
if (timings && Number.isNaN(timings.phases.request)) {
|
|
618
|
-
timings.phases.request = undefined;
|
|
619
|
-
}
|
|
620
599
|
response.once('error', (error) => {
|
|
621
600
|
this._aborted = true;
|
|
622
601
|
// Force clean-up, because some packages don't do this.
|
|
@@ -10,8 +10,8 @@ import { type FormDataLike } from 'form-data-encoder';
|
|
|
10
10
|
import type { KeyvStoreAdapter } from 'keyv';
|
|
11
11
|
import type KeyvType from 'keyv';
|
|
12
12
|
import type ResponseLike from 'responselike';
|
|
13
|
-
import type { IncomingMessageWithTimings } from '@szmarczak/http-timer';
|
|
14
13
|
import type { CancelableRequest } from '../as-promise/types.js';
|
|
14
|
+
import type { IncomingMessageWithTimings } from './utils/timer.js';
|
|
15
15
|
import type { PlainResponse, Response } from './response.js';
|
|
16
16
|
import type { RequestError } from './errors.js';
|
|
17
17
|
import type { Delays } from './timed-out.js';
|
|
@@ -1435,8 +1435,8 @@ export default class Options {
|
|
|
1435
1435
|
get strictContentLength(): boolean;
|
|
1436
1436
|
set strictContentLength(value: boolean);
|
|
1437
1437
|
toJSON(): {
|
|
1438
|
-
headers: Headers;
|
|
1439
1438
|
timeout: Delays;
|
|
1439
|
+
headers: Headers;
|
|
1440
1440
|
request: RequestFunction | undefined;
|
|
1441
1441
|
username: string;
|
|
1442
1442
|
password: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Buffer } from 'node:buffer';
|
|
2
|
-
import type { IncomingMessageWithTimings, Timings } from '
|
|
2
|
+
import type { IncomingMessageWithTimings, Timings } from './utils/timer.js';
|
|
3
3
|
import { RequestError } from './errors.js';
|
|
4
4
|
import type { ParseJsonFunction, ResponseType } from './options.js';
|
|
5
5
|
import type Request from './index.js';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Socket } from 'node:net';
|
|
2
|
+
import type { TLSSocket } from 'node:tls';
|
|
3
|
+
type Listeners = {
|
|
4
|
+
connect?: () => void;
|
|
5
|
+
secureConnect?: () => void;
|
|
6
|
+
close?: (hadError: boolean) => void;
|
|
7
|
+
};
|
|
8
|
+
declare const deferToConnect: (socket: TLSSocket | Socket, fn: Listeners | (() => void)) => void;
|
|
9
|
+
export default deferToConnect;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
function isTlsSocket(socket) {
|
|
2
|
+
return 'encrypted' in socket;
|
|
3
|
+
}
|
|
4
|
+
const deferToConnect = (socket, fn) => {
|
|
5
|
+
let listeners;
|
|
6
|
+
if (typeof fn === 'function') {
|
|
7
|
+
const connect = fn;
|
|
8
|
+
listeners = { connect };
|
|
9
|
+
}
|
|
10
|
+
else {
|
|
11
|
+
listeners = fn;
|
|
12
|
+
}
|
|
13
|
+
const hasConnectListener = typeof listeners.connect === 'function';
|
|
14
|
+
const hasSecureConnectListener = typeof listeners.secureConnect === 'function';
|
|
15
|
+
const hasCloseListener = typeof listeners.close === 'function';
|
|
16
|
+
const onConnect = () => {
|
|
17
|
+
if (hasConnectListener) {
|
|
18
|
+
listeners.connect();
|
|
19
|
+
}
|
|
20
|
+
if (isTlsSocket(socket) && hasSecureConnectListener) {
|
|
21
|
+
if (socket.authorized) {
|
|
22
|
+
listeners.secureConnect();
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
// Wait for secureConnect event (even if authorization fails, we need the timing)
|
|
26
|
+
socket.once('secureConnect', listeners.secureConnect);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (hasCloseListener) {
|
|
30
|
+
socket.once('close', listeners.close);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
if (socket.writable && !socket.connecting) {
|
|
34
|
+
onConnect();
|
|
35
|
+
}
|
|
36
|
+
else if (socket.connecting) {
|
|
37
|
+
socket.once('connect', onConnect);
|
|
38
|
+
}
|
|
39
|
+
else if (socket.destroyed && hasCloseListener) {
|
|
40
|
+
const hadError = '_hadError' in socket ? Boolean(socket._hadError) : false;
|
|
41
|
+
listeners.close(hadError);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
export default deferToConnect;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ClientRequest, IncomingMessage } from 'node:http';
|
|
2
|
+
export type Timings = {
|
|
3
|
+
start: number;
|
|
4
|
+
socket?: number;
|
|
5
|
+
lookup?: number;
|
|
6
|
+
connect?: number;
|
|
7
|
+
secureConnect?: number;
|
|
8
|
+
upload?: number;
|
|
9
|
+
response?: number;
|
|
10
|
+
end?: number;
|
|
11
|
+
error?: number;
|
|
12
|
+
abort?: number;
|
|
13
|
+
phases: {
|
|
14
|
+
wait?: number;
|
|
15
|
+
dns?: number;
|
|
16
|
+
tcp?: number;
|
|
17
|
+
tls?: number;
|
|
18
|
+
request?: number;
|
|
19
|
+
firstByte?: number;
|
|
20
|
+
download?: number;
|
|
21
|
+
total?: number;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
export type ClientRequestWithTimings = ClientRequest & {
|
|
25
|
+
timings?: Timings;
|
|
26
|
+
};
|
|
27
|
+
export type IncomingMessageWithTimings = IncomingMessage & {
|
|
28
|
+
timings?: Timings;
|
|
29
|
+
};
|
|
30
|
+
declare const timer: (request: ClientRequestWithTimings) => Timings;
|
|
31
|
+
export default timer;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { errorMonitor } from 'node:events';
|
|
2
|
+
import { types } from 'node:util';
|
|
3
|
+
import deferToConnect from './defer-to-connect.js';
|
|
4
|
+
const timer = (request) => {
|
|
5
|
+
if (request.timings) {
|
|
6
|
+
return request.timings;
|
|
7
|
+
}
|
|
8
|
+
const timings = {
|
|
9
|
+
start: Date.now(),
|
|
10
|
+
socket: undefined,
|
|
11
|
+
lookup: undefined,
|
|
12
|
+
connect: undefined,
|
|
13
|
+
secureConnect: undefined,
|
|
14
|
+
upload: undefined,
|
|
15
|
+
response: undefined,
|
|
16
|
+
end: undefined,
|
|
17
|
+
error: undefined,
|
|
18
|
+
abort: undefined,
|
|
19
|
+
phases: {
|
|
20
|
+
wait: undefined,
|
|
21
|
+
dns: undefined,
|
|
22
|
+
tcp: undefined,
|
|
23
|
+
tls: undefined,
|
|
24
|
+
request: undefined,
|
|
25
|
+
firstByte: undefined,
|
|
26
|
+
download: undefined,
|
|
27
|
+
total: undefined,
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
request.timings = timings;
|
|
31
|
+
const handleError = (origin) => {
|
|
32
|
+
origin.once(errorMonitor, () => {
|
|
33
|
+
timings.error = Date.now();
|
|
34
|
+
timings.phases.total = timings.error - timings.start;
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
handleError(request);
|
|
38
|
+
const onAbort = () => {
|
|
39
|
+
timings.abort = Date.now();
|
|
40
|
+
timings.phases.total = timings.abort - timings.start;
|
|
41
|
+
};
|
|
42
|
+
request.prependOnceListener('abort', onAbort);
|
|
43
|
+
const onSocket = (socket) => {
|
|
44
|
+
timings.socket = Date.now();
|
|
45
|
+
timings.phases.wait = timings.socket - timings.start;
|
|
46
|
+
if (types.isProxy(socket)) {
|
|
47
|
+
// HTTP/2: The socket is a proxy, so connection events won't fire.
|
|
48
|
+
// We can't measure connection timings, so leave them undefined.
|
|
49
|
+
// This prevents NaN in phases.request calculation.
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
// Check if socket is already connected (reused from connection pool)
|
|
53
|
+
const socketAlreadyConnected = socket.writable && !socket.connecting;
|
|
54
|
+
if (socketAlreadyConnected) {
|
|
55
|
+
// Socket reuse detected: the socket was already connected from a previous request.
|
|
56
|
+
// For reused sockets, set all connection timestamps to socket time since no new
|
|
57
|
+
// connection was made for THIS request. But preserve phase durations from the
|
|
58
|
+
// original connection so they're not lost.
|
|
59
|
+
timings.lookup = timings.socket;
|
|
60
|
+
timings.connect = timings.socket;
|
|
61
|
+
if (socket.__initial_connection_timings__) {
|
|
62
|
+
// Restore the phase timings from the initial connection
|
|
63
|
+
timings.phases.dns = socket.__initial_connection_timings__.dnsPhase;
|
|
64
|
+
timings.phases.tcp = socket.__initial_connection_timings__.tcpPhase;
|
|
65
|
+
timings.phases.tls = socket.__initial_connection_timings__.tlsPhase;
|
|
66
|
+
// Set secureConnect timestamp if there was TLS
|
|
67
|
+
if (timings.phases.tls !== undefined) {
|
|
68
|
+
timings.secureConnect = timings.socket;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
// Socket reused but no initial timings stored (e.g., from external code)
|
|
73
|
+
// Set phases to 0
|
|
74
|
+
timings.phases.dns = 0;
|
|
75
|
+
timings.phases.tcp = 0;
|
|
76
|
+
}
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const lookupListener = () => {
|
|
80
|
+
timings.lookup = Date.now();
|
|
81
|
+
timings.phases.dns = timings.lookup - timings.socket;
|
|
82
|
+
};
|
|
83
|
+
socket.prependOnceListener('lookup', lookupListener);
|
|
84
|
+
deferToConnect(socket, {
|
|
85
|
+
connect() {
|
|
86
|
+
timings.connect = Date.now();
|
|
87
|
+
if (timings.lookup === undefined) {
|
|
88
|
+
// No DNS lookup occurred (e.g., connecting to an IP address directly)
|
|
89
|
+
// Set lookup to socket time (no time elapsed for DNS)
|
|
90
|
+
socket.removeListener('lookup', lookupListener);
|
|
91
|
+
timings.lookup = timings.socket;
|
|
92
|
+
timings.phases.dns = 0;
|
|
93
|
+
}
|
|
94
|
+
timings.phases.tcp = timings.connect - timings.lookup;
|
|
95
|
+
// If lookup and connect happen at the EXACT same time (tcp = 0),
|
|
96
|
+
// DNS was served from cache and the dns value is just event loop overhead.
|
|
97
|
+
// Set dns to 0 to indicate no actual DNS resolution occurred.
|
|
98
|
+
// Fixes https://github.com/szmarczak/http-timer/issues/35
|
|
99
|
+
if (timings.phases.tcp === 0 && timings.phases.dns && timings.phases.dns > 0) {
|
|
100
|
+
timings.phases.dns = 0;
|
|
101
|
+
}
|
|
102
|
+
// Store connection phase timings on socket for potential reuse
|
|
103
|
+
if (!socket.__initial_connection_timings__) {
|
|
104
|
+
socket.__initial_connection_timings__ = {
|
|
105
|
+
dnsPhase: timings.phases.dns,
|
|
106
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion -- TypeScript can't prove this is defined due to callback structure
|
|
107
|
+
tcpPhase: timings.phases.tcp,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
secureConnect() {
|
|
112
|
+
timings.secureConnect = Date.now();
|
|
113
|
+
timings.phases.tls = timings.secureConnect - timings.connect;
|
|
114
|
+
// Update stored timings with TLS phase timing
|
|
115
|
+
if (socket.__initial_connection_timings__) {
|
|
116
|
+
socket.__initial_connection_timings__.tlsPhase = timings.phases.tls;
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
};
|
|
121
|
+
if (request.socket) {
|
|
122
|
+
onSocket(request.socket);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
request.prependOnceListener('socket', onSocket);
|
|
126
|
+
}
|
|
127
|
+
const onUpload = () => {
|
|
128
|
+
timings.upload = Date.now();
|
|
129
|
+
// Calculate request phase if we have connection timings
|
|
130
|
+
const secureOrConnect = timings.secureConnect ?? timings.connect;
|
|
131
|
+
if (secureOrConnect !== undefined) {
|
|
132
|
+
timings.phases.request = timings.upload - secureOrConnect;
|
|
133
|
+
}
|
|
134
|
+
// If both are undefined (HTTP/2), phases.request stays undefined (not NaN)
|
|
135
|
+
};
|
|
136
|
+
if (request.writableFinished) {
|
|
137
|
+
onUpload();
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
request.prependOnceListener('finish', onUpload);
|
|
141
|
+
}
|
|
142
|
+
request.prependOnceListener('response', (response) => {
|
|
143
|
+
timings.response = Date.now();
|
|
144
|
+
timings.phases.firstByte = timings.response - timings.upload;
|
|
145
|
+
response.timings = timings;
|
|
146
|
+
handleError(response);
|
|
147
|
+
response.prependOnceListener('end', () => {
|
|
148
|
+
request.off('abort', onAbort);
|
|
149
|
+
response.off('aborted', onAbort);
|
|
150
|
+
if (timings.phases.total !== undefined) {
|
|
151
|
+
// Aborted or errored
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
timings.end = Date.now();
|
|
155
|
+
timings.phases.download = timings.end - timings.response;
|
|
156
|
+
timings.phases.total = timings.end - timings.start;
|
|
157
|
+
});
|
|
158
|
+
response.prependOnceListener('aborted', onAbort);
|
|
159
|
+
});
|
|
160
|
+
return timings;
|
|
161
|
+
};
|
|
162
|
+
export default timer;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "got",
|
|
3
|
-
"version": "14.6.
|
|
3
|
+
"version": "14.6.3",
|
|
4
4
|
"description": "Human-friendly and powerful HTTP request library for Node.js",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": "sindresorhus/got",
|
|
@@ -52,7 +52,6 @@
|
|
|
52
52
|
],
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"@sindresorhus/is": "^7.0.1",
|
|
55
|
-
"@szmarczak/http-timer": "^5.0.1",
|
|
56
55
|
"byte-counter": "^0.1.0",
|
|
57
56
|
"cacheable-lookup": "^7.0.0",
|
|
58
57
|
"cacheable-request": "^13.0.12",
|