nstarter-http-request 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -0
- package/README.md +160 -0
- package/dist/cjs/adapter/axios.adapter.js +224 -0
- package/dist/cjs/adapter/axios.adapter.js.map +1 -0
- package/dist/cjs/adapter/base.js +134 -0
- package/dist/cjs/adapter/base.js.map +1 -0
- package/dist/cjs/adapter/index.js +20 -0
- package/dist/cjs/adapter/index.js.map +1 -0
- package/dist/cjs/adapter/undici.adapter.js +272 -0
- package/dist/cjs/adapter/undici.adapter.js.map +1 -0
- package/dist/cjs/client.js +409 -0
- package/dist/cjs/client.js.map +1 -0
- package/dist/cjs/config.js +69 -0
- package/dist/cjs/config.js.map +1 -0
- package/dist/cjs/const/config.const.js +53 -0
- package/dist/cjs/const/config.const.js.map +1 -0
- package/dist/cjs/const/dns.const.js +16 -0
- package/dist/cjs/const/dns.const.js.map +1 -0
- package/dist/cjs/const/enum.const.js +22 -0
- package/dist/cjs/const/enum.const.js.map +1 -0
- package/dist/cjs/const/index.js +21 -0
- package/dist/cjs/const/index.js.map +1 -0
- package/dist/cjs/const/ip.const.js +110 -0
- package/dist/cjs/const/ip.const.js.map +1 -0
- package/dist/cjs/index.js +25 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/logger/base.js +60 -0
- package/dist/cjs/logger/base.js.map +1 -0
- package/dist/cjs/logger/http_client.logger.js +209 -0
- package/dist/cjs/logger/http_client.logger.js.map +1 -0
- package/dist/cjs/logger/index.js +20 -0
- package/dist/cjs/logger/index.js.map +1 -0
- package/dist/cjs/logger/log.filter.js +126 -0
- package/dist/cjs/logger/log.filter.js.map +1 -0
- package/dist/cjs/security/dns.validator.js +137 -0
- package/dist/cjs/security/dns.validator.js.map +1 -0
- package/dist/cjs/security/index.js +21 -0
- package/dist/cjs/security/index.js.map +1 -0
- package/dist/cjs/security/ip.validator.js +107 -0
- package/dist/cjs/security/ip.validator.js.map +1 -0
- package/dist/cjs/security/ssrf.guard.js +180 -0
- package/dist/cjs/security/ssrf.guard.js.map +1 -0
- package/dist/cjs/security/url.validator.js +170 -0
- package/dist/cjs/security/url.validator.js.map +1 -0
- package/dist/cjs/tsconfig.tsbuildinfo +1 -0
- package/dist/cjs/types/adapter.js +6 -0
- package/dist/cjs/types/adapter.js.map +1 -0
- package/dist/cjs/types/client.js +6 -0
- package/dist/cjs/types/client.js.map +1 -0
- package/dist/cjs/types/config.js +6 -0
- package/dist/cjs/types/config.js.map +1 -0
- package/dist/cjs/types/errors.js +35 -0
- package/dist/cjs/types/errors.js.map +1 -0
- package/dist/cjs/types/index.js +35 -0
- package/dist/cjs/types/index.js.map +1 -0
- package/dist/cjs/types/ip.js +6 -0
- package/dist/cjs/types/ip.js.map +1 -0
- package/dist/cjs/types/logger.js +6 -0
- package/dist/cjs/types/logger.js.map +1 -0
- package/dist/cjs/types/request_response.js +6 -0
- package/dist/cjs/types/request_response.js.map +1 -0
- package/dist/cjs/types/security.js +6 -0
- package/dist/cjs/types/security.js.map +1 -0
- package/dist/cjs/types/trace.js +14 -0
- package/dist/cjs/types/trace.js.map +1 -0
- package/dist/cjs/utils/common.js +31 -0
- package/dist/cjs/utils/common.js.map +1 -0
- package/dist/cjs/utils/domain.js +79 -0
- package/dist/cjs/utils/domain.js.map +1 -0
- package/dist/cjs/utils/index.js +44 -0
- package/dist/cjs/utils/index.js.map +1 -0
- package/dist/cjs/utils/ip.range.js +200 -0
- package/dist/cjs/utils/ip.range.js.map +1 -0
- package/dist/cjs/utils/trace.context.js +213 -0
- package/dist/cjs/utils/trace.context.js.map +1 -0
- package/dist/esm/adapter/axios.adapter.js +184 -0
- package/dist/esm/adapter/axios.adapter.js.map +1 -0
- package/dist/esm/adapter/base.js +130 -0
- package/dist/esm/adapter/base.js.map +1 -0
- package/dist/esm/adapter/index.js +4 -0
- package/dist/esm/adapter/index.js.map +1 -0
- package/dist/esm/adapter/undici.adapter.js +235 -0
- package/dist/esm/adapter/undici.adapter.js.map +1 -0
- package/dist/esm/client.js +405 -0
- package/dist/esm/client.js.map +1 -0
- package/dist/esm/config.js +65 -0
- package/dist/esm/config.js.map +1 -0
- package/dist/esm/const/config.const.js +50 -0
- package/dist/esm/const/config.const.js.map +1 -0
- package/dist/esm/const/dns.const.js +13 -0
- package/dist/esm/const/dns.const.js.map +1 -0
- package/dist/esm/const/enum.const.js +19 -0
- package/dist/esm/const/enum.const.js.map +1 -0
- package/dist/esm/const/index.js +5 -0
- package/dist/esm/const/index.js.map +1 -0
- package/dist/esm/const/ip.const.js +107 -0
- package/dist/esm/const/ip.const.js.map +1 -0
- package/dist/esm/index.js +9 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/logger/base.js +55 -0
- package/dist/esm/logger/base.js.map +1 -0
- package/dist/esm/logger/http_client.logger.js +205 -0
- package/dist/esm/logger/http_client.logger.js.map +1 -0
- package/dist/esm/logger/index.js +4 -0
- package/dist/esm/logger/index.js.map +1 -0
- package/dist/esm/logger/log.filter.js +122 -0
- package/dist/esm/logger/log.filter.js.map +1 -0
- package/dist/esm/security/dns.validator.js +133 -0
- package/dist/esm/security/dns.validator.js.map +1 -0
- package/dist/esm/security/index.js +5 -0
- package/dist/esm/security/index.js.map +1 -0
- package/dist/esm/security/ip.validator.js +103 -0
- package/dist/esm/security/ip.validator.js.map +1 -0
- package/dist/esm/security/ssrf.guard.js +176 -0
- package/dist/esm/security/ssrf.guard.js.map +1 -0
- package/dist/esm/security/url.validator.js +166 -0
- package/dist/esm/security/url.validator.js.map +1 -0
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -0
- package/dist/esm/types/adapter.js +5 -0
- package/dist/esm/types/adapter.js.map +1 -0
- package/dist/esm/types/client.js +5 -0
- package/dist/esm/types/client.js.map +1 -0
- package/dist/esm/types/config.js +5 -0
- package/dist/esm/types/config.js.map +1 -0
- package/dist/esm/types/errors.js +30 -0
- package/dist/esm/types/errors.js.map +1 -0
- package/dist/esm/types/index.js +19 -0
- package/dist/esm/types/index.js.map +1 -0
- package/dist/esm/types/ip.js +5 -0
- package/dist/esm/types/ip.js.map +1 -0
- package/dist/esm/types/logger.js +5 -0
- package/dist/esm/types/logger.js.map +1 -0
- package/dist/esm/types/request_response.js +5 -0
- package/dist/esm/types/request_response.js.map +1 -0
- package/dist/esm/types/security.js +5 -0
- package/dist/esm/types/security.js.map +1 -0
- package/dist/esm/types/trace.js +11 -0
- package/dist/esm/types/trace.js.map +1 -0
- package/dist/esm/utils/common.js +27 -0
- package/dist/esm/utils/common.js.map +1 -0
- package/dist/esm/utils/domain.js +71 -0
- package/dist/esm/utils/domain.js.map +1 -0
- package/dist/esm/utils/index.js +7 -0
- package/dist/esm/utils/index.js.map +1 -0
- package/dist/esm/utils/ip.range.js +187 -0
- package/dist/esm/utils/ip.range.js.map +1 -0
- package/dist/esm/utils/trace.context.js +199 -0
- package/dist/esm/utils/trace.context.js.map +1 -0
- package/dist/types/adapter/axios.adapter.d.ts +51 -0
- package/dist/types/adapter/axios.adapter.d.ts.map +1 -0
- package/dist/types/adapter/base.d.ts +56 -0
- package/dist/types/adapter/base.d.ts.map +1 -0
- package/dist/types/adapter/index.d.ts +4 -0
- package/dist/types/adapter/index.d.ts.map +1 -0
- package/dist/types/adapter/undici.adapter.d.ts +68 -0
- package/dist/types/adapter/undici.adapter.d.ts.map +1 -0
- package/dist/types/client.d.ts +105 -0
- package/dist/types/client.d.ts.map +1 -0
- package/dist/types/config.d.ts +14 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/const/config.const.d.ts +23 -0
- package/dist/types/const/config.const.d.ts.map +1 -0
- package/dist/types/const/dns.const.d.ts +13 -0
- package/dist/types/const/dns.const.d.ts.map +1 -0
- package/dist/types/const/enum.const.d.ts +17 -0
- package/dist/types/const/enum.const.d.ts.map +1 -0
- package/dist/types/const/index.d.ts +5 -0
- package/dist/types/const/index.d.ts.map +1 -0
- package/dist/types/const/ip.const.d.ts +42 -0
- package/dist/types/const/ip.const.d.ts.map +1 -0
- package/dist/types/index.d.ts +9 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/logger/base.d.ts +42 -0
- package/dist/types/logger/base.d.ts.map +1 -0
- package/dist/types/logger/http_client.logger.d.ts +49 -0
- package/dist/types/logger/http_client.logger.d.ts.map +1 -0
- package/dist/types/logger/index.d.ts +4 -0
- package/dist/types/logger/index.d.ts.map +1 -0
- package/dist/types/logger/log.filter.d.ts +56 -0
- package/dist/types/logger/log.filter.d.ts.map +1 -0
- package/dist/types/security/dns.validator.d.ts +61 -0
- package/dist/types/security/dns.validator.d.ts.map +1 -0
- package/dist/types/security/index.d.ts +5 -0
- package/dist/types/security/index.d.ts.map +1 -0
- package/dist/types/security/ip.validator.d.ts +31 -0
- package/dist/types/security/ip.validator.d.ts.map +1 -0
- package/dist/types/security/ssrf.guard.d.ts +54 -0
- package/dist/types/security/ssrf.guard.d.ts.map +1 -0
- package/dist/types/security/url.validator.d.ts +76 -0
- package/dist/types/security/url.validator.d.ts.map +1 -0
- package/dist/types/types/adapter.d.ts +30 -0
- package/dist/types/types/adapter.d.ts.map +1 -0
- package/dist/types/types/client.d.ts +85 -0
- package/dist/types/types/client.d.ts.map +1 -0
- package/dist/types/types/config.d.ts +99 -0
- package/dist/types/types/config.d.ts.map +1 -0
- package/dist/types/types/errors.d.ts +23 -0
- package/dist/types/types/errors.d.ts.map +1 -0
- package/dist/types/types/index.d.ts +10 -0
- package/dist/types/types/index.d.ts.map +1 -0
- package/dist/types/types/ip.d.ts +32 -0
- package/dist/types/types/ip.d.ts.map +1 -0
- package/dist/types/types/logger.d.ts +136 -0
- package/dist/types/types/logger.d.ts.map +1 -0
- package/dist/types/types/request_response.d.ts +54 -0
- package/dist/types/types/request_response.d.ts.map +1 -0
- package/dist/types/types/security.d.ts +115 -0
- package/dist/types/types/security.d.ts.map +1 -0
- package/dist/types/types/trace.d.ts +34 -0
- package/dist/types/types/trace.d.ts.map +1 -0
- package/dist/types/utils/common.d.ts +14 -0
- package/dist/types/utils/common.d.ts.map +1 -0
- package/dist/types/utils/domain.d.ts +39 -0
- package/dist/types/utils/domain.d.ts.map +1 -0
- package/dist/types/utils/index.d.ts +6 -0
- package/dist/types/utils/index.d.ts.map +1 -0
- package/dist/types/utils/ip.range.d.ts +61 -0
- package/dist/types/utils/ip.range.d.ts.map +1 -0
- package/dist/types/utils/trace.context.d.ts +106 -0
- package/dist/types/utils/trace.context.d.ts.map +1 -0
- package/docs/adapters.md +53 -0
- package/docs/configuration.md +149 -0
- package/docs/logging.md +70 -0
- package/docs/proxy.md +44 -0
- package/docs/security.md +56 -0
- package/docs/trace-context.md +436 -0
- package/package.json +50 -0
- package/src/adapter/axios.adapter.ts +228 -0
- package/src/adapter/base.ts +180 -0
- package/src/adapter/index.ts +3 -0
- package/src/adapter/undici.adapter.ts +282 -0
- package/src/client.ts +552 -0
- package/src/config.ts +86 -0
- package/src/const/config.const.ts +60 -0
- package/src/const/dns.const.ts +15 -0
- package/src/const/enum.const.ts +17 -0
- package/src/const/index.ts +4 -0
- package/src/const/ip.const.ts +139 -0
- package/src/index.ts +8 -0
- package/src/logger/base.ts +75 -0
- package/src/logger/http_client.logger.ts +272 -0
- package/src/logger/index.ts +3 -0
- package/src/logger/log.filter.ts +149 -0
- package/src/security/dns.validator.ts +170 -0
- package/src/security/index.ts +4 -0
- package/src/security/ip.validator.ts +124 -0
- package/src/security/ssrf.guard.ts +224 -0
- package/src/security/url.validator.ts +192 -0
- package/src/types/adapter.ts +38 -0
- package/src/types/client.ts +119 -0
- package/src/types/config.ts +110 -0
- package/src/types/errors.ts +38 -0
- package/src/types/index.ts +27 -0
- package/src/types/ip.ts +34 -0
- package/src/types/logger.ts +150 -0
- package/src/types/request_response.ts +65 -0
- package/src/types/security.ts +126 -0
- package/src/types/trace.ts +35 -0
- package/src/utils/common.ts +28 -0
- package/src/utils/domain.ts +78 -0
- package/src/utils/index.ts +7 -0
- package/src/utils/ip.range.ts +218 -0
- package/src/utils/trace.context.ts +240 -0
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Undici 适配器实现
|
|
4
|
+
*/
|
|
5
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
+
}
|
|
11
|
+
Object.defineProperty(o, k2, desc);
|
|
12
|
+
}) : (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
o[k2] = m[k];
|
|
15
|
+
}));
|
|
16
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
17
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
18
|
+
}) : function(o, v) {
|
|
19
|
+
o["default"] = v;
|
|
20
|
+
});
|
|
21
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
22
|
+
var ownKeys = function(o) {
|
|
23
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
24
|
+
var ar = [];
|
|
25
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
26
|
+
return ar;
|
|
27
|
+
};
|
|
28
|
+
return ownKeys(o);
|
|
29
|
+
};
|
|
30
|
+
return function (mod) {
|
|
31
|
+
if (mod && mod.__esModule) return mod;
|
|
32
|
+
var result = {};
|
|
33
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
34
|
+
__setModuleDefault(result, mod);
|
|
35
|
+
return result;
|
|
36
|
+
};
|
|
37
|
+
})();
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.UndiciAdapter = void 0;
|
|
40
|
+
const types_1 = require("../types");
|
|
41
|
+
const base_1 = require("./base");
|
|
42
|
+
const undici_1 = __importStar(require("undici"));
|
|
43
|
+
const http_1 = require("http");
|
|
44
|
+
const const_1 = require("../const");
|
|
45
|
+
/**
|
|
46
|
+
* Undici 适配器
|
|
47
|
+
*/
|
|
48
|
+
class UndiciAdapter extends base_1.BaseHttpAdapter {
|
|
49
|
+
constructor() {
|
|
50
|
+
super(...arguments);
|
|
51
|
+
this.name = const_1.EClientAdapter.undici;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* 检查 Undici 是否可用
|
|
55
|
+
*/
|
|
56
|
+
isAvailable() {
|
|
57
|
+
return !!undici_1.default;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* 发送请求
|
|
61
|
+
* @param options 请求选项
|
|
62
|
+
*/
|
|
63
|
+
async sendRequest(options) {
|
|
64
|
+
if (!this.isAvailable()) {
|
|
65
|
+
throw new Error('Undici is not available. Please install it: npm install undici');
|
|
66
|
+
}
|
|
67
|
+
const fullUrl = this.buildUrl(options.url, options.params);
|
|
68
|
+
const headers = this.prepareHeaders(options.headers);
|
|
69
|
+
const body = this.prepareBody(options.body, headers);
|
|
70
|
+
const dispatcher = this.buildDispatcher(options);
|
|
71
|
+
const undiciOptions = this.buildUndiciOptions(options, headers, body, dispatcher);
|
|
72
|
+
try {
|
|
73
|
+
const startTime = Date.now();
|
|
74
|
+
const response = await undici_1.default.request(fullUrl, undiciOptions);
|
|
75
|
+
const duration = Date.now() - startTime;
|
|
76
|
+
this.logger?.debug('HTTP request completed', {
|
|
77
|
+
method: options.method,
|
|
78
|
+
url: fullUrl,
|
|
79
|
+
status: response.statusCode,
|
|
80
|
+
duration
|
|
81
|
+
});
|
|
82
|
+
const data = await this.readResponseBody(response, options.responseType);
|
|
83
|
+
const httpResponse = {
|
|
84
|
+
status: response.statusCode,
|
|
85
|
+
statusText: this.getStatusText(response.statusCode),
|
|
86
|
+
headers: this.normalizeHeaders(response.headers),
|
|
87
|
+
data,
|
|
88
|
+
config: options
|
|
89
|
+
};
|
|
90
|
+
return httpResponse;
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
throw this.handleUndiciError(error, options);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* 准备请求头
|
|
98
|
+
* @param headers 原始请求头
|
|
99
|
+
*/
|
|
100
|
+
prepareHeaders(headers) {
|
|
101
|
+
const prepared = {};
|
|
102
|
+
if (headers) {
|
|
103
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
104
|
+
prepared[key] = typeof value === 'number' ? String(value) : value;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return prepared;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* 准备请求体
|
|
111
|
+
* @param body 原始请求体
|
|
112
|
+
* @param headers 请求头
|
|
113
|
+
*/
|
|
114
|
+
prepareBody(body, headers) {
|
|
115
|
+
if (body === undefined) {
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
if (typeof body === 'object' && !(body instanceof Buffer)) {
|
|
119
|
+
// 自动设置 Content-Type
|
|
120
|
+
if (!headers['content-type'] && !headers['Content-Type']) {
|
|
121
|
+
headers['Content-Type'] = 'application/json';
|
|
122
|
+
}
|
|
123
|
+
return JSON.stringify(body);
|
|
124
|
+
}
|
|
125
|
+
return body;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* 构建 Dispatcher(代理或 TLS 配置)
|
|
129
|
+
* @param options 请求选项
|
|
130
|
+
*/
|
|
131
|
+
buildDispatcher(options) {
|
|
132
|
+
let dispatcher;
|
|
133
|
+
// 如果 rejectUnauthorized 为 false,需要创建 Agent 来设置 TLS 选项
|
|
134
|
+
if (!this.securityConfig.rejectUnauthorized) {
|
|
135
|
+
dispatcher = new undici_1.Agent({
|
|
136
|
+
connect: {
|
|
137
|
+
rejectUnauthorized: false
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
// 有代理时,创建 ProxyAgent
|
|
142
|
+
if (this.requestConfig.proxy) {
|
|
143
|
+
dispatcher = this.createGlobalProxyAgent();
|
|
144
|
+
}
|
|
145
|
+
// 设置正向代理(如果请求元数据中包含代理信息,会覆盖全局代理配置)
|
|
146
|
+
const forwardProxyMeta = options.meta?.__forwardProxy;
|
|
147
|
+
if (forwardProxyMeta) {
|
|
148
|
+
dispatcher = this.createForwardProxyAgent(forwardProxyMeta.proxyUrl);
|
|
149
|
+
}
|
|
150
|
+
return dispatcher;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* 创建全局代理 Agent
|
|
154
|
+
*/
|
|
155
|
+
createGlobalProxyAgent() {
|
|
156
|
+
const { protocol, host, port, auth } = this.requestConfig.proxy;
|
|
157
|
+
let proxyUrl = `${protocol}://${host}:${port}`;
|
|
158
|
+
if (auth) {
|
|
159
|
+
const encodedUsername = encodeURIComponent(auth.username);
|
|
160
|
+
const encodedPassword = encodeURIComponent(auth.password);
|
|
161
|
+
proxyUrl = `${protocol}://${encodedUsername}:${encodedPassword}@${host}:${port}`;
|
|
162
|
+
}
|
|
163
|
+
const agent = new undici_1.ProxyAgent({
|
|
164
|
+
uri: proxyUrl,
|
|
165
|
+
connect: {
|
|
166
|
+
rejectUnauthorized: this.securityConfig.rejectUnauthorized
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
this.logger?.debug('Using ProxyAgent with global proxy config', {
|
|
170
|
+
host,
|
|
171
|
+
port,
|
|
172
|
+
protocol,
|
|
173
|
+
rejectUnauthorized: this.securityConfig.rejectUnauthorized
|
|
174
|
+
});
|
|
175
|
+
return agent;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* 创建正向代理 Agent
|
|
179
|
+
* @param proxyUrl 代理 URL
|
|
180
|
+
*/
|
|
181
|
+
createForwardProxyAgent(proxyUrl) {
|
|
182
|
+
const agent = new undici_1.ProxyAgent({
|
|
183
|
+
uri: proxyUrl,
|
|
184
|
+
connect: {
|
|
185
|
+
rejectUnauthorized: this.securityConfig.rejectUnauthorized
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
this.logger?.debug('Using ProxyAgent for forward proxy (overrides global proxy)', {
|
|
189
|
+
proxyUrl,
|
|
190
|
+
rejectUnauthorized: this.securityConfig.rejectUnauthorized
|
|
191
|
+
});
|
|
192
|
+
return agent;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* 构建 Undici 请求选项
|
|
196
|
+
*/
|
|
197
|
+
buildUndiciOptions(options, headers, body, dispatcher) {
|
|
198
|
+
return {
|
|
199
|
+
method: options.method,
|
|
200
|
+
headers,
|
|
201
|
+
body,
|
|
202
|
+
bodyTimeout: options.timeout ?? this.requestConfig.timeout,
|
|
203
|
+
headersTimeout: options.timeout ?? this.requestConfig.timeout,
|
|
204
|
+
maxRedirections: this.requestConfig.maxRedirects,
|
|
205
|
+
...(dispatcher && { dispatcher }),
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* 处理 Undici 错误
|
|
210
|
+
* @param error 错误对象
|
|
211
|
+
* @param options 请求选项
|
|
212
|
+
*/
|
|
213
|
+
handleUndiciError(error, options) {
|
|
214
|
+
const errorMessage = error instanceof Error ? error.message : 'Undici request failed';
|
|
215
|
+
const errorCode = (error && typeof error === 'object' && 'code' in error && typeof error.code === 'string')
|
|
216
|
+
? error.code
|
|
217
|
+
: 'UNDICI_ERROR';
|
|
218
|
+
return new types_1.HttpError(errorMessage, errorCode, undefined, undefined, options);
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* 读取响应体
|
|
222
|
+
* @param response Undici 响应对象
|
|
223
|
+
* @param responseType 响应类型
|
|
224
|
+
*/
|
|
225
|
+
async readResponseBody(response, responseType) {
|
|
226
|
+
try {
|
|
227
|
+
switch (responseType) {
|
|
228
|
+
case 'text': {
|
|
229
|
+
return (await response.body.text());
|
|
230
|
+
}
|
|
231
|
+
case 'arraybuffer': {
|
|
232
|
+
return (await response.body.arrayBuffer());
|
|
233
|
+
}
|
|
234
|
+
case 'stream': {
|
|
235
|
+
// undici 返回 BodyReadable (继承自 Node.js Readable)
|
|
236
|
+
return response.body;
|
|
237
|
+
}
|
|
238
|
+
case 'blob': {
|
|
239
|
+
return (await response.body.blob());
|
|
240
|
+
}
|
|
241
|
+
case 'formdata': {
|
|
242
|
+
return (await response.body.formData());
|
|
243
|
+
}
|
|
244
|
+
case 'json': {
|
|
245
|
+
return (await response.body.json());
|
|
246
|
+
}
|
|
247
|
+
default: {
|
|
248
|
+
const text = await response.body.text();
|
|
249
|
+
try {
|
|
250
|
+
return JSON.parse(text);
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
// 如果不是有效的 JSON,返回原文本
|
|
254
|
+
return text;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
throw new Error(`Failed to read response body: ${error instanceof Error ? error.message : String(error)}`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* 获取状态文本
|
|
265
|
+
* @param statusCode 状态码
|
|
266
|
+
*/
|
|
267
|
+
getStatusText(statusCode) {
|
|
268
|
+
return http_1.STATUS_CODES[statusCode] || 'Unknown';
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
exports.UndiciAdapter = UndiciAdapter;
|
|
272
|
+
//# sourceMappingURL=undici.adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"undici.adapter.js","sourceRoot":"","sources":["../../../src/adapter/undici.adapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* HTTP 客户端核心类
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.NsHttpClient = void 0;
|
|
7
|
+
const config_1 = require("./config");
|
|
8
|
+
const security_1 = require("./security");
|
|
9
|
+
const adapter_1 = require("./adapter");
|
|
10
|
+
const logger_1 = require("./logger");
|
|
11
|
+
const const_1 = require("./const");
|
|
12
|
+
const types_1 = require("./types");
|
|
13
|
+
const utils_1 = require("./utils");
|
|
14
|
+
const node_stream_1 = require("node:stream");
|
|
15
|
+
/**
|
|
16
|
+
* HTTP 客户端
|
|
17
|
+
*/
|
|
18
|
+
class NsHttpClient {
|
|
19
|
+
constructor(userConfig = {}) {
|
|
20
|
+
// 合并配置
|
|
21
|
+
this.config = (0, config_1.mergeConfig)(userConfig);
|
|
22
|
+
// 验证配置
|
|
23
|
+
(0, config_1.validateConfig)(this.config);
|
|
24
|
+
// 初始化日志记录器
|
|
25
|
+
this.logger = this.initLogger();
|
|
26
|
+
this.httpLogger = this.initHttpLogger();
|
|
27
|
+
// 初始化 SSRF 防护
|
|
28
|
+
this.ssrfGuard = new security_1.SsrfGuard(this.config.security);
|
|
29
|
+
// 初始化适配器
|
|
30
|
+
this.adapter = this.initAdapter();
|
|
31
|
+
this.logger.debug('NsHttpClient initialized', {
|
|
32
|
+
adapter: this.config.adapter,
|
|
33
|
+
security: {
|
|
34
|
+
enableDnsValidation: this.config.security.enableDnsValidation,
|
|
35
|
+
enableIpValidation: this.config.security.enableIpValidation,
|
|
36
|
+
allowPrivateIp: this.config.security.allowPrivateIp
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* 从配置创建 HTTP 客户端
|
|
42
|
+
* @param config
|
|
43
|
+
*/
|
|
44
|
+
static fromHttpClientConfig(config = {}) {
|
|
45
|
+
return new NsHttpClient(config);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* 挂载适配器.
|
|
49
|
+
* @param adapter
|
|
50
|
+
*/
|
|
51
|
+
withAdapter(adapter) {
|
|
52
|
+
this.config.adapter = adapter;
|
|
53
|
+
this.adapter = this.initAdapter();
|
|
54
|
+
return this;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* 挂载正向代理.
|
|
58
|
+
* @param proxy
|
|
59
|
+
*/
|
|
60
|
+
withForwardProxy(proxy) {
|
|
61
|
+
this.forwardProxy = proxy;
|
|
62
|
+
this.logger.info('Forward proxy configured', {
|
|
63
|
+
enabled: proxy.enabled,
|
|
64
|
+
http_proxy: proxy.http_proxy,
|
|
65
|
+
https_proxy: proxy.https_proxy,
|
|
66
|
+
no_proxy_count: proxy.no_proxy?.length || 0
|
|
67
|
+
});
|
|
68
|
+
return this;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* 初始化日志记录器
|
|
72
|
+
*/
|
|
73
|
+
initLogger() {
|
|
74
|
+
if (!this.config.logging.enabled) {
|
|
75
|
+
return new logger_1.NullLogger();
|
|
76
|
+
}
|
|
77
|
+
return this.config.logging.logger || new logger_1.NullLogger();
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* 初始化 HTTP 客户端日志记录器
|
|
81
|
+
*/
|
|
82
|
+
initHttpLogger() {
|
|
83
|
+
if (!this.config.logging.enabled) {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
// 如果用户提供了自定义的 httpLogger,直接使用
|
|
87
|
+
if (this.config.logging.httpLogger) {
|
|
88
|
+
return this.config.logging.httpLogger;
|
|
89
|
+
}
|
|
90
|
+
// 否则,使用 NsHttpClientLogger 包装通用的 logger
|
|
91
|
+
return logger_1.NsHttpClientLogger.create(this.logger, this.config.logging.filter, {
|
|
92
|
+
logRequestBody: this.config.logging.logRequestBody,
|
|
93
|
+
logResponseBody: this.config.logging.logResponseBody,
|
|
94
|
+
bodyMaxLength: this.config.logging.bodyMaxLength
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* 初始化适配器
|
|
99
|
+
*/
|
|
100
|
+
initAdapter() {
|
|
101
|
+
const adapter = this.config.adapter === const_1.EClientAdapter.axios
|
|
102
|
+
? new adapter_1.AxiosAdapter(this.config.request, this.config.security, this.logger)
|
|
103
|
+
: new adapter_1.UndiciAdapter(this.config.request, this.config.security, this.logger);
|
|
104
|
+
if (!adapter.isAvailable()) {
|
|
105
|
+
throw new Error(`Adapter "${this.config.adapter}" is not available. ` +
|
|
106
|
+
`Please install it: npm install ${this.config.adapter}`);
|
|
107
|
+
}
|
|
108
|
+
return adapter;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* 发送 HTTP 请求
|
|
112
|
+
* @param options 请求选项
|
|
113
|
+
* @returns 响应结果
|
|
114
|
+
*/
|
|
115
|
+
async request(options) {
|
|
116
|
+
const startTime = Date.now();
|
|
117
|
+
try {
|
|
118
|
+
// 安全验证
|
|
119
|
+
const { safeUrl, result: ssrfResult } = await this.ssrfGuard.validateAndGetSafeUrl(options.url);
|
|
120
|
+
this.logger.debug('URL validation passed', {
|
|
121
|
+
originalUrl: options.url,
|
|
122
|
+
safeUrl,
|
|
123
|
+
resolvedIps: ssrfResult.resolvedIps,
|
|
124
|
+
scenario: options.scenario
|
|
125
|
+
});
|
|
126
|
+
// 使用安全的 URL 发起请求
|
|
127
|
+
const safeOptions = {
|
|
128
|
+
...options,
|
|
129
|
+
url: safeUrl
|
|
130
|
+
};
|
|
131
|
+
// 如果 URL 被替换为 IP,需要设置 Host 头
|
|
132
|
+
if (ssrfResult.safeIp && ssrfResult.hostname !== ssrfResult.safeIp) {
|
|
133
|
+
safeOptions.headers = {
|
|
134
|
+
...safeOptions.headers,
|
|
135
|
+
Host: ssrfResult.hostname
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
// 处理 W3C Trace Context traceparent 头
|
|
139
|
+
if (options.traceparent) {
|
|
140
|
+
// 验证 traceparent 格式
|
|
141
|
+
if (!utils_1.TraceContextUtils.isValidTraceParent(options.traceparent)) {
|
|
142
|
+
this.logger.warn('Invalid traceparent format, ignoring', {
|
|
143
|
+
traceparent: options.traceparent
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
safeOptions.headers = {
|
|
148
|
+
...safeOptions.headers,
|
|
149
|
+
traceparent: options.traceparent
|
|
150
|
+
};
|
|
151
|
+
this.logger.debug('Traceparent header added', {
|
|
152
|
+
traceparent: options.traceparent,
|
|
153
|
+
sampled: utils_1.TraceContextUtils.isSampled(options.traceparent)
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// 应用正向代理配置
|
|
158
|
+
if (this.forwardProxy) {
|
|
159
|
+
this.applyForwardProxy(safeOptions);
|
|
160
|
+
}
|
|
161
|
+
// 发起请求
|
|
162
|
+
const response = await this.adapter.request(safeOptions);
|
|
163
|
+
// 首次响应时间(TTFB)
|
|
164
|
+
const ttfb = Date.now() - startTime;
|
|
165
|
+
// 如果是流式响应,包装流对象以追踪完整生命周期
|
|
166
|
+
if (options.responseType === 'stream' && this.isStreamResponse(response.data)) {
|
|
167
|
+
return this.wrapStreamResponse(safeOptions, response, startTime, ttfb);
|
|
168
|
+
}
|
|
169
|
+
// 记录非流式响应日志
|
|
170
|
+
this.logResponse(safeOptions, response, ttfb);
|
|
171
|
+
return response;
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
// 记录错误日志
|
|
175
|
+
this.logError(options, error, Date.now() - startTime);
|
|
176
|
+
throw error;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* 便捷方法:GET 请求
|
|
181
|
+
*/
|
|
182
|
+
async get(url, options) {
|
|
183
|
+
return this.request({ ...options, method: 'GET', url });
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* 便捷方法:POST 请求
|
|
187
|
+
*/
|
|
188
|
+
async post(url, body, options) {
|
|
189
|
+
return this.request({ ...options, method: 'POST', url, body });
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* 便捷方法:PUT 请求
|
|
193
|
+
*/
|
|
194
|
+
async put(url, body, options) {
|
|
195
|
+
return this.request({ ...options, method: 'PUT', url, body });
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* 便捷方法:DELETE 请求
|
|
199
|
+
*/
|
|
200
|
+
async delete(url, options) {
|
|
201
|
+
return this.request({ ...options, method: 'DELETE', url });
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* 便捷方法:PATCH 请求
|
|
205
|
+
*/
|
|
206
|
+
async patch(url, body, options) {
|
|
207
|
+
return this.request({ ...options, method: 'PATCH', url, body });
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* 记录响应日志
|
|
211
|
+
*/
|
|
212
|
+
logResponse(options, response, duration, streamMetrics) {
|
|
213
|
+
if (!this.httpLogger) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
// 计算实际重试次数
|
|
217
|
+
const retryCount = options.retries ?? this.config.request.retries;
|
|
218
|
+
this.httpLogger.logSuccess({
|
|
219
|
+
request: {
|
|
220
|
+
method: options.method,
|
|
221
|
+
url: options.url,
|
|
222
|
+
headers: options.headers,
|
|
223
|
+
body: options.body,
|
|
224
|
+
scenario: options.scenario,
|
|
225
|
+
meta: options.meta
|
|
226
|
+
},
|
|
227
|
+
response,
|
|
228
|
+
duration,
|
|
229
|
+
retryCount,
|
|
230
|
+
streamMetrics
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* 判断响应数据是否为流对象(Node.js Readable)
|
|
235
|
+
*/
|
|
236
|
+
isStreamResponse(data) {
|
|
237
|
+
// undici 和 axios 都返回 Node.js Readable
|
|
238
|
+
return data instanceof node_stream_1.Readable;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* 包装流式响应,追踪流的完整生命周期
|
|
242
|
+
*/
|
|
243
|
+
/* eslint-disable max-lines-per-function */
|
|
244
|
+
wrapStreamResponse(options, response, requestStartTime, ttfb) {
|
|
245
|
+
const stream = response.data;
|
|
246
|
+
let bytesTransferred = 0;
|
|
247
|
+
// 记录首次响应日志(流开始)
|
|
248
|
+
this.logResponse(options, response, ttfb, {
|
|
249
|
+
ttfb,
|
|
250
|
+
completed: false
|
|
251
|
+
});
|
|
252
|
+
this.logger.debug('Stream response started', {
|
|
253
|
+
method: options.method,
|
|
254
|
+
url: options.url,
|
|
255
|
+
ttfb,
|
|
256
|
+
status: response.status
|
|
257
|
+
});
|
|
258
|
+
// 所有适配器都返回 Node.js Readable
|
|
259
|
+
const originalStream = stream;
|
|
260
|
+
const wrappedStream = new node_stream_1.PassThrough();
|
|
261
|
+
// 监听数据事件统计字节数
|
|
262
|
+
originalStream.on('data', (chunk) => {
|
|
263
|
+
bytesTransferred += chunk.length;
|
|
264
|
+
wrappedStream.write(chunk);
|
|
265
|
+
});
|
|
266
|
+
// 监听结束事件记录日志
|
|
267
|
+
originalStream.on('end', () => {
|
|
268
|
+
const totalDuration = Date.now() - requestStartTime;
|
|
269
|
+
wrappedStream.end();
|
|
270
|
+
// 记录流完成日志
|
|
271
|
+
this.logStreamCompleted(options, response, ttfb, totalDuration, bytesTransferred);
|
|
272
|
+
});
|
|
273
|
+
// 监听错误事件
|
|
274
|
+
originalStream.on('error', (error) => {
|
|
275
|
+
const totalDuration = Date.now() - requestStartTime;
|
|
276
|
+
this.logger.error('Stream read error', {
|
|
277
|
+
method: options.method,
|
|
278
|
+
url: options.url,
|
|
279
|
+
ttfb,
|
|
280
|
+
totalDuration,
|
|
281
|
+
bytesTransferred,
|
|
282
|
+
error: error.message
|
|
283
|
+
});
|
|
284
|
+
wrappedStream.destroy(error);
|
|
285
|
+
});
|
|
286
|
+
// 使用 pipe 连接流
|
|
287
|
+
originalStream.pipe(wrappedStream, { end: false });
|
|
288
|
+
// 返回包装后的响应
|
|
289
|
+
return {
|
|
290
|
+
...response,
|
|
291
|
+
data: wrappedStream
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* 记录流完成日志
|
|
296
|
+
*/
|
|
297
|
+
logStreamCompleted(options, response, ttfb, totalDuration, bytesTransferred) {
|
|
298
|
+
// 记录流完成日志
|
|
299
|
+
this.logResponse(options, response, totalDuration, {
|
|
300
|
+
ttfb,
|
|
301
|
+
completed: true,
|
|
302
|
+
totalDuration,
|
|
303
|
+
bytesTransferred
|
|
304
|
+
});
|
|
305
|
+
this.logger.debug('Stream response completed', {
|
|
306
|
+
method: options.method,
|
|
307
|
+
url: options.url,
|
|
308
|
+
ttfb,
|
|
309
|
+
totalDuration,
|
|
310
|
+
streamDuration: totalDuration - ttfb,
|
|
311
|
+
bytesTransferred,
|
|
312
|
+
throughput: bytesTransferred > 0 && totalDuration > ttfb
|
|
313
|
+
? `${(bytesTransferred / 1024 / ((totalDuration - ttfb) / 1000)).toFixed(2)} KB/s`
|
|
314
|
+
: 'N/A'
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* 记录错误日志
|
|
319
|
+
*/
|
|
320
|
+
logError(options, error, duration) {
|
|
321
|
+
if (!this.httpLogger) {
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
// 计算实际重试次数
|
|
325
|
+
const retryCount = options.retries ?? this.config.request.retries;
|
|
326
|
+
const httpError = error instanceof types_1.HttpError ? error : undefined;
|
|
327
|
+
this.httpLogger.logFailed({
|
|
328
|
+
request: {
|
|
329
|
+
method: options.method,
|
|
330
|
+
url: options.url,
|
|
331
|
+
headers: options.headers,
|
|
332
|
+
body: options.body,
|
|
333
|
+
scenario: options.scenario,
|
|
334
|
+
meta: options.meta
|
|
335
|
+
},
|
|
336
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
337
|
+
errorCode: httpError?.code || 'UNKNOWN_ERROR',
|
|
338
|
+
status: httpError?.status,
|
|
339
|
+
response: httpError?.response,
|
|
340
|
+
duration,
|
|
341
|
+
retryCount
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* 应用正向代理配置
|
|
346
|
+
* @param options 请求选项
|
|
347
|
+
*/
|
|
348
|
+
applyForwardProxy(options) {
|
|
349
|
+
if (!this.forwardProxy || !this.forwardProxy.enabled) {
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
const { https_proxy, http_proxy, no_proxy } = this.forwardProxy;
|
|
353
|
+
// 解析目标 URL
|
|
354
|
+
const targetUrl = new URL(options.url);
|
|
355
|
+
const hostname = targetUrl.hostname;
|
|
356
|
+
// 检查 no_proxy 规则(使用传统匹配模式)
|
|
357
|
+
const noProxyRegExps = utils_1.DomainUtils.createLegacyDomainMatchers(no_proxy ?? []);
|
|
358
|
+
if (noProxyRegExps.length > 0 && hostname) {
|
|
359
|
+
for (const noProxyRegex of noProxyRegExps) {
|
|
360
|
+
if (noProxyRegex.test(hostname)) {
|
|
361
|
+
this.logger.debug('Skip proxy for no_proxy matched host', {
|
|
362
|
+
hostname,
|
|
363
|
+
pattern: noProxyRegex.source
|
|
364
|
+
});
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
const protocol = targetUrl.protocol || 'http:';
|
|
370
|
+
const isHttps = /https:?/.test(protocol);
|
|
371
|
+
const proxyUrl = isHttps ? https_proxy : http_proxy;
|
|
372
|
+
if (!proxyUrl) {
|
|
373
|
+
this.logger.warn('Forward proxy enabled but proxy URL not configured', {
|
|
374
|
+
protocol
|
|
375
|
+
});
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
// 将代理信息附加到请求元数据中,由适配器处理
|
|
379
|
+
options.meta = {
|
|
380
|
+
...options.meta,
|
|
381
|
+
__forwardProxy: {
|
|
382
|
+
proxyUrl,
|
|
383
|
+
isHttps,
|
|
384
|
+
protocol
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
this.logger.debug('Applied forward proxy', {
|
|
388
|
+
url: options.url,
|
|
389
|
+
proxyUrl,
|
|
390
|
+
isHttps
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* 清空 DNS 缓存
|
|
395
|
+
*/
|
|
396
|
+
clearDnsCache() {
|
|
397
|
+
this.ssrfGuard.clearDnsCache();
|
|
398
|
+
this.logger.debug('DNS cache cleared');
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* 获取 DNS 缓存大小
|
|
402
|
+
* @returns 缓存条目数量
|
|
403
|
+
*/
|
|
404
|
+
getDnsCacheSize() {
|
|
405
|
+
return this.ssrfGuard.getDnsCacheSize();
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
exports.NsHttpClient = NsHttpClient;
|
|
409
|
+
//# sourceMappingURL=client.js.map
|