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
package/src/client.ts
ADDED
|
@@ -0,0 +1,552 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP 客户端核心类
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type {
|
|
6
|
+
HttpClientConfig,
|
|
7
|
+
PartialHttpClientConfig,
|
|
8
|
+
HttpRequestOptions,
|
|
9
|
+
HttpResponse,
|
|
10
|
+
IHttpAdapter,
|
|
11
|
+
IHttpClient,
|
|
12
|
+
ILogger,
|
|
13
|
+
IHttpClientLogger,
|
|
14
|
+
IForwardProxy,
|
|
15
|
+
ISsrfGuard,
|
|
16
|
+
IStreamMetrics
|
|
17
|
+
} from './types';
|
|
18
|
+
import {
|
|
19
|
+
mergeConfig,
|
|
20
|
+
validateConfig
|
|
21
|
+
} from './config';
|
|
22
|
+
import { SsrfGuard } from './security';
|
|
23
|
+
import {
|
|
24
|
+
AxiosAdapter,
|
|
25
|
+
UndiciAdapter
|
|
26
|
+
} from './adapter';
|
|
27
|
+
import {
|
|
28
|
+
NullLogger,
|
|
29
|
+
NsHttpClientLogger
|
|
30
|
+
} from './logger';
|
|
31
|
+
import { EClientAdapter } from './const';
|
|
32
|
+
import { HttpError } from './types';
|
|
33
|
+
import {
|
|
34
|
+
DomainUtils,
|
|
35
|
+
TraceContextUtils
|
|
36
|
+
} from './utils';
|
|
37
|
+
import {
|
|
38
|
+
PassThrough,
|
|
39
|
+
Readable
|
|
40
|
+
} from 'node:stream';
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* HTTP 客户端
|
|
44
|
+
*/
|
|
45
|
+
export class NsHttpClient implements IHttpClient {
|
|
46
|
+
private config: HttpClientConfig;
|
|
47
|
+
private adapter: IHttpAdapter;
|
|
48
|
+
private ssrfGuard: ISsrfGuard;
|
|
49
|
+
private logger: ILogger;
|
|
50
|
+
private httpLogger?: IHttpClientLogger;
|
|
51
|
+
private forwardProxy?: IForwardProxy;
|
|
52
|
+
|
|
53
|
+
constructor(userConfig: PartialHttpClientConfig = {}) {
|
|
54
|
+
// 合并配置
|
|
55
|
+
this.config = mergeConfig(userConfig);
|
|
56
|
+
|
|
57
|
+
// 验证配置
|
|
58
|
+
validateConfig(this.config);
|
|
59
|
+
|
|
60
|
+
// 初始化日志记录器
|
|
61
|
+
this.logger = this.initLogger();
|
|
62
|
+
this.httpLogger = this.initHttpLogger();
|
|
63
|
+
|
|
64
|
+
// 初始化 SSRF 防护
|
|
65
|
+
this.ssrfGuard = new SsrfGuard(this.config.security);
|
|
66
|
+
|
|
67
|
+
// 初始化适配器
|
|
68
|
+
this.adapter = this.initAdapter();
|
|
69
|
+
|
|
70
|
+
this.logger.debug('NsHttpClient initialized', {
|
|
71
|
+
adapter: this.config.adapter,
|
|
72
|
+
security: {
|
|
73
|
+
enableDnsValidation: this.config.security.enableDnsValidation,
|
|
74
|
+
enableIpValidation: this.config.security.enableIpValidation,
|
|
75
|
+
allowPrivateIp: this.config.security.allowPrivateIp
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* 从配置创建 HTTP 客户端
|
|
82
|
+
* @param config
|
|
83
|
+
*/
|
|
84
|
+
public static fromHttpClientConfig(config: PartialHttpClientConfig = {}): NsHttpClient {
|
|
85
|
+
return new NsHttpClient(config);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* 挂载适配器.
|
|
90
|
+
* @param adapter
|
|
91
|
+
*/
|
|
92
|
+
public withAdapter(adapter: EClientAdapter): this {
|
|
93
|
+
this.config.adapter = adapter;
|
|
94
|
+
this.adapter = this.initAdapter();
|
|
95
|
+
return this;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* 挂载正向代理.
|
|
100
|
+
* @param proxy
|
|
101
|
+
*/
|
|
102
|
+
public withForwardProxy(proxy: IForwardProxy): this {
|
|
103
|
+
this.forwardProxy = proxy;
|
|
104
|
+
this.logger.info('Forward proxy configured', {
|
|
105
|
+
enabled: proxy.enabled,
|
|
106
|
+
http_proxy: proxy.http_proxy,
|
|
107
|
+
https_proxy: proxy.https_proxy,
|
|
108
|
+
no_proxy_count: proxy.no_proxy?.length || 0
|
|
109
|
+
});
|
|
110
|
+
return this;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* 初始化日志记录器
|
|
115
|
+
*/
|
|
116
|
+
private initLogger(): ILogger {
|
|
117
|
+
if (!this.config.logging.enabled) {
|
|
118
|
+
return new NullLogger();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return this.config.logging.logger || new NullLogger();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* 初始化 HTTP 客户端日志记录器
|
|
126
|
+
*/
|
|
127
|
+
private initHttpLogger(): IHttpClientLogger | undefined {
|
|
128
|
+
if (!this.config.logging.enabled) {
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// 如果用户提供了自定义的 httpLogger,直接使用
|
|
133
|
+
if (this.config.logging.httpLogger) {
|
|
134
|
+
return this.config.logging.httpLogger;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// 否则,使用 NsHttpClientLogger 包装通用的 logger
|
|
138
|
+
return NsHttpClientLogger.create(
|
|
139
|
+
this.logger,
|
|
140
|
+
this.config.logging.filter,
|
|
141
|
+
{
|
|
142
|
+
logRequestBody: this.config.logging.logRequestBody,
|
|
143
|
+
logResponseBody: this.config.logging.logResponseBody,
|
|
144
|
+
bodyMaxLength: this.config.logging.bodyMaxLength
|
|
145
|
+
}
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* 初始化适配器
|
|
151
|
+
*/
|
|
152
|
+
private initAdapter(): IHttpAdapter {
|
|
153
|
+
const adapter = this.config.adapter === EClientAdapter.axios
|
|
154
|
+
? new AxiosAdapter(this.config.request, this.config.security, this.logger)
|
|
155
|
+
: new UndiciAdapter(this.config.request, this.config.security, this.logger);
|
|
156
|
+
|
|
157
|
+
if (!adapter.isAvailable()) {
|
|
158
|
+
throw new Error(
|
|
159
|
+
`Adapter "${ this.config.adapter }" is not available. ` +
|
|
160
|
+
`Please install it: npm install ${ this.config.adapter }`
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return adapter;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* 发送 HTTP 请求
|
|
169
|
+
* @param options 请求选项
|
|
170
|
+
* @returns 响应结果
|
|
171
|
+
*/
|
|
172
|
+
public async request<T = any>(options: HttpRequestOptions): Promise<HttpResponse<T>> {
|
|
173
|
+
const startTime = Date.now();
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
// 安全验证
|
|
177
|
+
const { safeUrl, result: ssrfResult } = await this.ssrfGuard.validateAndGetSafeUrl(options.url);
|
|
178
|
+
|
|
179
|
+
this.logger.debug('URL validation passed', {
|
|
180
|
+
originalUrl: options.url,
|
|
181
|
+
safeUrl,
|
|
182
|
+
resolvedIps: ssrfResult.resolvedIps,
|
|
183
|
+
scenario: options.scenario
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// 使用安全的 URL 发起请求
|
|
187
|
+
const safeOptions: HttpRequestOptions = {
|
|
188
|
+
...options,
|
|
189
|
+
url: safeUrl
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
// 如果 URL 被替换为 IP,需要设置 Host 头
|
|
193
|
+
if (ssrfResult.safeIp && ssrfResult.hostname !== ssrfResult.safeIp) {
|
|
194
|
+
safeOptions.headers = {
|
|
195
|
+
...safeOptions.headers,
|
|
196
|
+
Host: ssrfResult.hostname
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// 处理 W3C Trace Context traceparent 头
|
|
201
|
+
if (options.traceparent) {
|
|
202
|
+
// 验证 traceparent 格式
|
|
203
|
+
if (!TraceContextUtils.isValidTraceParent(options.traceparent)) {
|
|
204
|
+
this.logger.warn('Invalid traceparent format, ignoring', {
|
|
205
|
+
traceparent: options.traceparent
|
|
206
|
+
});
|
|
207
|
+
} else {
|
|
208
|
+
safeOptions.headers = {
|
|
209
|
+
...safeOptions.headers,
|
|
210
|
+
traceparent: options.traceparent
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
this.logger.debug('Traceparent header added', {
|
|
214
|
+
traceparent: options.traceparent,
|
|
215
|
+
sampled: TraceContextUtils.isSampled(options.traceparent)
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// 应用正向代理配置
|
|
221
|
+
if (this.forwardProxy) {
|
|
222
|
+
this.applyForwardProxy(safeOptions);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// 发起请求
|
|
226
|
+
const response = await this.adapter.request<T>(safeOptions);
|
|
227
|
+
|
|
228
|
+
// 首次响应时间(TTFB)
|
|
229
|
+
const ttfb = Date.now() - startTime;
|
|
230
|
+
|
|
231
|
+
// 如果是流式响应,包装流对象以追踪完整生命周期
|
|
232
|
+
if (options.responseType === 'stream' && this.isStreamResponse(response.data)) {
|
|
233
|
+
return this.wrapStreamResponse(safeOptions, response, startTime, ttfb);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// 记录非流式响应日志
|
|
237
|
+
this.logResponse(safeOptions, response, ttfb);
|
|
238
|
+
|
|
239
|
+
return response;
|
|
240
|
+
} catch (error) {
|
|
241
|
+
// 记录错误日志
|
|
242
|
+
this.logError(options, error, Date.now() - startTime);
|
|
243
|
+
throw error;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* 便捷方法:GET 请求
|
|
249
|
+
*/
|
|
250
|
+
public async get<T = any>(
|
|
251
|
+
url: string,
|
|
252
|
+
options?: Partial<Omit<HttpRequestOptions, 'method' | 'url'>>
|
|
253
|
+
): Promise<HttpResponse<T>> {
|
|
254
|
+
return this.request<T>({ ...options, method: 'GET', url });
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* 便捷方法:POST 请求
|
|
259
|
+
*/
|
|
260
|
+
public async post<T = any>(
|
|
261
|
+
url: string,
|
|
262
|
+
body?: any,
|
|
263
|
+
options?: Partial<Omit<HttpRequestOptions, 'method' | 'url' | 'body'>>
|
|
264
|
+
): Promise<HttpResponse<T>> {
|
|
265
|
+
return this.request<T>({ ...options, method: 'POST', url, body });
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* 便捷方法:PUT 请求
|
|
270
|
+
*/
|
|
271
|
+
public async put<T = any>(
|
|
272
|
+
url: string,
|
|
273
|
+
body?: any,
|
|
274
|
+
options?: Partial<Omit<HttpRequestOptions, 'method' | 'url' | 'body'>>
|
|
275
|
+
): Promise<HttpResponse<T>> {
|
|
276
|
+
return this.request<T>({ ...options, method: 'PUT', url, body });
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* 便捷方法:DELETE 请求
|
|
281
|
+
*/
|
|
282
|
+
public async delete<T = any>(
|
|
283
|
+
url: string,
|
|
284
|
+
options?: Partial<Omit<HttpRequestOptions, 'method' | 'url'>>
|
|
285
|
+
): Promise<HttpResponse<T>> {
|
|
286
|
+
return this.request<T>({ ...options, method: 'DELETE', url });
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* 便捷方法:PATCH 请求
|
|
291
|
+
*/
|
|
292
|
+
public async patch<T = any>(
|
|
293
|
+
url: string,
|
|
294
|
+
body?: any,
|
|
295
|
+
options?: Partial<Omit<HttpRequestOptions, 'method' | 'url' | 'body'>>
|
|
296
|
+
): Promise<HttpResponse<T>> {
|
|
297
|
+
return this.request<T>({ ...options, method: 'PATCH', url, body });
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* 记录响应日志
|
|
302
|
+
*/
|
|
303
|
+
private logResponse(
|
|
304
|
+
options: HttpRequestOptions,
|
|
305
|
+
response: HttpResponse,
|
|
306
|
+
duration: number,
|
|
307
|
+
streamMetrics?: IStreamMetrics
|
|
308
|
+
): void {
|
|
309
|
+
if (!this.httpLogger) {
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// 计算实际重试次数
|
|
314
|
+
const retryCount = options.retries ?? this.config.request.retries;
|
|
315
|
+
|
|
316
|
+
this.httpLogger.logSuccess({
|
|
317
|
+
request: {
|
|
318
|
+
method: options.method,
|
|
319
|
+
url: options.url,
|
|
320
|
+
headers: options.headers,
|
|
321
|
+
body: options.body,
|
|
322
|
+
scenario: options.scenario,
|
|
323
|
+
meta: options.meta
|
|
324
|
+
},
|
|
325
|
+
response,
|
|
326
|
+
duration,
|
|
327
|
+
retryCount,
|
|
328
|
+
streamMetrics
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* 判断响应数据是否为流对象(Node.js Readable)
|
|
334
|
+
*/
|
|
335
|
+
private isStreamResponse(data: any): boolean {
|
|
336
|
+
// undici 和 axios 都返回 Node.js Readable
|
|
337
|
+
return data instanceof Readable;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* 包装流式响应,追踪流的完整生命周期
|
|
342
|
+
*/
|
|
343
|
+
|
|
344
|
+
/* eslint-disable max-lines-per-function */
|
|
345
|
+
private wrapStreamResponse<T>(
|
|
346
|
+
options: HttpRequestOptions,
|
|
347
|
+
response: HttpResponse<T>,
|
|
348
|
+
requestStartTime: number,
|
|
349
|
+
ttfb: number
|
|
350
|
+
): HttpResponse<T> {
|
|
351
|
+
const stream = response.data as any;
|
|
352
|
+
let bytesTransferred = 0;
|
|
353
|
+
|
|
354
|
+
// 记录首次响应日志(流开始)
|
|
355
|
+
this.logResponse(options, response, ttfb, {
|
|
356
|
+
ttfb,
|
|
357
|
+
completed: false
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
this.logger.debug('Stream response started', {
|
|
361
|
+
method: options.method,
|
|
362
|
+
url: options.url,
|
|
363
|
+
ttfb,
|
|
364
|
+
status: response.status
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
// 所有适配器都返回 Node.js Readable
|
|
368
|
+
const originalStream = stream as Readable;
|
|
369
|
+
const wrappedStream = new PassThrough();
|
|
370
|
+
|
|
371
|
+
// 监听数据事件统计字节数
|
|
372
|
+
originalStream.on('data', (chunk: Buffer) => {
|
|
373
|
+
bytesTransferred += chunk.length;
|
|
374
|
+
wrappedStream.write(chunk);
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
// 监听结束事件记录日志
|
|
378
|
+
originalStream.on('end', () => {
|
|
379
|
+
const totalDuration = Date.now() - requestStartTime;
|
|
380
|
+
wrappedStream.end();
|
|
381
|
+
|
|
382
|
+
// 记录流完成日志
|
|
383
|
+
this.logStreamCompleted(
|
|
384
|
+
options,
|
|
385
|
+
response,
|
|
386
|
+
ttfb,
|
|
387
|
+
totalDuration,
|
|
388
|
+
bytesTransferred
|
|
389
|
+
);
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
// 监听错误事件
|
|
393
|
+
originalStream.on('error', (error: Error) => {
|
|
394
|
+
const totalDuration = Date.now() - requestStartTime;
|
|
395
|
+
this.logger.error('Stream read error', {
|
|
396
|
+
method: options.method,
|
|
397
|
+
url: options.url,
|
|
398
|
+
ttfb,
|
|
399
|
+
totalDuration,
|
|
400
|
+
bytesTransferred,
|
|
401
|
+
error: error.message
|
|
402
|
+
});
|
|
403
|
+
wrappedStream.destroy(error);
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
// 使用 pipe 连接流
|
|
407
|
+
originalStream.pipe(wrappedStream, { end: false });
|
|
408
|
+
|
|
409
|
+
// 返回包装后的响应
|
|
410
|
+
return {
|
|
411
|
+
...response,
|
|
412
|
+
data: wrappedStream as T
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* 记录流完成日志
|
|
418
|
+
*/
|
|
419
|
+
private logStreamCompleted(
|
|
420
|
+
options: HttpRequestOptions,
|
|
421
|
+
response: HttpResponse,
|
|
422
|
+
ttfb: number,
|
|
423
|
+
totalDuration: number,
|
|
424
|
+
bytesTransferred: number
|
|
425
|
+
): void {
|
|
426
|
+
// 记录流完成日志
|
|
427
|
+
this.logResponse(options, response, totalDuration, {
|
|
428
|
+
ttfb,
|
|
429
|
+
completed: true,
|
|
430
|
+
totalDuration,
|
|
431
|
+
bytesTransferred
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
this.logger.debug('Stream response completed', {
|
|
435
|
+
method: options.method,
|
|
436
|
+
url: options.url,
|
|
437
|
+
ttfb,
|
|
438
|
+
totalDuration,
|
|
439
|
+
streamDuration: totalDuration - ttfb,
|
|
440
|
+
bytesTransferred,
|
|
441
|
+
throughput: bytesTransferred > 0 && totalDuration > ttfb
|
|
442
|
+
? `${ (bytesTransferred / 1024 / ((totalDuration - ttfb) / 1000)).toFixed(2) } KB/s`
|
|
443
|
+
: 'N/A'
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* 记录错误日志
|
|
449
|
+
*/
|
|
450
|
+
private logError(options: HttpRequestOptions, error: any, duration: number): void {
|
|
451
|
+
if (!this.httpLogger) {
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// 计算实际重试次数
|
|
456
|
+
const retryCount = options.retries ?? this.config.request.retries;
|
|
457
|
+
|
|
458
|
+
const httpError = error instanceof HttpError ? error : undefined;
|
|
459
|
+
|
|
460
|
+
this.httpLogger.logFailed({
|
|
461
|
+
request: {
|
|
462
|
+
method: options.method,
|
|
463
|
+
url: options.url,
|
|
464
|
+
headers: options.headers,
|
|
465
|
+
body: options.body,
|
|
466
|
+
scenario: options.scenario,
|
|
467
|
+
meta: options.meta
|
|
468
|
+
},
|
|
469
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
470
|
+
errorCode: httpError?.code || 'UNKNOWN_ERROR',
|
|
471
|
+
status: httpError?.status,
|
|
472
|
+
response: httpError?.response,
|
|
473
|
+
duration,
|
|
474
|
+
retryCount
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* 应用正向代理配置
|
|
480
|
+
* @param options 请求选项
|
|
481
|
+
*/
|
|
482
|
+
private applyForwardProxy(options: HttpRequestOptions): void {
|
|
483
|
+
if (!this.forwardProxy || !this.forwardProxy.enabled) {
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
const { https_proxy, http_proxy, no_proxy } = this.forwardProxy;
|
|
488
|
+
|
|
489
|
+
// 解析目标 URL
|
|
490
|
+
const targetUrl = new URL(options.url);
|
|
491
|
+
const hostname = targetUrl.hostname;
|
|
492
|
+
|
|
493
|
+
// 检查 no_proxy 规则(使用传统匹配模式)
|
|
494
|
+
const noProxyRegExps = DomainUtils.createLegacyDomainMatchers(no_proxy ?? []);
|
|
495
|
+
if (noProxyRegExps.length > 0 && hostname) {
|
|
496
|
+
for (const noProxyRegex of noProxyRegExps) {
|
|
497
|
+
if (noProxyRegex.test(hostname)) {
|
|
498
|
+
this.logger.debug('Skip proxy for no_proxy matched host', {
|
|
499
|
+
hostname,
|
|
500
|
+
pattern: noProxyRegex.source
|
|
501
|
+
});
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
const protocol = targetUrl.protocol || 'http:';
|
|
508
|
+
const isHttps = /https:?/.test(protocol);
|
|
509
|
+
const proxyUrl = isHttps ? https_proxy : http_proxy;
|
|
510
|
+
|
|
511
|
+
if (!proxyUrl) {
|
|
512
|
+
this.logger.warn('Forward proxy enabled but proxy URL not configured', {
|
|
513
|
+
protocol
|
|
514
|
+
});
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// 将代理信息附加到请求元数据中,由适配器处理
|
|
519
|
+
options.meta = {
|
|
520
|
+
...options.meta,
|
|
521
|
+
__forwardProxy: {
|
|
522
|
+
proxyUrl,
|
|
523
|
+
isHttps,
|
|
524
|
+
protocol
|
|
525
|
+
}
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
this.logger.debug('Applied forward proxy', {
|
|
529
|
+
url: options.url,
|
|
530
|
+
proxyUrl,
|
|
531
|
+
isHttps
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* 清空 DNS 缓存
|
|
538
|
+
*/
|
|
539
|
+
public clearDnsCache(): void {
|
|
540
|
+
this.ssrfGuard.clearDnsCache();
|
|
541
|
+
this.logger.debug('DNS cache cleared');
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* 获取 DNS 缓存大小
|
|
546
|
+
* @returns 缓存条目数量
|
|
547
|
+
*/
|
|
548
|
+
public getDnsCacheSize(): number {
|
|
549
|
+
return this.ssrfGuard.getDnsCacheSize();
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
HttpClientConfig,
|
|
3
|
+
PartialHttpClientConfig
|
|
4
|
+
} from './types';
|
|
5
|
+
import {
|
|
6
|
+
DEFAULT_HTTP_CLIENT_CONFIG,
|
|
7
|
+
DEFAULT_LOGGING_CONFIG,
|
|
8
|
+
DEFAULT_REQUEST_CONFIG,
|
|
9
|
+
DEFAULT_SECURITY_CONFIG,
|
|
10
|
+
MAX_DNS_CACHE_TTL
|
|
11
|
+
} from './const';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 合并配置
|
|
15
|
+
* @param userConfig 用户配置
|
|
16
|
+
* @returns 完整配置
|
|
17
|
+
*/
|
|
18
|
+
export function mergeConfig(userConfig: PartialHttpClientConfig = {}): HttpClientConfig {
|
|
19
|
+
return {
|
|
20
|
+
adapter: userConfig.adapter ?? DEFAULT_HTTP_CLIENT_CONFIG.adapter,
|
|
21
|
+
security: {
|
|
22
|
+
...DEFAULT_SECURITY_CONFIG,
|
|
23
|
+
...userConfig.security
|
|
24
|
+
},
|
|
25
|
+
logging: {
|
|
26
|
+
...DEFAULT_LOGGING_CONFIG,
|
|
27
|
+
...userConfig.logging
|
|
28
|
+
},
|
|
29
|
+
request: {
|
|
30
|
+
...DEFAULT_REQUEST_CONFIG,
|
|
31
|
+
...userConfig.request
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 验证配置
|
|
38
|
+
* @param config 配置
|
|
39
|
+
* @throws {Error} 配置无效时抛出错误
|
|
40
|
+
*/
|
|
41
|
+
export function validateConfig(config: HttpClientConfig): void {
|
|
42
|
+
// 验证适配器
|
|
43
|
+
if (!['axios', 'undici'].includes(config.adapter)) {
|
|
44
|
+
throw new Error(`Invalid adapter: ${ config.adapter }. Must be 'axios' or 'undici'.`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// 验证安全配置
|
|
48
|
+
if (config.security.dnsCacheTtl < 0) {
|
|
49
|
+
throw new Error('DNS cache TTL must be non-negative.');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (config.security.dnsCacheTtl > MAX_DNS_CACHE_TTL) {
|
|
53
|
+
throw new Error(`DNS cache TTL must not exceed ${ MAX_DNS_CACHE_TTL }ms (24 hours) to prevent memory buildup.`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 验证请求配置
|
|
57
|
+
if (config.request.timeout <= 0) {
|
|
58
|
+
throw new Error('Timeout must be positive.');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (config.request.retries < 0) {
|
|
62
|
+
throw new Error('Retries must be non-negative.');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (config.request.retryDelay < 0) {
|
|
66
|
+
throw new Error('Retry delay must be non-negative.');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (config.request.maxRedirects < 0) {
|
|
70
|
+
throw new Error('Max redirects must be non-negative.');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (config.request.maxRequestBodySize <= 0) {
|
|
74
|
+
throw new Error('Max request body size must be positive.');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (config.request.maxResponseBodySize <= 0) {
|
|
78
|
+
throw new Error('Max response body size must be positive.');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// 验证日志配置
|
|
82
|
+
if (config.logging.bodyMaxLength <= 0) {
|
|
83
|
+
throw new Error('Body max length must be positive.');
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
HttpClientConfig,
|
|
3
|
+
LoggingConfig,
|
|
4
|
+
RequestConfig,
|
|
5
|
+
SecurityConfig
|
|
6
|
+
} from '../types';
|
|
7
|
+
import { EClientAdapter } from './enum.const';
|
|
8
|
+
import { DEFAULT_DNS_CACHE_TTL, DEFAULT_DNS_CACHE_MAX_SIZE } from './dns.const';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* DNS 缓存最大 TTL(毫秒)- 5 分钟
|
|
12
|
+
* 避免过长的缓存时间导致内存积压
|
|
13
|
+
*/
|
|
14
|
+
export const MAX_DNS_CACHE_TTL = 5 * 60 * 1000; // 300000ms (5 分钟)
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 默认安全配置
|
|
18
|
+
*/
|
|
19
|
+
export const DEFAULT_SECURITY_CONFIG: SecurityConfig = {
|
|
20
|
+
rejectUnauthorized: true,
|
|
21
|
+
enableDnsValidation: true,
|
|
22
|
+
enableIpValidation: true,
|
|
23
|
+
allowPrivateIp: false,
|
|
24
|
+
whitelist: [],
|
|
25
|
+
blacklist: [],
|
|
26
|
+
dnsCacheTtl: DEFAULT_DNS_CACHE_TTL,
|
|
27
|
+
dnsCacheMaxSize: DEFAULT_DNS_CACHE_MAX_SIZE
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 默认日志配置
|
|
32
|
+
*/
|
|
33
|
+
export const DEFAULT_LOGGING_CONFIG: LoggingConfig = {
|
|
34
|
+
enabled: true,
|
|
35
|
+
logRequestBody: true,
|
|
36
|
+
logResponseBody: true,
|
|
37
|
+
bodyMaxLength: 1024 * 1024 // 1MB
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 默认请求配置
|
|
42
|
+
*/
|
|
43
|
+
export const DEFAULT_REQUEST_CONFIG: RequestConfig = {
|
|
44
|
+
timeout: 30000, // 30 秒
|
|
45
|
+
retries: 0,
|
|
46
|
+
retryDelay: 1000, // 1 秒
|
|
47
|
+
maxRedirects: 5,
|
|
48
|
+
maxRequestBodySize: 10 * 1024 * 1024, // 10MB
|
|
49
|
+
maxResponseBodySize: 50 * 1024 * 1024 // 50MB
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 默认 HTTP 客户端配置
|
|
54
|
+
*/
|
|
55
|
+
export const DEFAULT_HTTP_CLIENT_CONFIG: HttpClientConfig = {
|
|
56
|
+
adapter: EClientAdapter.axios,
|
|
57
|
+
security: DEFAULT_SECURITY_CONFIG,
|
|
58
|
+
logging: DEFAULT_LOGGING_CONFIG,
|
|
59
|
+
request: DEFAULT_REQUEST_CONFIG
|
|
60
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 默认 DNS 缓存 TTL(毫秒)- 60 秒
|
|
3
|
+
*/
|
|
4
|
+
export const DEFAULT_DNS_CACHE_TTL = 60000;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 默认 DNS 解析超时时间(毫秒)- 3 秒
|
|
8
|
+
*/
|
|
9
|
+
export const DEFAULT_DNS_RESOLVE_TIMEOUT = 3000;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 默认 DNS 缓存最大条目数
|
|
13
|
+
*/
|
|
14
|
+
export const DEFAULT_DNS_CACHE_MAX_SIZE = 500;
|
|
15
|
+
|