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/docs/logging.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# 日志配置详解
|
|
2
|
+
|
|
3
|
+
## 自定义日志记录器
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import { ILogger } from 'nstarter-http-request';
|
|
7
|
+
|
|
8
|
+
const customLogger: ILogger = {
|
|
9
|
+
debug: (msg, meta) => console.debug(msg, meta),
|
|
10
|
+
info: (msg, meta) => console.info(msg, meta),
|
|
11
|
+
warn: (msg, meta) => console.warn(msg, meta),
|
|
12
|
+
error: (msg, meta) => console.error(msg, meta)
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const client = new NsHttpClient({
|
|
16
|
+
logging: { logger: customLogger }
|
|
17
|
+
});
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## 日志过滤
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
const client = new NsHttpClient({
|
|
24
|
+
logging: {
|
|
25
|
+
enabled: true,
|
|
26
|
+
filter: {
|
|
27
|
+
// 只记录失败的 POST 请求
|
|
28
|
+
methods: ['POST'],
|
|
29
|
+
onlyFailed: true,
|
|
30
|
+
minDuration: 1000 // 且耗时超过 1 秒
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## 完整日志配置示例
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
const client = new NsHttpClient({
|
|
40
|
+
// 日志配置
|
|
41
|
+
logging: {
|
|
42
|
+
enabled: true, // 是否启用日志
|
|
43
|
+
logRequestBody: true, // 是否记录请求体
|
|
44
|
+
logResponseBody: true, // 是否记录响应体
|
|
45
|
+
bodyMaxLength: 1024 * 1024, // 请求/响应体最大记录长度(字节,默认 1MB)
|
|
46
|
+
logger: customLogger, // 自定义日志记录器(可选,实现 ILogger 接口)
|
|
47
|
+
httpLogger: customHttpLogger, // 自定义 HTTP 日志记录器(可选,实现 IHttpClientLogger 接口)
|
|
48
|
+
filter: { // 日志过滤配置
|
|
49
|
+
methods: ['POST', 'PUT'], // 只记录指定 HTTP 方法
|
|
50
|
+
urlPatterns: [ // 只记录匹配的 URL(支持正则)
|
|
51
|
+
/\/api\//,
|
|
52
|
+
'https://api.example.com'
|
|
53
|
+
],
|
|
54
|
+
scenarios: ['payment'], // 只记录指定业务场景
|
|
55
|
+
statusCodes: [404, 500], // 只记录指定状态码
|
|
56
|
+
statusCodeRanges: [ // 只记录指定状态码范围
|
|
57
|
+
{ min: 400, max: 499 }, // 客户端错误
|
|
58
|
+
{ min: 500, max: 599 } // 服务端错误
|
|
59
|
+
],
|
|
60
|
+
onlyFailed: false, // 是否只记录失败请求
|
|
61
|
+
onlySuccess: false, // 是否只记录成功请求
|
|
62
|
+
minDuration: 100, // 只记录耗时超过指定毫秒的请求
|
|
63
|
+
customFilter: (options) => { // 自定义过滤函数
|
|
64
|
+
return options.request.method === 'POST';
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
package/docs/proxy.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# 正向代理配置
|
|
2
|
+
|
|
3
|
+
## 基础使用
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
const client = new NsHttpClient()
|
|
7
|
+
.withForwardProxy({
|
|
8
|
+
enabled: true,
|
|
9
|
+
http_proxy: 'http://proxy.example.com:8080',
|
|
10
|
+
https_proxy: 'https://proxy.example.com:8443',
|
|
11
|
+
no_proxy: [ // 不使用代理的域名(支持传统模式)
|
|
12
|
+
'localhost', // 精确匹配
|
|
13
|
+
'127.0.0.1', // 精确匹配
|
|
14
|
+
'.internal.com', // 后缀匹配(匹配所有子域名)
|
|
15
|
+
'.local', // 后缀匹配
|
|
16
|
+
'192.168.' // 前缀匹配(IP地址)
|
|
17
|
+
]
|
|
18
|
+
});
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## no_proxy 支持的模式
|
|
22
|
+
|
|
23
|
+
- `example.com` - 精确匹配该域名
|
|
24
|
+
- `.example.com` - 后缀匹配,匹配所有以 `.example.com` 结尾的域名(包括子域名)
|
|
25
|
+
- `192.168.` - 前缀匹配,主要用于 IP 地址范围匹配
|
|
26
|
+
|
|
27
|
+
## HTTP 代理配置(不同于正向代理)
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
const client = new NsHttpClient({
|
|
31
|
+
request: {
|
|
32
|
+
proxy: { // HTTP 代理配置(可选,不同于正向代理)
|
|
33
|
+
host: 'proxy.example.com',
|
|
34
|
+
port: 8080,
|
|
35
|
+
protocol: 'http', // 'http' | 'https'
|
|
36
|
+
auth: { // 代理认证(可选)
|
|
37
|
+
username: 'user',
|
|
38
|
+
password: 'pass'
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
|
package/docs/security.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# 安全配置详解
|
|
2
|
+
|
|
3
|
+
## SSL 证书验证
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
const client = new NsHttpClient({
|
|
7
|
+
security: {
|
|
8
|
+
rejectUnauthorized: true // 生产环境必须为 true,开发环境可设为 false
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## SSRF 防护
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
const client = new NsHttpClient({
|
|
17
|
+
security: {
|
|
18
|
+
enableDnsValidation: true, // 启用 DNS 解析验证
|
|
19
|
+
enableIpValidation: true, // 启用 IP 地址验证
|
|
20
|
+
allowPrivateIp: false, // 禁止访问内网 IP(10.x.x.x, 192.168.x.x, 127.x.x.x 等)
|
|
21
|
+
whitelist: [ // 白名单(支持以下模式)
|
|
22
|
+
'api.example.com', // 1. 精确匹配
|
|
23
|
+
'*.safe.com', // 2. 通配符匹配(只匹配一级子域名)
|
|
24
|
+
'trusted.com', '*.trusted.com' // 3. 组合使用:匹配根域名和一级子域名
|
|
25
|
+
],
|
|
26
|
+
blacklist: ['malicious.com', '*.malicious.com'] // 黑名单:根域名 + 一级子域名
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## 支持的域名模式
|
|
32
|
+
|
|
33
|
+
| 模式 | 匹配规则 | 示例 |
|
|
34
|
+
|------|---------|------|
|
|
35
|
+
| `example.com` | 精确匹配 | ✅ `example.com`<br>❌ `api.example.com` |
|
|
36
|
+
| `*.example.com` | 通配符匹配<br>只匹配一级子域名(不含根域名) | ❌ `example.com`<br>✅ `api.example.com`<br>✅ `www.example.com`<br>❌ `v2.api.example.com`(多级不匹配) |
|
|
37
|
+
| 组合使用<br>`['example.com', '*.example.com']` | 精确匹配 + 通配符匹配<br>匹配根域名和一级子域名 | ✅ `example.com`<br>✅ `api.example.com`<br>✅ `www.example.com`<br>❌ `v2.api.example.com`(多级不匹配) |
|
|
38
|
+
|
|
39
|
+
## DNS 缓存机制
|
|
40
|
+
|
|
41
|
+
为提升性能,DNS 解析结果会被缓存。缓存使用 [lru-cache](https://www.npmjs.com/package/lru-cache) 实现,具有以下特性:
|
|
42
|
+
|
|
43
|
+
- ✅ **TTL 自动过期**:缓存条目到期后自动释放内存
|
|
44
|
+
- ✅ **LRU 淘汰策略**:缓存满时自动淘汰最近最少使用的条目
|
|
45
|
+
- ✅ **容量限制**:默认最多缓存 500 个域名解析结果
|
|
46
|
+
- ✅ **可配置**:支持自定义 TTL 和缓存容量
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
const client = new NsHttpClient({
|
|
50
|
+
security: {
|
|
51
|
+
dnsCacheTtl: 60000, // 缓存时间:60 秒(默认)
|
|
52
|
+
dnsCacheMaxSize: 500 // 最大缓存条目数(默认)
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
# W3C Trace Context 支持
|
|
2
|
+
|
|
3
|
+
本 HTTP 客户端完全支持 [W3C Trace Context](https://www.w3.org/TR/trace-context/) 规范,允许你在分布式系统中进行跨服务的链路追踪。
|
|
4
|
+
|
|
5
|
+
## 什么是 Trace Context?
|
|
6
|
+
|
|
7
|
+
W3C Trace Context 定义了标准的 HTTP 头格式,用于在分布式系统中传播追踪上下文信息。核心是 `traceparent` 头,格式如下:
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
traceparent: version-trace-id-parent-id-trace-flags
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
例如:
|
|
14
|
+
```
|
|
15
|
+
traceparent: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
其中:
|
|
19
|
+
- `version`(2位):当前固定为 `00`
|
|
20
|
+
- `trace-id`(32位十六进制):唯一标识整个追踪链路
|
|
21
|
+
- `parent-id`(16位十六进制):唯一标识当前 span/操作
|
|
22
|
+
- `trace-flags`(2位十六进制):标志位,`01` 表示已采样,`00` 表示未采样
|
|
23
|
+
|
|
24
|
+
## 基础使用
|
|
25
|
+
|
|
26
|
+
### 1. 在请求中传递 traceparent
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { NsHttpClient, TraceContext } from '@nstarter/plugin-http-request';
|
|
30
|
+
|
|
31
|
+
const client = new NsHttpClient();
|
|
32
|
+
|
|
33
|
+
// 生成新的 traceparent
|
|
34
|
+
const traceparent = TraceContext.generate();
|
|
35
|
+
|
|
36
|
+
// 在请求中使用
|
|
37
|
+
const response = await client.get('https://api.example.com/users', {
|
|
38
|
+
traceparent
|
|
39
|
+
});
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### 2. 传播现有的 traceparent
|
|
43
|
+
|
|
44
|
+
在微服务架构中,你通常需要从上游服务接收 traceparent 并传递给下游服务:
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
// 从上游请求中获取 traceparent
|
|
48
|
+
const incomingTraceparent = request.headers.traceparent;
|
|
49
|
+
|
|
50
|
+
// 创建子 span 并传递给下游服务
|
|
51
|
+
const childTraceparent = TraceContext.createChild(incomingTraceparent);
|
|
52
|
+
|
|
53
|
+
const response = await client.post('https://downstream-service.com/api', data, {
|
|
54
|
+
traceparent: childTraceparent
|
|
55
|
+
});
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 3. 自定义 traceparent 生成
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
// 生成未采样的 traceparent
|
|
62
|
+
const unsampled = TraceContext.generate({ sampled: false });
|
|
63
|
+
|
|
64
|
+
// 使用自定义的 trace ID
|
|
65
|
+
const customTrace = TraceContext.generate({
|
|
66
|
+
traceId: '0af7651916cd43dd8448eb211c80319c',
|
|
67
|
+
sampled: true
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## TraceContext 工具类
|
|
72
|
+
|
|
73
|
+
`TraceContext` 类提供了完整的工具方法来处理 W3C Trace Context:
|
|
74
|
+
|
|
75
|
+
### 生成方法
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
// 生成新的 traceparent
|
|
79
|
+
const traceparent = TraceContext.generate({
|
|
80
|
+
traceId?: string, // 可选,自定义 trace ID
|
|
81
|
+
parentId?: string, // 可选,自定义 parent/span ID
|
|
82
|
+
sampled?: boolean // 可选,是否采样,默认 true
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// 生成 trace ID(32位十六进制)
|
|
86
|
+
const traceId = TraceContext.generateTraceId();
|
|
87
|
+
|
|
88
|
+
// 生成 span ID(16位十六进制)
|
|
89
|
+
const spanId = TraceContext.generateSpanId();
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### 解析和验证
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
// 解析 traceparent
|
|
96
|
+
const parsed = TraceContext.parse('00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01');
|
|
97
|
+
// 返回:
|
|
98
|
+
// {
|
|
99
|
+
// version: '00',
|
|
100
|
+
// traceId: '0af7651916cd43dd8448eb211c80319c',
|
|
101
|
+
// parentId: 'b7ad6b7169203331',
|
|
102
|
+
// traceFlags: '01'
|
|
103
|
+
// }
|
|
104
|
+
|
|
105
|
+
// 验证 traceparent 是否有效
|
|
106
|
+
const isValid = TraceContext.isValid('00-...-...-01'); // true or false
|
|
107
|
+
|
|
108
|
+
// 验证 trace ID
|
|
109
|
+
const isValidTraceId = TraceContext.isValidTraceId('0af7651916cd43dd8448eb211c80319c');
|
|
110
|
+
|
|
111
|
+
// 验证 span ID
|
|
112
|
+
const isValidSpanId = TraceContext.isValidSpanId('b7ad6b7169203331');
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### 创建子 Span
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
// 从父 traceparent 创建子 span
|
|
119
|
+
const parentTraceparent = '00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01';
|
|
120
|
+
const childTraceparent = TraceContext.createChild(parentTraceparent);
|
|
121
|
+
|
|
122
|
+
// 子 span 会继承父 span 的 trace ID 和采样决策
|
|
123
|
+
// 但会生成新的 span ID
|
|
124
|
+
|
|
125
|
+
// 覆盖采样决策
|
|
126
|
+
const unsampledChild = TraceContext.createChild(parentTraceparent, false);
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### 检查采样状态
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
// 检查 traceparent 是否已采样
|
|
133
|
+
const sampled = TraceContext.isSampled('00-...-...-01'); // true
|
|
134
|
+
const notSampled = TraceContext.isSampled('00-...-...-00'); // false
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## 完整示例:微服务链路追踪
|
|
138
|
+
|
|
139
|
+
### 服务 A(入口服务)
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
import express from 'express';
|
|
143
|
+
import { NsHttpClient, TraceContext } from '@nstarter/plugin-http-request';
|
|
144
|
+
|
|
145
|
+
const app = express();
|
|
146
|
+
const client = new NsHttpClient();
|
|
147
|
+
|
|
148
|
+
app.get('/api/users/:id', async (req, res) => {
|
|
149
|
+
// 检查是否有上游的 traceparent
|
|
150
|
+
let traceparent = req.headers.traceparent as string;
|
|
151
|
+
|
|
152
|
+
// 如果没有,生成新的
|
|
153
|
+
if (!traceparent || !TraceContext.isValid(traceparent)) {
|
|
154
|
+
traceparent = TraceContext.generate();
|
|
155
|
+
console.log('生成新的 trace:', traceparent);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// 创建子 span 调用服务 B
|
|
159
|
+
const childTraceparent = TraceContext.createChild(traceparent);
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
const userResponse = await client.get(
|
|
163
|
+
`https://service-b.com/users/${req.params.id}`,
|
|
164
|
+
{ traceparent: childTraceparent }
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
res.json(userResponse.data);
|
|
168
|
+
} catch (error) {
|
|
169
|
+
console.error('Error with trace:', traceparent);
|
|
170
|
+
res.status(500).json({ error: 'Internal error' });
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### 服务 B(中间服务)
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
import express from 'express';
|
|
179
|
+
import { NsHttpClient, TraceContext } from '@nstarter/plugin-http-request';
|
|
180
|
+
|
|
181
|
+
const app = express();
|
|
182
|
+
const client = new NsHttpClient();
|
|
183
|
+
|
|
184
|
+
app.get('/users/:id', async (req, res) => {
|
|
185
|
+
// 接收上游的 traceparent
|
|
186
|
+
const incomingTraceparent = req.headers.traceparent as string;
|
|
187
|
+
|
|
188
|
+
if (!TraceContext.isValid(incomingTraceparent)) {
|
|
189
|
+
return res.status(400).json({ error: 'Invalid traceparent' });
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const parsed = TraceContext.parse(incomingTraceparent);
|
|
193
|
+
console.log('处理 trace:', parsed?.traceId);
|
|
194
|
+
|
|
195
|
+
// 创建子 span 调用服务 C
|
|
196
|
+
const childTraceparent = TraceContext.createChild(incomingTraceparent);
|
|
197
|
+
|
|
198
|
+
const ordersResponse = await client.get(
|
|
199
|
+
`https://service-c.com/orders?userId=${req.params.id}`,
|
|
200
|
+
{ traceparent: childTraceparent }
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
res.json({
|
|
204
|
+
user: { id: req.params.id },
|
|
205
|
+
orders: ordersResponse.data
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### 服务 C(下游服务)
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
import express from 'express';
|
|
214
|
+
import { TraceContext } from '@nstarter/plugin-http-request';
|
|
215
|
+
|
|
216
|
+
const app = express();
|
|
217
|
+
|
|
218
|
+
app.get('/orders', async (req, res) => {
|
|
219
|
+
// 接收上游的 traceparent
|
|
220
|
+
const traceparent = req.headers.traceparent as string;
|
|
221
|
+
|
|
222
|
+
if (TraceContext.isValid(traceparent)) {
|
|
223
|
+
const parsed = TraceContext.parse(traceparent);
|
|
224
|
+
console.log('查询订单,trace:', parsed?.traceId);
|
|
225
|
+
console.log('当前 span:', parsed?.parentId);
|
|
226
|
+
console.log('已采样:', TraceContext.isSampled(traceparent));
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// 执行业务逻辑
|
|
230
|
+
const orders = await getOrdersByUserId(req.query.userId);
|
|
231
|
+
|
|
232
|
+
res.json(orders);
|
|
233
|
+
});
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## 与追踪系统集成
|
|
237
|
+
|
|
238
|
+
### 与 OpenTelemetry 集成
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
import { trace } from '@opentelemetry/api';
|
|
242
|
+
import { NsHttpClient, TraceContext } from '@nstarter/plugin-http-request';
|
|
243
|
+
|
|
244
|
+
const tracer = trace.getTracer('my-service');
|
|
245
|
+
const client = new NsHttpClient();
|
|
246
|
+
|
|
247
|
+
async function makeTracedRequest() {
|
|
248
|
+
return tracer.startActiveSpan('http-request', async (span) => {
|
|
249
|
+
// 从 OpenTelemetry span 获取 trace context
|
|
250
|
+
const spanContext = span.spanContext();
|
|
251
|
+
const traceId = spanContext.traceId;
|
|
252
|
+
const spanId = spanContext.spanId;
|
|
253
|
+
const traceFlags = spanContext.traceFlags.toString(16).padStart(2, '0');
|
|
254
|
+
|
|
255
|
+
// 生成 W3C traceparent
|
|
256
|
+
const traceparent = `00-${traceId}-${spanId}-${traceFlags}`;
|
|
257
|
+
|
|
258
|
+
try {
|
|
259
|
+
const response = await client.get('https://api.example.com/data', {
|
|
260
|
+
traceparent
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
264
|
+
return response.data;
|
|
265
|
+
} catch (error) {
|
|
266
|
+
span.setStatus({
|
|
267
|
+
code: SpanStatusCode.ERROR,
|
|
268
|
+
message: error.message
|
|
269
|
+
});
|
|
270
|
+
throw error;
|
|
271
|
+
} finally {
|
|
272
|
+
span.end();
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### 日志关联
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
import { NsHttpClient, TraceContext } from '@nstarter/plugin-http-request';
|
|
282
|
+
|
|
283
|
+
const client = new NsHttpClient();
|
|
284
|
+
|
|
285
|
+
async function makeRequestWithLogging() {
|
|
286
|
+
const traceparent = TraceContext.generate();
|
|
287
|
+
const parsed = TraceContext.parse(traceparent);
|
|
288
|
+
|
|
289
|
+
// 在日志中包含 trace ID,方便日志聚合和查询
|
|
290
|
+
console.log({
|
|
291
|
+
message: 'Making API request',
|
|
292
|
+
traceId: parsed?.traceId,
|
|
293
|
+
spanId: parsed?.parentId,
|
|
294
|
+
timestamp: new Date().toISOString()
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
const response = await client.get('https://api.example.com/data', {
|
|
298
|
+
traceparent
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
console.log({
|
|
302
|
+
message: 'API request completed',
|
|
303
|
+
traceId: parsed?.traceId,
|
|
304
|
+
spanId: parsed?.parentId,
|
|
305
|
+
status: response.status,
|
|
306
|
+
timestamp: new Date().toISOString()
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
return response.data;
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## 最佳实践
|
|
314
|
+
|
|
315
|
+
### 1. 始终验证 traceparent
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
const traceparent = req.headers.traceparent as string;
|
|
319
|
+
|
|
320
|
+
if (traceparent && TraceContext.isValid(traceparent)) {
|
|
321
|
+
// 使用有效的 traceparent
|
|
322
|
+
} else {
|
|
323
|
+
// 生成新的或记录警告
|
|
324
|
+
}
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### 2. 创建子 span 而不是重用
|
|
328
|
+
|
|
329
|
+
```typescript
|
|
330
|
+
// ✅ 正确:创建子 span
|
|
331
|
+
const childTraceparent = TraceContext.createChild(incomingTraceparent);
|
|
332
|
+
await client.get(url, { traceparent: childTraceparent });
|
|
333
|
+
|
|
334
|
+
// ❌ 错误:直接重用父 span
|
|
335
|
+
await client.get(url, { traceparent: incomingTraceparent });
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### 3. 在日志中包含 trace 信息
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
const parsed = TraceContext.parse(traceparent);
|
|
342
|
+
logger.info('Processing request', {
|
|
343
|
+
traceId: parsed?.traceId,
|
|
344
|
+
spanId: parsed?.parentId,
|
|
345
|
+
// 其他日志信息
|
|
346
|
+
});
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### 4. 采样策略
|
|
350
|
+
|
|
351
|
+
```typescript
|
|
352
|
+
// 根据业务需求决定是否采样
|
|
353
|
+
const shouldSample = Math.random() < 0.1; // 10% 采样率
|
|
354
|
+
|
|
355
|
+
const traceparent = TraceContext.generate({
|
|
356
|
+
sampled: shouldSample
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
// 或者基于特定条件
|
|
360
|
+
const traceparent = TraceContext.generate({
|
|
361
|
+
sampled: request.path.includes('/api/critical')
|
|
362
|
+
});
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### 5. 错误处理
|
|
366
|
+
|
|
367
|
+
```typescript
|
|
368
|
+
try {
|
|
369
|
+
await client.get(url, { traceparent });
|
|
370
|
+
} catch (error) {
|
|
371
|
+
// 在错误日志中包含 trace 信息,便于追踪问题
|
|
372
|
+
const parsed = TraceContext.parse(traceparent);
|
|
373
|
+
logger.error('Request failed', {
|
|
374
|
+
error: error.message,
|
|
375
|
+
traceId: parsed?.traceId,
|
|
376
|
+
spanId: parsed?.parentId
|
|
377
|
+
});
|
|
378
|
+
throw error;
|
|
379
|
+
}
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
## 规范参考
|
|
383
|
+
|
|
384
|
+
- [W3C Trace Context](https://www.w3.org/TR/trace-context/)
|
|
385
|
+
- [W3C Trace Context Level 2](https://www.w3.org/TR/trace-context-2/)
|
|
386
|
+
|
|
387
|
+
## API 文档
|
|
388
|
+
|
|
389
|
+
### TraceContext
|
|
390
|
+
|
|
391
|
+
完整的 API 文档:
|
|
392
|
+
|
|
393
|
+
```typescript
|
|
394
|
+
class TraceContext {
|
|
395
|
+
// 生成 traceparent
|
|
396
|
+
static generate(options?: TraceParentOptions): string;
|
|
397
|
+
|
|
398
|
+
// 解析 traceparent
|
|
399
|
+
static parse(traceparent: string): TraceParent | null;
|
|
400
|
+
|
|
401
|
+
// 验证 traceparent
|
|
402
|
+
static isValid(traceparent: string): boolean;
|
|
403
|
+
|
|
404
|
+
// 创建子 span
|
|
405
|
+
static createChild(parentTraceparent: string, sampled?: boolean): string;
|
|
406
|
+
|
|
407
|
+
// 检查是否采样
|
|
408
|
+
static isSampled(traceparent: string): boolean;
|
|
409
|
+
|
|
410
|
+
// 生成 trace ID
|
|
411
|
+
static generateTraceId(): string;
|
|
412
|
+
|
|
413
|
+
// 生成 span ID
|
|
414
|
+
static generateSpanId(): string;
|
|
415
|
+
|
|
416
|
+
// 验证 trace ID
|
|
417
|
+
static isValidTraceId(traceId: string): boolean;
|
|
418
|
+
|
|
419
|
+
// 验证 span ID
|
|
420
|
+
static isValidSpanId(spanId: string): boolean;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
interface TraceParentOptions {
|
|
424
|
+
traceId?: string; // 自定义 trace ID(32位十六进制)
|
|
425
|
+
parentId?: string; // 自定义 parent ID(16位十六进制)
|
|
426
|
+
sampled?: boolean; // 是否采样,默认 true
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
interface TraceParent {
|
|
430
|
+
version: string; // 版本号,当前为 '00'
|
|
431
|
+
traceId: string; // Trace ID(32位十六进制)
|
|
432
|
+
parentId: string; // Parent/Span ID(16位十六进制)
|
|
433
|
+
traceFlags: string; // Trace 标志位(2位十六进制)
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "nstarter-http-request",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "nstarter HTTP 请求客户端插件",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/jiandaoyun/nstarter.git",
|
|
9
|
+
"directory": "packages/plugin-http-request"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"nstarter",
|
|
13
|
+
"plugin",
|
|
14
|
+
"http"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "rimraf dist && tsc --build tsconfig.json && tsc --build tsconfig.esm.json",
|
|
18
|
+
"test": "nyc --nycrc-path ../../.nycrc mocha --config .mocharc.yml",
|
|
19
|
+
"eslint": "eslint --ext .js,.ts --config ../../.eslintrc.js ./src",
|
|
20
|
+
"eslint:fix": "npm run eslint -- --fix",
|
|
21
|
+
"eslint:html": "npm run eslint -- --output-file ./lint/eslint.html --format html",
|
|
22
|
+
"clean": "rimraf dist && rimraf lint && rimraf coverage && rimraf .nyc_output",
|
|
23
|
+
"upload": "npm-publish ./"
|
|
24
|
+
},
|
|
25
|
+
"main": "./dist/cjs/index.js",
|
|
26
|
+
"types": "./dist/types/index.d.ts",
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"default": "./dist/cjs/index.js",
|
|
30
|
+
"types": "./dist/types/index.d.ts",
|
|
31
|
+
"development": "./src/index.ts",
|
|
32
|
+
"import": "./dist/esm/index.js",
|
|
33
|
+
"require": "./dist/cjs/index.js"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"axios": "^1.7.5",
|
|
38
|
+
"https-proxy-agent": "^7.0.6",
|
|
39
|
+
"lru-cache": "^11.2.4",
|
|
40
|
+
"undici": "^6.0.0"
|
|
41
|
+
},
|
|
42
|
+
"peerDependencies": {
|
|
43
|
+
"nstarter-core": ">=1.2.0 <2.0.0"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/node": "^24.10.1",
|
|
47
|
+
"nock": "^14.0.10",
|
|
48
|
+
"nstarter-core": "*"
|
|
49
|
+
}
|
|
50
|
+
}
|