urllib 3.20.0 → 3.21.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/dist/commonjs/HttpAgent.js +1 -1
- package/dist/commonjs/HttpClient.js +37 -24
- package/dist/commonjs/HttpClientError.d.ts +19 -0
- package/dist/commonjs/HttpClientError.js +29 -0
- package/dist/commonjs/Response.d.ts +5 -0
- package/dist/commonjs/diagnosticsChannel.js +45 -2
- package/dist/commonjs/index.d.ts +1 -0
- package/dist/commonjs/index.js +15 -0
- package/dist/commonjs/symbols.d.ts +5 -0
- package/dist/commonjs/symbols.js +5 -0
- package/dist/esm/HttpAgent.js +1 -1
- package/dist/esm/HttpClient.js +37 -24
- package/dist/esm/HttpClientError.d.ts +19 -0
- package/dist/esm/HttpClientError.js +23 -0
- package/dist/esm/Response.d.ts +5 -0
- package/dist/esm/diagnosticsChannel.js +45 -2
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/symbols.d.ts +5 -0
- package/dist/esm/symbols.js +5 -0
- package/package.json +3 -2
- package/src/HttpAgent.ts +1 -1
- package/src/HttpClient.ts +35 -24
- package/src/HttpClientError.ts +34 -0
- package/src/Response.ts +5 -0
- package/src/diagnosticsChannel.ts +48 -2
- package/src/index.ts +1 -0
- package/src/symbols.ts +5 -0
@@ -68,7 +68,7 @@ class HttpAgent extends undici_1.Agent {
|
|
68
68
|
}
|
69
69
|
const family = (0, node_net_1.isIP)(hostname);
|
70
70
|
if (family === 4 || family === 6) {
|
71
|
-
// if request hostname is ip, custom lookup won't
|
71
|
+
// if request hostname is ip, custom lookup won't execute
|
72
72
|
if (!this.#checkAddress(hostname, family)) {
|
73
73
|
throw new IllegalAddressError(hostname, hostname, family);
|
74
74
|
}
|
@@ -29,6 +29,7 @@ const HttpAgent_js_1 = require("./HttpAgent.js");
|
|
29
29
|
const utils_js_1 = require("./utils.js");
|
30
30
|
const symbols_js_1 = __importDefault(require("./symbols.js"));
|
31
31
|
const diagnosticsChannel_js_1 = require("./diagnosticsChannel.js");
|
32
|
+
const HttpClientError_js_1 = require("./HttpClientError.js");
|
32
33
|
const PROTO_RE = /^https?:\/\//i;
|
33
34
|
const FormData = undici_1.FormData ?? formdata_node_1.FormData;
|
34
35
|
// impl promise pipeline on Node.js 14
|
@@ -65,15 +66,7 @@ class BlobFromStream {
|
|
65
66
|
return 'Blob';
|
66
67
|
}
|
67
68
|
}
|
68
|
-
|
69
|
-
constructor(timeout, options) {
|
70
|
-
const message = `Request timeout for ${timeout} ms`;
|
71
|
-
super(message, options);
|
72
|
-
this.name = this.constructor.name;
|
73
|
-
Error.captureStackTrace(this, this.constructor);
|
74
|
-
}
|
75
|
-
}
|
76
|
-
exports.HEADER_USER_AGENT = (0, default_user_agent_1.default)('node-urllib', '3.20.0');
|
69
|
+
exports.HEADER_USER_AGENT = (0, default_user_agent_1.default)('node-urllib', '3.21.0');
|
77
70
|
function getFileName(stream) {
|
78
71
|
const filePath = stream.path;
|
79
72
|
if (filePath) {
|
@@ -580,14 +573,17 @@ class HttpClient extends node_events_1.EventEmitter {
|
|
580
573
|
}
|
581
574
|
return clientResponse;
|
582
575
|
}
|
583
|
-
catch (
|
584
|
-
debug('Request#%d throw error: %s', requestId,
|
585
|
-
let err =
|
576
|
+
catch (rawError) {
|
577
|
+
debug('Request#%d throw error: %s', requestId, rawError);
|
578
|
+
let err = rawError;
|
586
579
|
if (err.name === 'HeadersTimeoutError') {
|
587
|
-
err = new HttpClientRequestTimeoutError(headersTimeout, { cause:
|
580
|
+
err = new HttpClientError_js_1.HttpClientRequestTimeoutError(headersTimeout, { cause: err });
|
588
581
|
}
|
589
582
|
else if (err.name === 'BodyTimeoutError') {
|
590
|
-
err = new HttpClientRequestTimeoutError(bodyTimeout, { cause:
|
583
|
+
err = new HttpClientError_js_1.HttpClientRequestTimeoutError(bodyTimeout, { cause: err });
|
584
|
+
}
|
585
|
+
else if (err.code === 'UND_ERR_CONNECT_TIMEOUT') {
|
586
|
+
err = new HttpClientError_js_1.HttpClientConnectTimeoutError(err.message, err.code, { cause: err });
|
591
587
|
}
|
592
588
|
else if (err.code === 'UND_ERR_SOCKET' || err.code === 'ECONNRESET') {
|
593
589
|
// auto retry on socket error, https://github.com/node-modules/urllib/issues/454
|
@@ -610,7 +606,7 @@ class HttpClient extends node_events_1.EventEmitter {
|
|
610
606
|
res.requestUrls.push(requestUrl.href);
|
611
607
|
}
|
612
608
|
res.rt = (0, utils_js_1.performanceTime)(requestStartTime);
|
613
|
-
this.#updateSocketInfo(socketInfo, internalOpaque);
|
609
|
+
this.#updateSocketInfo(socketInfo, internalOpaque, rawError);
|
614
610
|
channels.response.publish({
|
615
611
|
request: reqMeta,
|
616
612
|
response: res,
|
@@ -631,21 +627,38 @@ class HttpClient extends node_events_1.EventEmitter {
|
|
631
627
|
throw err;
|
632
628
|
}
|
633
629
|
}
|
634
|
-
#updateSocketInfo(socketInfo, internalOpaque) {
|
635
|
-
const socket = internalOpaque[symbols_js_1.default.kRequestSocket];
|
630
|
+
#updateSocketInfo(socketInfo, internalOpaque, err) {
|
631
|
+
const socket = internalOpaque[symbols_js_1.default.kRequestSocket] ?? err?.[symbols_js_1.default.kErrorSocket];
|
636
632
|
if (socket) {
|
637
633
|
socketInfo.id = socket[symbols_js_1.default.kSocketId];
|
638
634
|
socketInfo.handledRequests = socket[symbols_js_1.default.kHandledRequests];
|
639
635
|
socketInfo.handledResponses = socket[symbols_js_1.default.kHandledResponses];
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
636
|
+
if (socket[symbols_js_1.default.kSocketLocalAddress]) {
|
637
|
+
socketInfo.localAddress = socket[symbols_js_1.default.kSocketLocalAddress];
|
638
|
+
socketInfo.localPort = socket[symbols_js_1.default.kSocketLocalPort];
|
639
|
+
}
|
640
|
+
if (socket.remoteAddress) {
|
641
|
+
socketInfo.remoteAddress = socket.remoteAddress;
|
642
|
+
socketInfo.remotePort = socket.remotePort;
|
643
|
+
socketInfo.remoteFamily = socket.remoteFamily;
|
644
|
+
}
|
645
645
|
socketInfo.bytesRead = socket.bytesRead;
|
646
646
|
socketInfo.bytesWritten = socket.bytesWritten;
|
647
|
-
|
648
|
-
|
647
|
+
if (socket[symbols_js_1.default.kSocketConnectErrorTime]) {
|
648
|
+
socketInfo.connectErrorTime = socket[symbols_js_1.default.kSocketConnectErrorTime];
|
649
|
+
if (Array.isArray(socket.autoSelectFamilyAttemptedAddresses)) {
|
650
|
+
socketInfo.attemptedRemoteAddresses = socket.autoSelectFamilyAttemptedAddresses;
|
651
|
+
}
|
652
|
+
socketInfo.connectProtocol = socket[symbols_js_1.default.kSocketConnectProtocol];
|
653
|
+
socketInfo.connectHost = socket[symbols_js_1.default.kSocketConnectHost];
|
654
|
+
socketInfo.connectPort = socket[symbols_js_1.default.kSocketConnectPort];
|
655
|
+
}
|
656
|
+
if (socket[symbols_js_1.default.kSocketConnectedTime]) {
|
657
|
+
socketInfo.connectedTime = socket[symbols_js_1.default.kSocketConnectedTime];
|
658
|
+
}
|
659
|
+
if (socket[symbols_js_1.default.kSocketRequestEndTime]) {
|
660
|
+
socketInfo.lastRequestEndTime = socket[symbols_js_1.default.kSocketRequestEndTime];
|
661
|
+
}
|
649
662
|
socket[symbols_js_1.default.kSocketRequestEndTime] = new Date();
|
650
663
|
}
|
651
664
|
}
|
@@ -0,0 +1,19 @@
|
|
1
|
+
import type { RawResponseWithMeta, SocketInfo } from './Response.js';
|
2
|
+
import type { IncomingHttpHeaders } from './IncomingHttpHeaders.js';
|
3
|
+
interface ErrorOptions {
|
4
|
+
cause?: Error;
|
5
|
+
}
|
6
|
+
export declare class HttpClientRequestError extends Error {
|
7
|
+
status?: number;
|
8
|
+
headers?: IncomingHttpHeaders;
|
9
|
+
socket?: SocketInfo;
|
10
|
+
res?: RawResponseWithMeta;
|
11
|
+
}
|
12
|
+
export declare class HttpClientRequestTimeoutError extends HttpClientRequestError {
|
13
|
+
constructor(timeout: number, options: ErrorOptions);
|
14
|
+
}
|
15
|
+
export declare class HttpClientConnectTimeoutError extends HttpClientRequestError {
|
16
|
+
code: string;
|
17
|
+
constructor(message: string, code: string, options: ErrorOptions);
|
18
|
+
}
|
19
|
+
export {};
|
@@ -0,0 +1,29 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.HttpClientConnectTimeoutError = exports.HttpClientRequestTimeoutError = exports.HttpClientRequestError = void 0;
|
4
|
+
class HttpClientRequestError extends Error {
|
5
|
+
status;
|
6
|
+
headers;
|
7
|
+
socket;
|
8
|
+
res;
|
9
|
+
}
|
10
|
+
exports.HttpClientRequestError = HttpClientRequestError;
|
11
|
+
class HttpClientRequestTimeoutError extends HttpClientRequestError {
|
12
|
+
constructor(timeout, options) {
|
13
|
+
const message = `Request timeout for ${timeout} ms`;
|
14
|
+
super(message, options);
|
15
|
+
this.name = this.constructor.name;
|
16
|
+
Error.captureStackTrace(this, this.constructor);
|
17
|
+
}
|
18
|
+
}
|
19
|
+
exports.HttpClientRequestTimeoutError = HttpClientRequestTimeoutError;
|
20
|
+
class HttpClientConnectTimeoutError extends HttpClientRequestError {
|
21
|
+
code;
|
22
|
+
constructor(message, code, options) {
|
23
|
+
super(message, options);
|
24
|
+
this.name = this.constructor.name;
|
25
|
+
this.code = code;
|
26
|
+
Error.captureStackTrace(this, this.constructor);
|
27
|
+
}
|
28
|
+
}
|
29
|
+
exports.HttpClientConnectTimeoutError = HttpClientConnectTimeoutError;
|
@@ -13,7 +13,12 @@ export type SocketInfo = {
|
|
13
13
|
handledRequests: number;
|
14
14
|
handledResponses: number;
|
15
15
|
connectedTime?: Date;
|
16
|
+
connectErrorTime?: Date;
|
16
17
|
lastRequestEndTime?: Date;
|
18
|
+
attemptedRemoteAddresses?: string[];
|
19
|
+
connectProtocol?: string;
|
20
|
+
connectHost?: string;
|
21
|
+
connectPort?: string;
|
17
22
|
};
|
18
23
|
/**
|
19
24
|
* https://eggjs.org/en/core/httpclient.html#timing-boolean
|
@@ -7,6 +7,7 @@ exports.initDiagnosticsChannel = void 0;
|
|
7
7
|
const node_diagnostics_channel_1 = __importDefault(require("node:diagnostics_channel"));
|
8
8
|
const node_perf_hooks_1 = require("node:perf_hooks");
|
9
9
|
const node_util_1 = require("node:util");
|
10
|
+
const node_net_1 = require("node:net");
|
10
11
|
const symbols_js_1 = __importDefault(require("./symbols.js"));
|
11
12
|
const utils_js_1 = require("./utils.js");
|
12
13
|
const debug = (0, node_util_1.debuglog)('urllib:DiagnosticsChannel');
|
@@ -38,8 +39,23 @@ function formatSocket(socket) {
|
|
38
39
|
localPort: socket[symbols_js_1.default.kSocketLocalPort],
|
39
40
|
remoteAddress: socket.remoteAddress,
|
40
41
|
remotePort: socket.remotePort,
|
42
|
+
attemptedAddresses: socket.autoSelectFamilyAttemptedAddresses,
|
43
|
+
connecting: socket.connecting,
|
41
44
|
};
|
42
45
|
}
|
46
|
+
// make sure error contains socket info
|
47
|
+
const kDestroy = Symbol('kDestroy');
|
48
|
+
node_net_1.Socket.prototype[kDestroy] = node_net_1.Socket.prototype.destroy;
|
49
|
+
node_net_1.Socket.prototype.destroy = function (err) {
|
50
|
+
if (err) {
|
51
|
+
Object.defineProperty(err, symbols_js_1.default.kErrorSocket, {
|
52
|
+
// don't show on console log
|
53
|
+
enumerable: false,
|
54
|
+
value: this,
|
55
|
+
});
|
56
|
+
}
|
57
|
+
return this[kDestroy](err);
|
58
|
+
};
|
43
59
|
function initDiagnosticsChannel() {
|
44
60
|
// makre sure init global DiagnosticsChannel once
|
45
61
|
if (initedDiagnosticsChannel)
|
@@ -69,10 +85,34 @@ function initDiagnosticsChannel() {
|
|
69
85
|
opaque[symbols_js_1.default.kRequestTiming].queuing = (0, utils_js_1.performanceTime)(opaque[symbols_js_1.default.kRequestStartTime]);
|
70
86
|
});
|
71
87
|
// diagnosticsChannel.channel('undici:client:beforeConnect')
|
72
|
-
|
88
|
+
subscribe('undici:client:connectError', (message, name) => {
|
89
|
+
const { error, connectParams } = message;
|
90
|
+
let { socket } = message;
|
91
|
+
if (!socket && error[symbols_js_1.default.kErrorSocket]) {
|
92
|
+
socket = error[symbols_js_1.default.kErrorSocket];
|
93
|
+
}
|
94
|
+
if (socket) {
|
95
|
+
socket[symbols_js_1.default.kSocketId] = (0, utils_js_1.globalId)('UndiciSocket');
|
96
|
+
socket[symbols_js_1.default.kSocketConnectErrorTime] = new Date();
|
97
|
+
socket[symbols_js_1.default.kHandledRequests] = 0;
|
98
|
+
socket[symbols_js_1.default.kHandledResponses] = 0;
|
99
|
+
// copy local address to symbol, avoid them be reset after request error throw
|
100
|
+
if (socket.localAddress) {
|
101
|
+
socket[symbols_js_1.default.kSocketLocalAddress] = socket.localAddress;
|
102
|
+
socket[symbols_js_1.default.kSocketLocalPort] = socket.localPort;
|
103
|
+
}
|
104
|
+
socket[symbols_js_1.default.kSocketConnectProtocol] = connectParams.protocol;
|
105
|
+
socket[symbols_js_1.default.kSocketConnectHost] = connectParams.host;
|
106
|
+
socket[symbols_js_1.default.kSocketConnectPort] = connectParams.port;
|
107
|
+
debug('[%s] Socket#%d connectError, connectParams: %o, error: %s, (sock: %o)', name, socket[symbols_js_1.default.kSocketId], connectParams, error.message, formatSocket(socket));
|
108
|
+
}
|
109
|
+
else {
|
110
|
+
debug('[%s] connectError, connectParams: %o, error: %o', name, connectParams, error);
|
111
|
+
}
|
112
|
+
});
|
73
113
|
// This message is published after a connection is established.
|
74
114
|
subscribe('undici:client:connected', (message, name) => {
|
75
|
-
const { socket } = message;
|
115
|
+
const { socket, connectParams } = message;
|
76
116
|
socket[symbols_js_1.default.kSocketId] = (0, utils_js_1.globalId)('UndiciSocket');
|
77
117
|
socket[symbols_js_1.default.kSocketStartTime] = node_perf_hooks_1.performance.now();
|
78
118
|
socket[symbols_js_1.default.kSocketConnectedTime] = new Date();
|
@@ -81,6 +121,9 @@ function initDiagnosticsChannel() {
|
|
81
121
|
// copy local address to symbol, avoid them be reset after request error throw
|
82
122
|
socket[symbols_js_1.default.kSocketLocalAddress] = socket.localAddress;
|
83
123
|
socket[symbols_js_1.default.kSocketLocalPort] = socket.localPort;
|
124
|
+
socket[symbols_js_1.default.kSocketConnectProtocol] = connectParams.protocol;
|
125
|
+
socket[symbols_js_1.default.kSocketConnectHost] = connectParams.host;
|
126
|
+
socket[symbols_js_1.default.kSocketConnectPort] = connectParams.port;
|
84
127
|
debug('[%s] Socket#%d connected (sock: %o)', name, socket[symbols_js_1.default.kSocketId], formatSocket(socket));
|
85
128
|
});
|
86
129
|
// This message is published right before the first byte of the request is written to the socket.
|
package/dist/commonjs/index.d.ts
CHANGED
@@ -6,6 +6,7 @@ export { HttpClient, HttpClient as HttpClient2, HEADER_USER_AGENT as USER_AGENT,
|
|
6
6
|
export { RequestOptions, RequestOptions as RequestOptions2, RequestURL, HttpMethod, FixJSONCtlCharsHandler, FixJSONCtlChars, } from './Request.js';
|
7
7
|
export { SocketInfo, Timing, RawResponseWithMeta, HttpClientResponse, } from './Response.js';
|
8
8
|
export { IncomingHttpHeaders, } from './IncomingHttpHeaders.js';
|
9
|
+
export * from './HttpClientError.js';
|
9
10
|
declare const _default: {
|
10
11
|
request: typeof request;
|
11
12
|
curl: typeof curl;
|
package/dist/commonjs/index.js
CHANGED
@@ -1,4 +1,18 @@
|
|
1
1
|
"use strict";
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
3
|
+
if (k2 === undefined) k2 = k;
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
7
|
+
}
|
8
|
+
Object.defineProperty(o, k2, desc);
|
9
|
+
}) : (function(o, m, k, k2) {
|
10
|
+
if (k2 === undefined) k2 = k;
|
11
|
+
o[k2] = m[k];
|
12
|
+
}));
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
15
|
+
};
|
2
16
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
17
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
18
|
};
|
@@ -46,6 +60,7 @@ var HttpClient_js_2 = require("./HttpClient.js");
|
|
46
60
|
Object.defineProperty(exports, "HttpClient", { enumerable: true, get: function () { return HttpClient_js_2.HttpClient; } });
|
47
61
|
Object.defineProperty(exports, "HttpClient2", { enumerable: true, get: function () { return HttpClient_js_2.HttpClient; } });
|
48
62
|
Object.defineProperty(exports, "USER_AGENT", { enumerable: true, get: function () { return HttpClient_js_2.HEADER_USER_AGENT; } });
|
63
|
+
__exportStar(require("./HttpClientError.js"), exports);
|
49
64
|
exports.default = {
|
50
65
|
request,
|
51
66
|
curl,
|
@@ -2,9 +2,13 @@ declare const _default: {
|
|
2
2
|
kSocketId: symbol;
|
3
3
|
kSocketStartTime: symbol;
|
4
4
|
kSocketConnectedTime: symbol;
|
5
|
+
kSocketConnectErrorTime: symbol;
|
5
6
|
kSocketRequestEndTime: symbol;
|
6
7
|
kSocketLocalAddress: symbol;
|
7
8
|
kSocketLocalPort: symbol;
|
9
|
+
kSocketConnectHost: symbol;
|
10
|
+
kSocketConnectPort: symbol;
|
11
|
+
kSocketConnectProtocol: symbol;
|
8
12
|
kHandledRequests: symbol;
|
9
13
|
kHandledResponses: symbol;
|
10
14
|
kRequestSocket: symbol;
|
@@ -13,5 +17,6 @@ declare const _default: {
|
|
13
17
|
kEnableRequestTiming: symbol;
|
14
18
|
kRequestTiming: symbol;
|
15
19
|
kRequestOriginalOpaque: symbol;
|
20
|
+
kErrorSocket: symbol;
|
16
21
|
};
|
17
22
|
export default _default;
|
package/dist/commonjs/symbols.js
CHANGED
@@ -4,9 +4,13 @@ exports.default = {
|
|
4
4
|
kSocketId: Symbol('socket id'),
|
5
5
|
kSocketStartTime: Symbol('socket start time'),
|
6
6
|
kSocketConnectedTime: Symbol('socket connected time'),
|
7
|
+
kSocketConnectErrorTime: Symbol('socket connectError time'),
|
7
8
|
kSocketRequestEndTime: Symbol('socket request end time'),
|
8
9
|
kSocketLocalAddress: Symbol('socket local address'),
|
9
10
|
kSocketLocalPort: Symbol('socket local port'),
|
11
|
+
kSocketConnectHost: Symbol('socket connect params: host'),
|
12
|
+
kSocketConnectPort: Symbol('socket connect params: port'),
|
13
|
+
kSocketConnectProtocol: Symbol('socket connect params: protocol'),
|
10
14
|
kHandledRequests: Symbol('handled requests per socket'),
|
11
15
|
kHandledResponses: Symbol('handled responses per socket'),
|
12
16
|
kRequestSocket: Symbol('request on the socket'),
|
@@ -15,4 +19,5 @@ exports.default = {
|
|
15
19
|
kEnableRequestTiming: Symbol('enable request timing or not'),
|
16
20
|
kRequestTiming: Symbol('request timing'),
|
17
21
|
kRequestOriginalOpaque: Symbol('request original opaque'),
|
22
|
+
kErrorSocket: Symbol('socket of error'),
|
18
23
|
};
|
package/dist/esm/HttpAgent.js
CHANGED
@@ -62,7 +62,7 @@ export class HttpAgent extends Agent {
|
|
62
62
|
}
|
63
63
|
const family = isIP(hostname);
|
64
64
|
if (family === 4 || family === 6) {
|
65
|
-
// if request hostname is ip, custom lookup won't
|
65
|
+
// if request hostname is ip, custom lookup won't execute
|
66
66
|
if (!this.#checkAddress(hostname, family)) {
|
67
67
|
throw new IllegalAddressError(hostname, hostname, family);
|
68
68
|
}
|
package/dist/esm/HttpClient.js
CHANGED
@@ -23,6 +23,7 @@ import { HttpAgent } from './HttpAgent.js';
|
|
23
23
|
import { parseJSON, sleep, digestAuthHeader, globalId, performanceTime, isReadable } from './utils.js';
|
24
24
|
import symbols from './symbols.js';
|
25
25
|
import { initDiagnosticsChannel } from './diagnosticsChannel.js';
|
26
|
+
import { HttpClientConnectTimeoutError, HttpClientRequestTimeoutError } from './HttpClientError.js';
|
26
27
|
const PROTO_RE = /^https?:\/\//i;
|
27
28
|
const FormData = FormDataNative ?? FormDataNode;
|
28
29
|
// impl promise pipeline on Node.js 14
|
@@ -59,15 +60,7 @@ class BlobFromStream {
|
|
59
60
|
return 'Blob';
|
60
61
|
}
|
61
62
|
}
|
62
|
-
|
63
|
-
constructor(timeout, options) {
|
64
|
-
const message = `Request timeout for ${timeout} ms`;
|
65
|
-
super(message, options);
|
66
|
-
this.name = this.constructor.name;
|
67
|
-
Error.captureStackTrace(this, this.constructor);
|
68
|
-
}
|
69
|
-
}
|
70
|
-
export const HEADER_USER_AGENT = createUserAgent('node-urllib', '3.20.0');
|
63
|
+
export const HEADER_USER_AGENT = createUserAgent('node-urllib', '3.21.0');
|
71
64
|
function getFileName(stream) {
|
72
65
|
const filePath = stream.path;
|
73
66
|
if (filePath) {
|
@@ -574,14 +567,17 @@ export class HttpClient extends EventEmitter {
|
|
574
567
|
}
|
575
568
|
return clientResponse;
|
576
569
|
}
|
577
|
-
catch (
|
578
|
-
debug('Request#%d throw error: %s', requestId,
|
579
|
-
let err =
|
570
|
+
catch (rawError) {
|
571
|
+
debug('Request#%d throw error: %s', requestId, rawError);
|
572
|
+
let err = rawError;
|
580
573
|
if (err.name === 'HeadersTimeoutError') {
|
581
|
-
err = new HttpClientRequestTimeoutError(headersTimeout, { cause:
|
574
|
+
err = new HttpClientRequestTimeoutError(headersTimeout, { cause: err });
|
582
575
|
}
|
583
576
|
else if (err.name === 'BodyTimeoutError') {
|
584
|
-
err = new HttpClientRequestTimeoutError(bodyTimeout, { cause:
|
577
|
+
err = new HttpClientRequestTimeoutError(bodyTimeout, { cause: err });
|
578
|
+
}
|
579
|
+
else if (err.code === 'UND_ERR_CONNECT_TIMEOUT') {
|
580
|
+
err = new HttpClientConnectTimeoutError(err.message, err.code, { cause: err });
|
585
581
|
}
|
586
582
|
else if (err.code === 'UND_ERR_SOCKET' || err.code === 'ECONNRESET') {
|
587
583
|
// auto retry on socket error, https://github.com/node-modules/urllib/issues/454
|
@@ -604,7 +600,7 @@ export class HttpClient extends EventEmitter {
|
|
604
600
|
res.requestUrls.push(requestUrl.href);
|
605
601
|
}
|
606
602
|
res.rt = performanceTime(requestStartTime);
|
607
|
-
this.#updateSocketInfo(socketInfo, internalOpaque);
|
603
|
+
this.#updateSocketInfo(socketInfo, internalOpaque, rawError);
|
608
604
|
channels.response.publish({
|
609
605
|
request: reqMeta,
|
610
606
|
response: res,
|
@@ -625,21 +621,38 @@ export class HttpClient extends EventEmitter {
|
|
625
621
|
throw err;
|
626
622
|
}
|
627
623
|
}
|
628
|
-
#updateSocketInfo(socketInfo, internalOpaque) {
|
629
|
-
const socket = internalOpaque[symbols.kRequestSocket];
|
624
|
+
#updateSocketInfo(socketInfo, internalOpaque, err) {
|
625
|
+
const socket = internalOpaque[symbols.kRequestSocket] ?? err?.[symbols.kErrorSocket];
|
630
626
|
if (socket) {
|
631
627
|
socketInfo.id = socket[symbols.kSocketId];
|
632
628
|
socketInfo.handledRequests = socket[symbols.kHandledRequests];
|
633
629
|
socketInfo.handledResponses = socket[symbols.kHandledResponses];
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
630
|
+
if (socket[symbols.kSocketLocalAddress]) {
|
631
|
+
socketInfo.localAddress = socket[symbols.kSocketLocalAddress];
|
632
|
+
socketInfo.localPort = socket[symbols.kSocketLocalPort];
|
633
|
+
}
|
634
|
+
if (socket.remoteAddress) {
|
635
|
+
socketInfo.remoteAddress = socket.remoteAddress;
|
636
|
+
socketInfo.remotePort = socket.remotePort;
|
637
|
+
socketInfo.remoteFamily = socket.remoteFamily;
|
638
|
+
}
|
639
639
|
socketInfo.bytesRead = socket.bytesRead;
|
640
640
|
socketInfo.bytesWritten = socket.bytesWritten;
|
641
|
-
|
642
|
-
|
641
|
+
if (socket[symbols.kSocketConnectErrorTime]) {
|
642
|
+
socketInfo.connectErrorTime = socket[symbols.kSocketConnectErrorTime];
|
643
|
+
if (Array.isArray(socket.autoSelectFamilyAttemptedAddresses)) {
|
644
|
+
socketInfo.attemptedRemoteAddresses = socket.autoSelectFamilyAttemptedAddresses;
|
645
|
+
}
|
646
|
+
socketInfo.connectProtocol = socket[symbols.kSocketConnectProtocol];
|
647
|
+
socketInfo.connectHost = socket[symbols.kSocketConnectHost];
|
648
|
+
socketInfo.connectPort = socket[symbols.kSocketConnectPort];
|
649
|
+
}
|
650
|
+
if (socket[symbols.kSocketConnectedTime]) {
|
651
|
+
socketInfo.connectedTime = socket[symbols.kSocketConnectedTime];
|
652
|
+
}
|
653
|
+
if (socket[symbols.kSocketRequestEndTime]) {
|
654
|
+
socketInfo.lastRequestEndTime = socket[symbols.kSocketRequestEndTime];
|
655
|
+
}
|
643
656
|
socket[symbols.kSocketRequestEndTime] = new Date();
|
644
657
|
}
|
645
658
|
}
|
@@ -0,0 +1,19 @@
|
|
1
|
+
import type { RawResponseWithMeta, SocketInfo } from './Response.js';
|
2
|
+
import type { IncomingHttpHeaders } from './IncomingHttpHeaders.js';
|
3
|
+
interface ErrorOptions {
|
4
|
+
cause?: Error;
|
5
|
+
}
|
6
|
+
export declare class HttpClientRequestError extends Error {
|
7
|
+
status?: number;
|
8
|
+
headers?: IncomingHttpHeaders;
|
9
|
+
socket?: SocketInfo;
|
10
|
+
res?: RawResponseWithMeta;
|
11
|
+
}
|
12
|
+
export declare class HttpClientRequestTimeoutError extends HttpClientRequestError {
|
13
|
+
constructor(timeout: number, options: ErrorOptions);
|
14
|
+
}
|
15
|
+
export declare class HttpClientConnectTimeoutError extends HttpClientRequestError {
|
16
|
+
code: string;
|
17
|
+
constructor(message: string, code: string, options: ErrorOptions);
|
18
|
+
}
|
19
|
+
export {};
|
@@ -0,0 +1,23 @@
|
|
1
|
+
export class HttpClientRequestError extends Error {
|
2
|
+
status;
|
3
|
+
headers;
|
4
|
+
socket;
|
5
|
+
res;
|
6
|
+
}
|
7
|
+
export class HttpClientRequestTimeoutError extends HttpClientRequestError {
|
8
|
+
constructor(timeout, options) {
|
9
|
+
const message = `Request timeout for ${timeout} ms`;
|
10
|
+
super(message, options);
|
11
|
+
this.name = this.constructor.name;
|
12
|
+
Error.captureStackTrace(this, this.constructor);
|
13
|
+
}
|
14
|
+
}
|
15
|
+
export class HttpClientConnectTimeoutError extends HttpClientRequestError {
|
16
|
+
code;
|
17
|
+
constructor(message, code, options) {
|
18
|
+
super(message, options);
|
19
|
+
this.name = this.constructor.name;
|
20
|
+
this.code = code;
|
21
|
+
Error.captureStackTrace(this, this.constructor);
|
22
|
+
}
|
23
|
+
}
|
package/dist/esm/Response.d.ts
CHANGED
@@ -13,7 +13,12 @@ export type SocketInfo = {
|
|
13
13
|
handledRequests: number;
|
14
14
|
handledResponses: number;
|
15
15
|
connectedTime?: Date;
|
16
|
+
connectErrorTime?: Date;
|
16
17
|
lastRequestEndTime?: Date;
|
18
|
+
attemptedRemoteAddresses?: string[];
|
19
|
+
connectProtocol?: string;
|
20
|
+
connectHost?: string;
|
21
|
+
connectPort?: string;
|
17
22
|
};
|
18
23
|
/**
|
19
24
|
* https://eggjs.org/en/core/httpclient.html#timing-boolean
|
@@ -1,6 +1,7 @@
|
|
1
1
|
import diagnosticsChannel from 'node:diagnostics_channel';
|
2
2
|
import { performance } from 'node:perf_hooks';
|
3
3
|
import { debuglog } from 'node:util';
|
4
|
+
import { Socket } from 'node:net';
|
4
5
|
import symbols from './symbols.js';
|
5
6
|
import { globalId, performanceTime } from './utils.js';
|
6
7
|
const debug = debuglog('urllib:DiagnosticsChannel');
|
@@ -32,8 +33,23 @@ function formatSocket(socket) {
|
|
32
33
|
localPort: socket[symbols.kSocketLocalPort],
|
33
34
|
remoteAddress: socket.remoteAddress,
|
34
35
|
remotePort: socket.remotePort,
|
36
|
+
attemptedAddresses: socket.autoSelectFamilyAttemptedAddresses,
|
37
|
+
connecting: socket.connecting,
|
35
38
|
};
|
36
39
|
}
|
40
|
+
// make sure error contains socket info
|
41
|
+
const kDestroy = Symbol('kDestroy');
|
42
|
+
Socket.prototype[kDestroy] = Socket.prototype.destroy;
|
43
|
+
Socket.prototype.destroy = function (err) {
|
44
|
+
if (err) {
|
45
|
+
Object.defineProperty(err, symbols.kErrorSocket, {
|
46
|
+
// don't show on console log
|
47
|
+
enumerable: false,
|
48
|
+
value: this,
|
49
|
+
});
|
50
|
+
}
|
51
|
+
return this[kDestroy](err);
|
52
|
+
};
|
37
53
|
export function initDiagnosticsChannel() {
|
38
54
|
// makre sure init global DiagnosticsChannel once
|
39
55
|
if (initedDiagnosticsChannel)
|
@@ -63,10 +79,34 @@ export function initDiagnosticsChannel() {
|
|
63
79
|
opaque[symbols.kRequestTiming].queuing = performanceTime(opaque[symbols.kRequestStartTime]);
|
64
80
|
});
|
65
81
|
// diagnosticsChannel.channel('undici:client:beforeConnect')
|
66
|
-
|
82
|
+
subscribe('undici:client:connectError', (message, name) => {
|
83
|
+
const { error, connectParams } = message;
|
84
|
+
let { socket } = message;
|
85
|
+
if (!socket && error[symbols.kErrorSocket]) {
|
86
|
+
socket = error[symbols.kErrorSocket];
|
87
|
+
}
|
88
|
+
if (socket) {
|
89
|
+
socket[symbols.kSocketId] = globalId('UndiciSocket');
|
90
|
+
socket[symbols.kSocketConnectErrorTime] = new Date();
|
91
|
+
socket[symbols.kHandledRequests] = 0;
|
92
|
+
socket[symbols.kHandledResponses] = 0;
|
93
|
+
// copy local address to symbol, avoid them be reset after request error throw
|
94
|
+
if (socket.localAddress) {
|
95
|
+
socket[symbols.kSocketLocalAddress] = socket.localAddress;
|
96
|
+
socket[symbols.kSocketLocalPort] = socket.localPort;
|
97
|
+
}
|
98
|
+
socket[symbols.kSocketConnectProtocol] = connectParams.protocol;
|
99
|
+
socket[symbols.kSocketConnectHost] = connectParams.host;
|
100
|
+
socket[symbols.kSocketConnectPort] = connectParams.port;
|
101
|
+
debug('[%s] Socket#%d connectError, connectParams: %o, error: %s, (sock: %o)', name, socket[symbols.kSocketId], connectParams, error.message, formatSocket(socket));
|
102
|
+
}
|
103
|
+
else {
|
104
|
+
debug('[%s] connectError, connectParams: %o, error: %o', name, connectParams, error);
|
105
|
+
}
|
106
|
+
});
|
67
107
|
// This message is published after a connection is established.
|
68
108
|
subscribe('undici:client:connected', (message, name) => {
|
69
|
-
const { socket } = message;
|
109
|
+
const { socket, connectParams } = message;
|
70
110
|
socket[symbols.kSocketId] = globalId('UndiciSocket');
|
71
111
|
socket[symbols.kSocketStartTime] = performance.now();
|
72
112
|
socket[symbols.kSocketConnectedTime] = new Date();
|
@@ -75,6 +115,9 @@ export function initDiagnosticsChannel() {
|
|
75
115
|
// copy local address to symbol, avoid them be reset after request error throw
|
76
116
|
socket[symbols.kSocketLocalAddress] = socket.localAddress;
|
77
117
|
socket[symbols.kSocketLocalPort] = socket.localPort;
|
118
|
+
socket[symbols.kSocketConnectProtocol] = connectParams.protocol;
|
119
|
+
socket[symbols.kSocketConnectHost] = connectParams.host;
|
120
|
+
socket[symbols.kSocketConnectPort] = connectParams.port;
|
78
121
|
debug('[%s] Socket#%d connected (sock: %o)', name, socket[symbols.kSocketId], formatSocket(socket));
|
79
122
|
});
|
80
123
|
// This message is published right before the first byte of the request is written to the socket.
|
package/dist/esm/index.d.ts
CHANGED
@@ -6,6 +6,7 @@ export { HttpClient, HttpClient as HttpClient2, HEADER_USER_AGENT as USER_AGENT,
|
|
6
6
|
export { RequestOptions, RequestOptions as RequestOptions2, RequestURL, HttpMethod, FixJSONCtlCharsHandler, FixJSONCtlChars, } from './Request.js';
|
7
7
|
export { SocketInfo, Timing, RawResponseWithMeta, HttpClientResponse, } from './Response.js';
|
8
8
|
export { IncomingHttpHeaders, } from './IncomingHttpHeaders.js';
|
9
|
+
export * from './HttpClientError.js';
|
9
10
|
declare const _default: {
|
10
11
|
request: typeof request;
|
11
12
|
curl: typeof curl;
|
package/dist/esm/index.js
CHANGED
@@ -29,6 +29,7 @@ export async function curl(url, options) {
|
|
29
29
|
export { MockAgent, ProxyAgent, Agent, Dispatcher, setGlobalDispatcher, getGlobalDispatcher, } from 'undici';
|
30
30
|
// HttpClient2 is keep compatible with urlib@2 HttpClient2
|
31
31
|
export { HttpClient, HttpClient as HttpClient2, HEADER_USER_AGENT as USER_AGENT, } from './HttpClient.js';
|
32
|
+
export * from './HttpClientError.js';
|
32
33
|
export default {
|
33
34
|
request,
|
34
35
|
curl,
|
package/dist/esm/symbols.d.ts
CHANGED
@@ -2,9 +2,13 @@ declare const _default: {
|
|
2
2
|
kSocketId: symbol;
|
3
3
|
kSocketStartTime: symbol;
|
4
4
|
kSocketConnectedTime: symbol;
|
5
|
+
kSocketConnectErrorTime: symbol;
|
5
6
|
kSocketRequestEndTime: symbol;
|
6
7
|
kSocketLocalAddress: symbol;
|
7
8
|
kSocketLocalPort: symbol;
|
9
|
+
kSocketConnectHost: symbol;
|
10
|
+
kSocketConnectPort: symbol;
|
11
|
+
kSocketConnectProtocol: symbol;
|
8
12
|
kHandledRequests: symbol;
|
9
13
|
kHandledResponses: symbol;
|
10
14
|
kRequestSocket: symbol;
|
@@ -13,5 +17,6 @@ declare const _default: {
|
|
13
17
|
kEnableRequestTiming: symbol;
|
14
18
|
kRequestTiming: symbol;
|
15
19
|
kRequestOriginalOpaque: symbol;
|
20
|
+
kErrorSocket: symbol;
|
16
21
|
};
|
17
22
|
export default _default;
|
package/dist/esm/symbols.js
CHANGED
@@ -2,9 +2,13 @@ export default {
|
|
2
2
|
kSocketId: Symbol('socket id'),
|
3
3
|
kSocketStartTime: Symbol('socket start time'),
|
4
4
|
kSocketConnectedTime: Symbol('socket connected time'),
|
5
|
+
kSocketConnectErrorTime: Symbol('socket connectError time'),
|
5
6
|
kSocketRequestEndTime: Symbol('socket request end time'),
|
6
7
|
kSocketLocalAddress: Symbol('socket local address'),
|
7
8
|
kSocketLocalPort: Symbol('socket local port'),
|
9
|
+
kSocketConnectHost: Symbol('socket connect params: host'),
|
10
|
+
kSocketConnectPort: Symbol('socket connect params: port'),
|
11
|
+
kSocketConnectProtocol: Symbol('socket connect params: protocol'),
|
8
12
|
kHandledRequests: Symbol('handled requests per socket'),
|
9
13
|
kHandledResponses: Symbol('handled responses per socket'),
|
10
14
|
kRequestSocket: Symbol('request on the socket'),
|
@@ -13,4 +17,5 @@ export default {
|
|
13
17
|
kEnableRequestTiming: Symbol('enable request timing or not'),
|
14
18
|
kRequestTiming: Symbol('request timing'),
|
15
19
|
kRequestOriginalOpaque: Symbol('request original opaque'),
|
20
|
+
kErrorSocket: Symbol('socket of error'),
|
16
21
|
};
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "urllib",
|
3
|
-
"version": "3.
|
3
|
+
"version": "3.21.0",
|
4
4
|
"publishConfig": {
|
5
5
|
"tag": "latest"
|
6
6
|
},
|
@@ -32,8 +32,9 @@
|
|
32
32
|
"build:esm:test": "cd test/esm && rm -rf node_modules && npm link ../.. && node index.js",
|
33
33
|
"build:mts:test": "cd test/mts && rm -rf node_modules && npm link ../.. && tsc",
|
34
34
|
"build:test": "npm run build && npm run build:cjs:test && npm run build:esm:test && npm run build:mts:test && npm run test-tsc",
|
35
|
-
"test-tsc": "npm run test-tsc:cjs && npm run test-tsc:esm",
|
35
|
+
"test-tsc": "npm run test-tsc:cjs:es2021 && npm run test-tsc:cjs && npm run test-tsc:esm",
|
36
36
|
"test-tsc:cjs": "cd test/fixtures/ts && rm -rf node_modules && npm link ../../.. && npm run build",
|
37
|
+
"test-tsc:cjs:es2021": "cd test/fixtures/ts-cjs-es2021 && rm -rf node_modules && npm link ../../.. && npm run build",
|
37
38
|
"test-tsc:esm": "cd test/fixtures/ts-esm && rm -rf node_modules && npm link ../../.. && npm run build",
|
38
39
|
"test": "npm run lint && vitest run",
|
39
40
|
"test-keepalive": "cross-env TEST_KEEPALIVE_COUNT=50 vitest run --test-timeout 180000 keep-alive-header.test.ts",
|
package/src/HttpAgent.ts
CHANGED
@@ -77,7 +77,7 @@ export class HttpAgent extends Agent {
|
|
77
77
|
}
|
78
78
|
const family = isIP(hostname);
|
79
79
|
if (family === 4 || family === 6) {
|
80
|
-
// if request hostname is ip, custom lookup won't
|
80
|
+
// if request hostname is ip, custom lookup won't execute
|
81
81
|
if (!this.#checkAddress(hostname, family)) {
|
82
82
|
throw new IllegalAddressError(hostname, hostname, family);
|
83
83
|
}
|
package/src/HttpClient.ts
CHANGED
@@ -38,6 +38,7 @@ import { RawResponseWithMeta, HttpClientResponse, SocketInfo } from './Response.
|
|
38
38
|
import { parseJSON, sleep, digestAuthHeader, globalId, performanceTime, isReadable } from './utils.js';
|
39
39
|
import symbols from './symbols.js';
|
40
40
|
import { initDiagnosticsChannel } from './diagnosticsChannel.js';
|
41
|
+
import { HttpClientConnectTimeoutError, HttpClientRequestTimeoutError } from './HttpClientError.js';
|
41
42
|
|
42
43
|
type Exists<T> = T extends undefined ? never : T;
|
43
44
|
type UndiciRequestOption = Exists<Parameters<typeof undiciRequest>[1]>;
|
@@ -121,15 +122,6 @@ class BlobFromStream {
|
|
121
122
|
}
|
122
123
|
}
|
123
124
|
|
124
|
-
class HttpClientRequestTimeoutError extends Error {
|
125
|
-
constructor(timeout: number, options: ErrorOptions) {
|
126
|
-
const message = `Request timeout for ${timeout} ms`;
|
127
|
-
super(message, options);
|
128
|
-
this.name = this.constructor.name;
|
129
|
-
Error.captureStackTrace(this, this.constructor);
|
130
|
-
}
|
131
|
-
}
|
132
|
-
|
133
125
|
export const HEADER_USER_AGENT = createUserAgent('node-urllib', 'VERSION');
|
134
126
|
|
135
127
|
function getFileName(stream: Readable) {
|
@@ -653,13 +645,15 @@ export class HttpClient extends EventEmitter {
|
|
653
645
|
}
|
654
646
|
|
655
647
|
return clientResponse;
|
656
|
-
} catch (
|
657
|
-
debug('Request#%d throw error: %s', requestId,
|
658
|
-
let err =
|
648
|
+
} catch (rawError: any) {
|
649
|
+
debug('Request#%d throw error: %s', requestId, rawError);
|
650
|
+
let err = rawError;
|
659
651
|
if (err.name === 'HeadersTimeoutError') {
|
660
|
-
err = new HttpClientRequestTimeoutError(headersTimeout, { cause:
|
652
|
+
err = new HttpClientRequestTimeoutError(headersTimeout, { cause: err });
|
661
653
|
} else if (err.name === 'BodyTimeoutError') {
|
662
|
-
err = new HttpClientRequestTimeoutError(bodyTimeout, { cause:
|
654
|
+
err = new HttpClientRequestTimeoutError(bodyTimeout, { cause: err });
|
655
|
+
} else if (err.code === 'UND_ERR_CONNECT_TIMEOUT') {
|
656
|
+
err = new HttpClientConnectTimeoutError(err.message, err.code, { cause: err });
|
663
657
|
} else if (err.code === 'UND_ERR_SOCKET' || err.code === 'ECONNRESET') {
|
664
658
|
// auto retry on socket error, https://github.com/node-modules/urllib/issues/454
|
665
659
|
if (args.socketErrorRetry > 0 && requestContext.socketErrorRetries < args.socketErrorRetry) {
|
@@ -681,7 +675,7 @@ export class HttpClient extends EventEmitter {
|
|
681
675
|
res.requestUrls.push(requestUrl.href);
|
682
676
|
}
|
683
677
|
res.rt = performanceTime(requestStartTime);
|
684
|
-
this.#updateSocketInfo(socketInfo, internalOpaque);
|
678
|
+
this.#updateSocketInfo(socketInfo, internalOpaque, rawError);
|
685
679
|
|
686
680
|
channels.response.publish({
|
687
681
|
request: reqMeta,
|
@@ -704,21 +698,38 @@ export class HttpClient extends EventEmitter {
|
|
704
698
|
}
|
705
699
|
}
|
706
700
|
|
707
|
-
#updateSocketInfo(socketInfo: SocketInfo, internalOpaque: any) {
|
708
|
-
const socket = internalOpaque[symbols.kRequestSocket];
|
701
|
+
#updateSocketInfo(socketInfo: SocketInfo, internalOpaque: any, err?: any) {
|
702
|
+
const socket = internalOpaque[symbols.kRequestSocket] ?? err?.[symbols.kErrorSocket];
|
709
703
|
if (socket) {
|
710
704
|
socketInfo.id = socket[symbols.kSocketId];
|
711
705
|
socketInfo.handledRequests = socket[symbols.kHandledRequests];
|
712
706
|
socketInfo.handledResponses = socket[symbols.kHandledResponses];
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
707
|
+
if (socket[symbols.kSocketLocalAddress]) {
|
708
|
+
socketInfo.localAddress = socket[symbols.kSocketLocalAddress];
|
709
|
+
socketInfo.localPort = socket[symbols.kSocketLocalPort];
|
710
|
+
}
|
711
|
+
if (socket.remoteAddress) {
|
712
|
+
socketInfo.remoteAddress = socket.remoteAddress;
|
713
|
+
socketInfo.remotePort = socket.remotePort;
|
714
|
+
socketInfo.remoteFamily = socket.remoteFamily;
|
715
|
+
}
|
718
716
|
socketInfo.bytesRead = socket.bytesRead;
|
719
717
|
socketInfo.bytesWritten = socket.bytesWritten;
|
720
|
-
|
721
|
-
|
718
|
+
if (socket[symbols.kSocketConnectErrorTime]) {
|
719
|
+
socketInfo.connectErrorTime = socket[symbols.kSocketConnectErrorTime];
|
720
|
+
if (Array.isArray(socket.autoSelectFamilyAttemptedAddresses)) {
|
721
|
+
socketInfo.attemptedRemoteAddresses = socket.autoSelectFamilyAttemptedAddresses;
|
722
|
+
}
|
723
|
+
socketInfo.connectProtocol = socket[symbols.kSocketConnectProtocol];
|
724
|
+
socketInfo.connectHost = socket[symbols.kSocketConnectHost];
|
725
|
+
socketInfo.connectPort = socket[symbols.kSocketConnectPort];
|
726
|
+
}
|
727
|
+
if (socket[symbols.kSocketConnectedTime]) {
|
728
|
+
socketInfo.connectedTime = socket[symbols.kSocketConnectedTime];
|
729
|
+
}
|
730
|
+
if (socket[symbols.kSocketRequestEndTime]) {
|
731
|
+
socketInfo.lastRequestEndTime = socket[symbols.kSocketRequestEndTime];
|
732
|
+
}
|
722
733
|
socket[symbols.kSocketRequestEndTime] = new Date();
|
723
734
|
}
|
724
735
|
}
|
@@ -0,0 +1,34 @@
|
|
1
|
+
import type { RawResponseWithMeta, SocketInfo } from './Response.js';
|
2
|
+
import type { IncomingHttpHeaders } from './IncomingHttpHeaders.js';
|
3
|
+
|
4
|
+
// need to support ES2021
|
5
|
+
interface ErrorOptions {
|
6
|
+
cause?: Error;
|
7
|
+
}
|
8
|
+
|
9
|
+
export class HttpClientRequestError extends Error {
|
10
|
+
status?: number;
|
11
|
+
headers?: IncomingHttpHeaders;
|
12
|
+
socket?: SocketInfo;
|
13
|
+
res?: RawResponseWithMeta;
|
14
|
+
}
|
15
|
+
|
16
|
+
export class HttpClientRequestTimeoutError extends HttpClientRequestError {
|
17
|
+
constructor(timeout: number, options: ErrorOptions) {
|
18
|
+
const message = `Request timeout for ${timeout} ms`;
|
19
|
+
super(message, options);
|
20
|
+
this.name = this.constructor.name;
|
21
|
+
Error.captureStackTrace(this, this.constructor);
|
22
|
+
}
|
23
|
+
}
|
24
|
+
|
25
|
+
export class HttpClientConnectTimeoutError extends HttpClientRequestError {
|
26
|
+
code: string;
|
27
|
+
|
28
|
+
constructor(message: string, code: string, options: ErrorOptions) {
|
29
|
+
super(message, options);
|
30
|
+
this.name = this.constructor.name;
|
31
|
+
this.code = code;
|
32
|
+
Error.captureStackTrace(this, this.constructor);
|
33
|
+
}
|
34
|
+
}
|
package/src/Response.ts
CHANGED
@@ -13,7 +13,12 @@ export type SocketInfo = {
|
|
13
13
|
handledRequests: number;
|
14
14
|
handledResponses: number;
|
15
15
|
connectedTime?: Date;
|
16
|
+
connectErrorTime?: Date;
|
16
17
|
lastRequestEndTime?: Date;
|
18
|
+
attemptedRemoteAddresses?: string[];
|
19
|
+
connectProtocol?: string;
|
20
|
+
connectHost?: string;
|
21
|
+
connectPort?: string;
|
17
22
|
};
|
18
23
|
|
19
24
|
/**
|
@@ -35,9 +35,25 @@ function formatSocket(socket: Socket) {
|
|
35
35
|
localPort: socket[symbols.kSocketLocalPort],
|
36
36
|
remoteAddress: socket.remoteAddress,
|
37
37
|
remotePort: socket.remotePort,
|
38
|
+
attemptedAddresses: socket.autoSelectFamilyAttemptedAddresses,
|
39
|
+
connecting: socket.connecting,
|
38
40
|
};
|
39
41
|
}
|
40
42
|
|
43
|
+
// make sure error contains socket info
|
44
|
+
const kDestroy = Symbol('kDestroy');
|
45
|
+
Socket.prototype[kDestroy] = Socket.prototype.destroy;
|
46
|
+
Socket.prototype.destroy = function(err?: any) {
|
47
|
+
if (err) {
|
48
|
+
Object.defineProperty(err, symbols.kErrorSocket, {
|
49
|
+
// don't show on console log
|
50
|
+
enumerable: false,
|
51
|
+
value: this,
|
52
|
+
});
|
53
|
+
}
|
54
|
+
return this[kDestroy](err);
|
55
|
+
};
|
56
|
+
|
41
57
|
export function initDiagnosticsChannel() {
|
42
58
|
// makre sure init global DiagnosticsChannel once
|
43
59
|
if (initedDiagnosticsChannel) return;
|
@@ -67,10 +83,37 @@ export function initDiagnosticsChannel() {
|
|
67
83
|
});
|
68
84
|
|
69
85
|
// diagnosticsChannel.channel('undici:client:beforeConnect')
|
70
|
-
|
86
|
+
|
87
|
+
subscribe('undici:client:connectError', (message, name) => {
|
88
|
+
const { error, connectParams } = message as DiagnosticsChannel.ClientConnectErrorMessage & { error: any };
|
89
|
+
let { socket } = message as DiagnosticsChannel.ClientConnectErrorMessage;
|
90
|
+
if (!socket && error[symbols.kErrorSocket]) {
|
91
|
+
socket = error[symbols.kErrorSocket];
|
92
|
+
}
|
93
|
+
if (socket) {
|
94
|
+
socket[symbols.kSocketId] = globalId('UndiciSocket');
|
95
|
+
socket[symbols.kSocketConnectErrorTime] = new Date();
|
96
|
+
socket[symbols.kHandledRequests] = 0;
|
97
|
+
socket[symbols.kHandledResponses] = 0;
|
98
|
+
// copy local address to symbol, avoid them be reset after request error throw
|
99
|
+
if (socket.localAddress) {
|
100
|
+
socket[symbols.kSocketLocalAddress] = socket.localAddress;
|
101
|
+
socket[symbols.kSocketLocalPort] = socket.localPort;
|
102
|
+
}
|
103
|
+
socket[symbols.kSocketConnectProtocol] = connectParams.protocol;
|
104
|
+
socket[symbols.kSocketConnectHost] = connectParams.host;
|
105
|
+
socket[symbols.kSocketConnectPort] = connectParams.port;
|
106
|
+
debug('[%s] Socket#%d connectError, connectParams: %o, error: %s, (sock: %o)',
|
107
|
+
name, socket[symbols.kSocketId], connectParams, (error as Error).message, formatSocket(socket));
|
108
|
+
} else {
|
109
|
+
debug('[%s] connectError, connectParams: %o, error: %o',
|
110
|
+
name, connectParams, error);
|
111
|
+
}
|
112
|
+
});
|
113
|
+
|
71
114
|
// This message is published after a connection is established.
|
72
115
|
subscribe('undici:client:connected', (message, name) => {
|
73
|
-
const { socket } = message as DiagnosticsChannel.ClientConnectedMessage;
|
116
|
+
const { socket, connectParams } = message as DiagnosticsChannel.ClientConnectedMessage;
|
74
117
|
socket[symbols.kSocketId] = globalId('UndiciSocket');
|
75
118
|
socket[symbols.kSocketStartTime] = performance.now();
|
76
119
|
socket[symbols.kSocketConnectedTime] = new Date();
|
@@ -79,6 +122,9 @@ export function initDiagnosticsChannel() {
|
|
79
122
|
// copy local address to symbol, avoid them be reset after request error throw
|
80
123
|
socket[symbols.kSocketLocalAddress] = socket.localAddress;
|
81
124
|
socket[symbols.kSocketLocalPort] = socket.localPort;
|
125
|
+
socket[symbols.kSocketConnectProtocol] = connectParams.protocol;
|
126
|
+
socket[symbols.kSocketConnectHost] = connectParams.host;
|
127
|
+
socket[symbols.kSocketConnectPort] = connectParams.port;
|
82
128
|
debug('[%s] Socket#%d connected (sock: %o)', name, socket[symbols.kSocketId], formatSocket(socket));
|
83
129
|
});
|
84
130
|
|
package/src/index.ts
CHANGED
package/src/symbols.ts
CHANGED
@@ -2,9 +2,13 @@ export default {
|
|
2
2
|
kSocketId: Symbol('socket id'),
|
3
3
|
kSocketStartTime: Symbol('socket start time'),
|
4
4
|
kSocketConnectedTime: Symbol('socket connected time'),
|
5
|
+
kSocketConnectErrorTime: Symbol('socket connectError time'),
|
5
6
|
kSocketRequestEndTime: Symbol('socket request end time'),
|
6
7
|
kSocketLocalAddress: Symbol('socket local address'),
|
7
8
|
kSocketLocalPort: Symbol('socket local port'),
|
9
|
+
kSocketConnectHost: Symbol('socket connect params: host'),
|
10
|
+
kSocketConnectPort: Symbol('socket connect params: port'),
|
11
|
+
kSocketConnectProtocol: Symbol('socket connect params: protocol'),
|
8
12
|
kHandledRequests: Symbol('handled requests per socket'),
|
9
13
|
kHandledResponses: Symbol('handled responses per socket'),
|
10
14
|
kRequestSocket: Symbol('request on the socket'),
|
@@ -13,4 +17,5 @@ export default {
|
|
13
17
|
kEnableRequestTiming: Symbol('enable request timing or not'),
|
14
18
|
kRequestTiming: Symbol('request timing'),
|
15
19
|
kRequestOriginalOpaque: Symbol('request original opaque'),
|
20
|
+
kErrorSocket: Symbol('socket of error'),
|
16
21
|
};
|