nstarter-http-request 0.1.2 → 0.1.4

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 CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ * 0.1.4
4
+ * 升级 `axios` 至 `^1.15.0`,修复 `CVE-2026-40175` header injection gadget chain 风险.
5
+ * 同步修复 `axios` 旧版本的 `NO_PROXY` 主机名规范化绕过导致的 SSRF 风险.
6
+ * 升级 `undici` 至 `^6.25.0`,修复 v6 线 HTTP 与 WebSocket 相关安全风险.
7
+
8
+ * 0.1.3
9
+ * 修复 `dns` 解析在 `ACK` 等带集群后缀的环境会 `ENOTFOUND`.
10
+ * 默认配置关闭 `ip` 与 `dns` 校验.
11
+
3
12
  * 0.1.2
4
13
  * 流处理修复
5
14
 
@@ -8,4 +17,3 @@
8
17
 
9
18
  * 0.1.0
10
19
  * Initial release.
11
-
package/README.md CHANGED
@@ -105,12 +105,12 @@ nx run-many -t test
105
105
  ```typescript
106
106
  import { NsHttpClient, EClientAdapter } from 'nstarter-http-request';
107
107
 
108
- // 创建客户端实例
108
+ // 创建客户端实例(SSRF 防护默认关闭,如需开启请设置 security.enableDnsValidation / enableIpValidation 为 true)
109
109
  const client = new NsHttpClient({
110
110
  adapter: EClientAdapter.axios,
111
111
  security: {
112
- enableDnsValidation: true,
113
- enableIpValidation: true,
112
+ enableDnsValidation: false,
113
+ enableIpValidation: false,
114
114
  allowPrivateIp: false
115
115
  }
116
116
  });
@@ -127,7 +127,7 @@ const user = await client.post('https://api.example.com/users', {
127
127
 
128
128
  ### 完整配置示例
129
129
 
130
- 详细配置说明请参考 [配置文档](./docs/configuration.md)
130
+ 详细配置说明请参考 [使用文档 - 完整配置](https://nstarter-docs.jdydevelop.com/docs/extension/plugins/http-request/configuration)
131
131
 
132
132
  ```typescript
133
133
  import { NsHttpClient, EClientAdapter } from 'nstarter-http-request';
@@ -135,8 +135,8 @@ import { NsHttpClient, EClientAdapter } from 'nstarter-http-request';
135
135
  const client = new NsHttpClient({
136
136
  adapter: EClientAdapter.axios,
137
137
  security: {
138
- enableDnsValidation: true,
139
- enableIpValidation: true,
138
+ enableDnsValidation: false,
139
+ enableIpValidation: false,
140
140
  allowPrivateIp: false
141
141
  },
142
142
  logging: {
@@ -151,10 +151,7 @@ const client = new NsHttpClient({
151
151
 
152
152
  ## 详细文档
153
153
 
154
- - [完整配置示例](./docs/configuration.md) - 所有配置选项的详细说明
155
- - [安全配置](./docs/security.md) - SSRF 防护、SSL 验证、域名黑白名单等安全特性
156
- - [日志配置](./docs/logging.md) - 日志记录、过滤、自定义日志器等
157
- - [正向代理配置](./docs/proxy.md) - HTTP/HTTPS 正向代理和 no_proxy 配置
158
- - [适配器切换](./docs/adapters.md) - Axios 和 Undici 适配器的选择与切换
159
- - [W3C Trace Context 支持](./docs/trace-context.md) - 分布式链路追踪支持
154
+ 完整配置、安全、日志、代理、适配器、W3C Trace Context 等说明已统一放在使用文档站点,请查看:
155
+
156
+ - [nstarter-http-request 插件文档](https://nstarter-docs.jdydevelop.com/docs/extension/plugins/http-request/)
160
157
 
@@ -13,8 +13,8 @@ exports.MAX_DNS_CACHE_TTL = 5 * 60 * 1000; // 300000ms (5 分钟)
13
13
  */
14
14
  exports.DEFAULT_SECURITY_CONFIG = {
15
15
  rejectUnauthorized: true,
16
- enableDnsValidation: true,
17
- enableIpValidation: true,
16
+ enableDnsValidation: false,
17
+ enableIpValidation: false,
18
18
  allowPrivateIp: false,
19
19
  whitelist: [],
20
20
  blacklist: [],
@@ -1 +1 @@
1
- {"version":3,"file":"config.const.js","sourceRoot":"","sources":["../../../src/const/config.const.ts"],"names":[],"mappings":";;;AAMA,6CAA8C;AAC9C,2CAAgF;AAEhF;;;GAGG;AACU,QAAA,iBAAiB,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,mBAAmB;AAEnE;;GAEG;AACU,QAAA,uBAAuB,GAAmB;IACnD,kBAAkB,EAAE,IAAI;IACxB,mBAAmB,EAAE,IAAI;IACzB,kBAAkB,EAAE,IAAI;IACxB,cAAc,EAAE,KAAK;IACrB,SAAS,EAAE,EAAE;IACb,SAAS,EAAE,EAAE;IACb,WAAW,EAAE,iCAAqB;IAClC,eAAe,EAAE,sCAA0B;CAC9C,CAAC;AAEF;;GAEG;AACU,QAAA,sBAAsB,GAAkB;IACjD,OAAO,EAAE,IAAI;IACb,cAAc,EAAE,IAAI;IACpB,eAAe,EAAE,IAAI;IACrB,aAAa,EAAE,IAAI,GAAG,IAAI,CAAC,MAAM;CACpC,CAAC;AAEF;;GAEG;AACU,QAAA,sBAAsB,GAAkB;IACjD,OAAO,EAAE,KAAK,EAAE,OAAO;IACvB,OAAO,EAAE,CAAC;IACV,UAAU,EAAE,IAAI,EAAE,MAAM;IACxB,YAAY,EAAE,CAAC;IACf,kBAAkB,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,OAAO;IAC7C,mBAAmB,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC,OAAO;CAChD,CAAC;AAEF;;GAEG;AACU,QAAA,0BAA0B,GAAqB;IACxD,OAAO,EAAE,2BAAc,CAAC,KAAK;IAC7B,QAAQ,EAAE,+BAAuB;IACjC,OAAO,EAAE,8BAAsB;IAC/B,OAAO,EAAE,8BAAsB;CAClC,CAAC"}
1
+ {"version":3,"file":"config.const.js","sourceRoot":"","sources":["../../../src/const/config.const.ts"],"names":[],"mappings":";;;AAMA,6CAA8C;AAC9C,2CAAgF;AAEhF;;;GAGG;AACU,QAAA,iBAAiB,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,mBAAmB;AAEnE;;GAEG;AACU,QAAA,uBAAuB,GAAmB;IACnD,kBAAkB,EAAE,IAAI;IACxB,mBAAmB,EAAE,KAAK;IAC1B,kBAAkB,EAAE,KAAK;IACzB,cAAc,EAAE,KAAK;IACrB,SAAS,EAAE,EAAE;IACb,SAAS,EAAE,EAAE;IACb,WAAW,EAAE,iCAAqB;IAClC,eAAe,EAAE,sCAA0B;CAC9C,CAAC;AAEF;;GAEG;AACU,QAAA,sBAAsB,GAAkB;IACjD,OAAO,EAAE,IAAI;IACb,cAAc,EAAE,IAAI;IACpB,eAAe,EAAE,IAAI;IACrB,aAAa,EAAE,IAAI,GAAG,IAAI,CAAC,MAAM;CACpC,CAAC;AAEF;;GAEG;AACU,QAAA,sBAAsB,GAAkB;IACjD,OAAO,EAAE,KAAK,EAAE,OAAO;IACvB,OAAO,EAAE,CAAC;IACV,UAAU,EAAE,IAAI,EAAE,MAAM;IACxB,YAAY,EAAE,CAAC;IACf,kBAAkB,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,OAAO;IAC7C,mBAAmB,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC,OAAO;CAChD,CAAC;AAEF;;GAEG;AACU,QAAA,0BAA0B,GAAqB;IACxD,OAAO,EAAE,2BAAc,CAAC,KAAK;IAC7B,QAAQ,EAAE,+BAAuB;IACjC,OAAO,EAAE,8BAAsB;IAC/B,OAAO,EAAE,8BAAsB;CAClC,CAAC"}
@@ -55,6 +55,19 @@ class DnsValidator {
55
55
  catch {
56
56
  // IPv6 解析失败
57
57
  }
58
+ // resolve4/resolve6 不拼接 search 域,在 ACK 等带集群后缀的环境会 ENOTFOUND;
59
+ // 失败时回退到 lookup(走 getaddrinfo,会按 resolv.conf 拼 search),与建连时行为一致
60
+ if (addresses.length === 0) {
61
+ try {
62
+ const lookupResult = await utils_1.CommonUtils.withTimeout(node_dns_1.promises.lookup(hostname, { all: true }), this.resolveTimeout, 'DNS lookup timed out');
63
+ for (const { address } of lookupResult) {
64
+ addresses.push(address);
65
+ }
66
+ }
67
+ catch {
68
+ // lookup 也失败,下面统一抛错
69
+ }
70
+ }
58
71
  if (addresses.length === 0) {
59
72
  throw new Error(`Unable to resolve hostname: ${hostname}`);
60
73
  }
@@ -1 +1 @@
1
- {"version":3,"file":"dns.validator.js","sourceRoot":"","sources":["../../../src/security/dns.validator.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"dns.validator.js","sourceRoot":"","sources":["../../../src/security/dns.validator.ts"],"names":[],"mappings":"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"}
@@ -10,8 +10,8 @@ export const MAX_DNS_CACHE_TTL = 5 * 60 * 1000; // 300000ms (5 分钟)
10
10
  */
11
11
  export const DEFAULT_SECURITY_CONFIG = {
12
12
  rejectUnauthorized: true,
13
- enableDnsValidation: true,
14
- enableIpValidation: true,
13
+ enableDnsValidation: false,
14
+ enableIpValidation: false,
15
15
  allowPrivateIp: false,
16
16
  whitelist: [],
17
17
  blacklist: [],
@@ -1 +1 @@
1
- {"version":3,"file":"config.const.js","sourceRoot":"","sources":["../../../src/const/config.const.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,qBAAqB,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC;AAEhF;;;GAGG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,mBAAmB;AAEnE;;GAEG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAmB;IACnD,kBAAkB,EAAE,IAAI;IACxB,mBAAmB,EAAE,IAAI;IACzB,kBAAkB,EAAE,IAAI;IACxB,cAAc,EAAE,KAAK;IACrB,SAAS,EAAE,EAAE;IACb,SAAS,EAAE,EAAE;IACb,WAAW,EAAE,qBAAqB;IAClC,eAAe,EAAE,0BAA0B;CAC9C,CAAC;AAEF;;GAEG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAkB;IACjD,OAAO,EAAE,IAAI;IACb,cAAc,EAAE,IAAI;IACpB,eAAe,EAAE,IAAI;IACrB,aAAa,EAAE,IAAI,GAAG,IAAI,CAAC,MAAM;CACpC,CAAC;AAEF;;GAEG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAkB;IACjD,OAAO,EAAE,KAAK,EAAE,OAAO;IACvB,OAAO,EAAE,CAAC;IACV,UAAU,EAAE,IAAI,EAAE,MAAM;IACxB,YAAY,EAAE,CAAC;IACf,kBAAkB,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,OAAO;IAC7C,mBAAmB,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC,OAAO;CAChD,CAAC;AAEF;;GAEG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAqB;IACxD,OAAO,EAAE,cAAc,CAAC,KAAK;IAC7B,QAAQ,EAAE,uBAAuB;IACjC,OAAO,EAAE,sBAAsB;IAC/B,OAAO,EAAE,sBAAsB;CAClC,CAAC"}
1
+ {"version":3,"file":"config.const.js","sourceRoot":"","sources":["../../../src/const/config.const.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,qBAAqB,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC;AAEhF;;;GAGG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,mBAAmB;AAEnE;;GAEG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAmB;IACnD,kBAAkB,EAAE,IAAI;IACxB,mBAAmB,EAAE,KAAK;IAC1B,kBAAkB,EAAE,KAAK;IACzB,cAAc,EAAE,KAAK;IACrB,SAAS,EAAE,EAAE;IACb,SAAS,EAAE,EAAE;IACb,WAAW,EAAE,qBAAqB;IAClC,eAAe,EAAE,0BAA0B;CAC9C,CAAC;AAEF;;GAEG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAkB;IACjD,OAAO,EAAE,IAAI;IACb,cAAc,EAAE,IAAI;IACpB,eAAe,EAAE,IAAI;IACrB,aAAa,EAAE,IAAI,GAAG,IAAI,CAAC,MAAM;CACpC,CAAC;AAEF;;GAEG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAkB;IACjD,OAAO,EAAE,KAAK,EAAE,OAAO;IACvB,OAAO,EAAE,CAAC;IACV,UAAU,EAAE,IAAI,EAAE,MAAM;IACxB,YAAY,EAAE,CAAC;IACf,kBAAkB,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,OAAO;IAC7C,mBAAmB,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC,OAAO;CAChD,CAAC;AAEF;;GAEG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAqB;IACxD,OAAO,EAAE,cAAc,CAAC,KAAK;IAC7B,QAAQ,EAAE,uBAAuB;IACjC,OAAO,EAAE,sBAAsB;IAC/B,OAAO,EAAE,sBAAsB;CAClC,CAAC"}
@@ -52,6 +52,19 @@ export class DnsValidator {
52
52
  catch {
53
53
  // IPv6 解析失败
54
54
  }
55
+ // resolve4/resolve6 不拼接 search 域,在 ACK 等带集群后缀的环境会 ENOTFOUND;
56
+ // 失败时回退到 lookup(走 getaddrinfo,会按 resolv.conf 拼 search),与建连时行为一致
57
+ if (addresses.length === 0) {
58
+ try {
59
+ const lookupResult = await CommonUtils.withTimeout(dns.lookup(hostname, { all: true }), this.resolveTimeout, 'DNS lookup timed out');
60
+ for (const { address } of lookupResult) {
61
+ addresses.push(address);
62
+ }
63
+ }
64
+ catch {
65
+ // lookup 也失败,下面统一抛错
66
+ }
67
+ }
55
68
  if (addresses.length === 0) {
56
69
  throw new Error(`Unable to resolve hostname: ${hostname}`);
57
70
  }
@@ -1 +1 @@
1
- {"version":3,"file":"dns.validator.js","sourceRoot":"","sources":["../../../src/security/dns.validator.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"dns.validator.js","sourceRoot":"","sources":["../../../src/security/dns.validator.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"dns.validator.d.ts","sourceRoot":"","sources":["../../../src/security/dns.validator.ts"],"names":[],"mappings":"AAAA;;GAEG;AAUH,OAAO,KAAK,EACR,SAAS,EACZ,MAAM,UAAU,CAAC;AAIlB;;GAEG;AACH,qBAAa,YAAY;IACrB,OAAO,CAAC,KAAK,CAA8B;IAC3C,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,cAAc,CAAS;gBAG3B,QAAQ,SAAwB,EAChC,cAAc,SAA8B,EAC5C,YAAY,SAA6B;IAgB7C;;;;OAIG;IACU,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;IAgD1D;;;;;OAKG;IACU,kBAAkB,CAC3B,QAAQ,EAAE,MAAM,EAChB,cAAc,UAAQ,GACvB,OAAO,CAAC;QAAE,SAAS,EAAE,SAAS,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAC;QAAC,UAAU,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAe7E;;OAEG;IACI,UAAU,IAAI,IAAI;IAIzB;;;OAGG;IACI,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAI5C;;;OAGG;IACI,YAAY,IAAI,MAAM;IAI7B;;;OAGG;IACI,iBAAiB,IAAI,IAAI;IAIhC;;;OAGG;IACI,eAAe,IAAI,MAAM;IAIhC;;;;OAIG;IACI,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;CAG7C"}
1
+ {"version":3,"file":"dns.validator.d.ts","sourceRoot":"","sources":["../../../src/security/dns.validator.ts"],"names":[],"mappings":"AAAA;;GAEG;AAUH,OAAO,KAAK,EACR,SAAS,EACZ,MAAM,UAAU,CAAC;AAIlB;;GAEG;AACH,qBAAa,YAAY;IACrB,OAAO,CAAC,KAAK,CAA8B;IAC3C,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,cAAc,CAAS;gBAG3B,QAAQ,SAAwB,EAChC,cAAc,SAA8B,EAC5C,YAAY,SAA6B;IAgB7C;;;;OAIG;IACU,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;IAiE1D;;;;;OAKG;IACU,kBAAkB,CAC3B,QAAQ,EAAE,MAAM,EAChB,cAAc,UAAQ,GACvB,OAAO,CAAC;QAAE,SAAS,EAAE,SAAS,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAC;QAAC,UAAU,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAe7E;;OAEG;IACI,UAAU,IAAI,IAAI;IAIzB;;;OAGG;IACI,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAI5C;;;OAGG;IACI,YAAY,IAAI,MAAM;IAI7B;;;OAGG;IACI,iBAAiB,IAAI,IAAI;IAIhC;;;OAGG;IACI,eAAe,IAAI,MAAM;IAIhC;;;;OAIG;IACI,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;CAG7C"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nstarter-http-request",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "nstarter HTTP 请求客户端插件",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -34,10 +34,10 @@
34
34
  }
35
35
  },
36
36
  "dependencies": {
37
- "axios": "^1.7.5",
37
+ "axios": "^1.15.0",
38
38
  "https-proxy-agent": "^7.0.6",
39
39
  "lru-cache": "^11.2.4",
40
- "undici": "^6.0.0"
40
+ "undici": "^6.25.0"
41
41
  },
42
42
  "peerDependencies": {
43
43
  "nstarter-core": ">=1.2.0 <2.0.0"
@@ -18,8 +18,8 @@ export const MAX_DNS_CACHE_TTL = 5 * 60 * 1000; // 300000ms (5 分钟)
18
18
  */
19
19
  export const DEFAULT_SECURITY_CONFIG: SecurityConfig = {
20
20
  rejectUnauthorized: true,
21
- enableDnsValidation: true,
22
- enableIpValidation: true,
21
+ enableDnsValidation: false,
22
+ enableIpValidation: false,
23
23
  allowPrivateIp: false,
24
24
  whitelist: [],
25
25
  blacklist: [],
@@ -73,6 +73,23 @@ export class DnsValidator {
73
73
  // IPv6 解析失败
74
74
  }
75
75
 
76
+ // resolve4/resolve6 不拼接 search 域,在 ACK 等带集群后缀的环境会 ENOTFOUND;
77
+ // 失败时回退到 lookup(走 getaddrinfo,会按 resolv.conf 拼 search),与建连时行为一致
78
+ if (addresses.length === 0) {
79
+ try {
80
+ const lookupResult = await CommonUtils.withTimeout(
81
+ dns.lookup(hostname, { all: true }),
82
+ this.resolveTimeout,
83
+ 'DNS lookup timed out'
84
+ );
85
+ for (const { address } of lookupResult) {
86
+ addresses.push(address);
87
+ }
88
+ } catch {
89
+ // lookup 也失败,下面统一抛错
90
+ }
91
+ }
92
+
76
93
  if (addresses.length === 0) {
77
94
  throw new Error(`Unable to resolve hostname: ${ hostname }`);
78
95
  }
package/docs/adapters.md DELETED
@@ -1,53 +0,0 @@
1
- # 适配器切换
2
-
3
- ## 支持的适配器
4
-
5
- 本库支持多种 HTTP 客户端适配器,你可以根据场景选择最适合的:
6
-
7
- - **Axios** - 适合通用场景,功能丰富,生态完善
8
- - **Undici** - Node.js 原生高性能 HTTP 客户端,适合高并发场景
9
-
10
- ## 切换适配器
11
-
12
- ```typescript
13
- import { EClientAdapter } from 'nstarter-http-request';
14
-
15
- // 使用 Axios 适配器(适合通用场景)
16
- const axiosClient = new NsHttpClient().withAdapter(EClientAdapter.axios);
17
-
18
- // 使用 Undici 适配器(高性能,Node.js 原生)
19
- const undiciClient = new NsHttpClient().withAdapter(EClientAdapter.undici);
20
- ```
21
-
22
- ## 在初始化时指定适配器
23
-
24
- ```typescript
25
- import { NsHttpClient, EClientAdapter } from 'nstarter-http-request';
26
-
27
- // 创建客户端实例时指定适配器
28
- const client = new NsHttpClient({
29
- adapter: EClientAdapter.axios,
30
- security: {
31
- enableDnsValidation: true,
32
- enableIpValidation: true,
33
- allowPrivateIp: false
34
- }
35
- });
36
- ```
37
-
38
- ## 适配器选择建议
39
-
40
- ### 使用 Axios 的场景
41
-
42
- - 需要丰富的配置选项
43
- - 需要拦截器功能
44
- - 需要广泛的浏览器兼容性(通过 polyfill)
45
- - 团队熟悉 Axios API
46
-
47
- ### 使用 Undici 的场景
48
-
49
- - 需要极致性能
50
- - 高并发场景
51
- - 纯 Node.js 环境
52
- - 希望减少依赖体积
53
-
@@ -1,149 +0,0 @@
1
- # 完整配置示例
2
-
3
- 以下是一个包含所有配置选项的完整示例:
4
-
5
- ```typescript
6
- import { NsHttpClient, EClientAdapter } from 'nstarter-http-request';
7
-
8
- const client = new NsHttpClient({
9
- // 适配器配置
10
- adapter: EClientAdapter.axios,
11
-
12
- // 安全配置
13
- security: {
14
- rejectUnauthorized: true, // 是否验证 SSL 证书(生产环境建议 true)
15
- enableDnsValidation: true, // 开启 DNS 验证
16
- enableIpValidation: true, // 开启 IP 验证
17
- allowPrivateIp: false, // 是否允许访问私有 IP(内网地址)
18
- whitelist: [ // 域名白名单
19
- 'trusted.com', '*.trusted.com', // 匹配根域名和一级子域名
20
- 'api.example.com' // 仅匹配该域名
21
- ],
22
- blacklist: [ // 域名黑名单
23
- 'malicious.com', '*.malicious.com', // 根域名和一级子域名
24
- 'unsafe.net'
25
- ],
26
- dnsCacheTtl: 60000, // DNS 缓存 TTL(毫秒,默认 60 秒)
27
- dnsCacheMaxSize: 500 // DNS 缓存最大条目数(默认 500,使用 LRU 淘汰策略)
28
- },
29
-
30
- // 日志配置
31
- logging: {
32
- enabled: true, // 是否启用日志
33
- logRequestBody: true, // 是否记录请求体
34
- logResponseBody: true, // 是否记录响应体
35
- bodyMaxLength: 1024 * 1024, // 请求/响应体最大记录长度(字节,默认 1MB)
36
- logger: customLogger, // 自定义日志记录器(可选,实现 ILogger 接口)
37
- httpLogger: customHttpLogger, // 自定义 HTTP 日志记录器(可选,实现 IHttpClientLogger 接口)
38
- filter: { // 日志过滤配置
39
- methods: ['POST', 'PUT'], // 只记录指定 HTTP 方法
40
- urlPatterns: [ // 只记录匹配的 URL(支持正则)
41
- /\/api\//,
42
- 'https://api.example.com'
43
- ],
44
- scenarios: ['payment'], // 只记录指定业务场景
45
- statusCodes: [404, 500], // 只记录指定状态码
46
- statusCodeRanges: [ // 只记录指定状态码范围
47
- { min: 400, max: 499 }, // 客户端错误
48
- { min: 500, max: 599 } // 服务端错误
49
- ],
50
- onlyFailed: false, // 是否只记录失败请求
51
- onlySuccess: false, // 是否只记录成功请求
52
- minDuration: 100, // 只记录耗时超过指定毫秒的请求
53
- customFilter: (options) => { // 自定义过滤函数
54
- return options.request.method === 'POST';
55
- }
56
- }
57
- },
58
-
59
- // 请求配置
60
- request: {
61
- timeout: 30000, // 请求超时(毫秒,默认 30 秒)
62
- retries: 3, // 重试次数(默认 0)
63
- retryDelay: 1000, // 重试延迟(毫秒,默认 1 秒)
64
- maxRedirects: 5, // 最大重定向次数(默认 5)
65
- maxRequestBodySize: 10 * 1024 * 1024, // 最大请求体大小(字节,默认 10MB)
66
- maxResponseBodySize: 50 * 1024 * 1024, // 最大响应体大小(字节,默认 50MB)
67
- proxy: { // HTTP 代理配置(可选,不同于正向代理)
68
- host: 'proxy.example.com',
69
- port: 8080,
70
- protocol: 'http', // 'http' | 'https'
71
- auth: { // 代理认证(可选)
72
- username: 'user',
73
- password: 'pass'
74
- }
75
- }
76
- }
77
- });
78
- ```
79
-
80
- ## 配置说明
81
-
82
- ### 适配器配置
83
-
84
- - `adapter`: 指定使用的 HTTP 客户端适配器,可选值:`EClientAdapter.axios` 或 `EClientAdapter.undici`
85
-
86
- ### 安全配置
87
-
88
- 详细说明请参考 [安全配置文档](./security.md)
89
-
90
- ### 日志配置
91
-
92
- 详细说明请参考 [日志配置文档](./logging.md)
93
-
94
- ### 请求配置
95
-
96
- - `timeout`: 请求超时时间(毫秒)
97
- - `retries`: 请求失败时的重试次数
98
- - `retryDelay`: 重试延迟时间(毫秒)
99
- - `maxRedirects`: 最大重定向次数
100
- - `maxRequestBodySize`: 最大请求体大小限制(字节)
101
- - `maxResponseBodySize`: 最大响应体大小限制(字节)
102
- - `proxy`: HTTP 代理配置(注意:这与正向代理不同,详见 [代理配置文档](./proxy.md))
103
-
104
- ## 默认配置值
105
-
106
- 以下是所有配置项的默认值,如果你不指定某个配置项,将使用以下默认值:
107
-
108
- ### 适配器配置
109
-
110
- | 配置项 | 默认值 | 说明 |
111
- |--------|--------|------|
112
- | `adapter` | `EClientAdapter.axios` | 默认使用 Axios 适配器 |
113
-
114
- ### 安全配置
115
-
116
- | 配置项 | 默认值 | 说明 |
117
- |--------|--------|------|
118
- | `rejectUnauthorized` | `true` | 默认验证 SSL 证书(生产环境建议保持为 true) |
119
- | `enableDnsValidation` | `true` | 默认启用 DNS 验证 |
120
- | `enableIpValidation` | `true` | 默认启用 IP 验证 |
121
- | `allowPrivateIp` | `false` | 默认禁止访问私有 IP(内网地址) |
122
- | `whitelist` | `[]` | 默认无白名单 |
123
- | `blacklist` | `[]` | 默认无黑名单 |
124
- | `dnsCacheTtl` | `60000` | DNS 缓存 TTL,默认 60 秒(60000 毫秒) |
125
- | `dnsCacheMaxSize` | `500` | DNS 缓存最大条目数,默认 500,使用 LRU 淘汰策略 |
126
-
127
- > **注意**:DNS 缓存最大 TTL 限制为 5 分钟(300000 毫秒),即使设置更大的值也会被限制为 5 分钟,避免过长的缓存时间导致内存积压。
128
-
129
- ### 日志配置
130
-
131
- | 配置项 | 默认值 | 说明 |
132
- |--------|--------|------|
133
- | `enabled` | `true` | 默认启用日志 |
134
- | `logRequestBody` | `true` | 默认记录请求体 |
135
- | `logResponseBody` | `true` | 默认记录响应体 |
136
- | `bodyMaxLength` | `1048576` | 请求/响应体最大记录长度,默认 1MB(1024 * 1024 字节) |
137
-
138
- ### 请求配置
139
-
140
- | 配置项 | 默认值 | 说明 |
141
- |--------|--------|------|
142
- | `timeout` | `30000` | 请求超时时间,默认 30 秒(30000 毫秒) |
143
- | `retries` | `0` | 默认不重试 |
144
- | `retryDelay` | `1000` | 重试延迟时间,默认 1 秒(1000 毫秒) |
145
- | `maxRedirects` | `5` | 最大重定向次数,默认 5 次 |
146
- | `maxRequestBodySize` | `10485760` | 最大请求体大小,默认 10MB(10 * 1024 * 1024 字节) |
147
- | `maxResponseBodySize` | `52428800` | 最大响应体大小,默认 50MB(50 * 1024 * 1024 字节) |
148
- | `proxy` | `undefined` | 默认不使用 HTTP 代理 |
149
-
package/docs/logging.md DELETED
@@ -1,70 +0,0 @@
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 DELETED
@@ -1,44 +0,0 @@
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 DELETED
@@ -1,56 +0,0 @@
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
-
@@ -1,436 +0,0 @@
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
-