nstarter-http-request 0.1.1 → 0.1.3

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.
Files changed (42) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +9 -12
  3. package/dist/cjs/client.js +10 -15
  4. package/dist/cjs/client.js.map +1 -1
  5. package/dist/cjs/const/config.const.js +2 -2
  6. package/dist/cjs/const/config.const.js.map +1 -1
  7. package/dist/cjs/security/dns.validator.js +13 -0
  8. package/dist/cjs/security/dns.validator.js.map +1 -1
  9. package/dist/cjs/stream/index.js +6 -0
  10. package/dist/cjs/stream/index.js.map +1 -0
  11. package/dist/cjs/stream/metrics.transform.js +30 -0
  12. package/dist/cjs/stream/metrics.transform.js.map +1 -0
  13. package/dist/cjs/tsconfig.tsbuildinfo +1 -1
  14. package/dist/esm/client.js +11 -16
  15. package/dist/esm/client.js.map +1 -1
  16. package/dist/esm/const/config.const.js +2 -2
  17. package/dist/esm/const/config.const.js.map +1 -1
  18. package/dist/esm/security/dns.validator.js +13 -0
  19. package/dist/esm/security/dns.validator.js.map +1 -1
  20. package/dist/esm/stream/index.js +2 -0
  21. package/dist/esm/stream/index.js.map +1 -0
  22. package/dist/esm/stream/metrics.transform.js +26 -0
  23. package/dist/esm/stream/metrics.transform.js.map +1 -0
  24. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  25. package/dist/types/client.d.ts.map +1 -1
  26. package/dist/types/security/dns.validator.d.ts.map +1 -1
  27. package/dist/types/stream/index.d.ts +2 -0
  28. package/dist/types/stream/index.d.ts.map +1 -0
  29. package/dist/types/stream/metrics.transform.d.ts +18 -0
  30. package/dist/types/stream/metrics.transform.d.ts.map +1 -0
  31. package/package.json +1 -1
  32. package/src/client.ts +11 -17
  33. package/src/const/config.const.ts +2 -2
  34. package/src/security/dns.validator.ts +17 -0
  35. package/src/stream/index.ts +2 -0
  36. package/src/stream/metrics.transform.ts +32 -0
  37. package/docs/adapters.md +0 -53
  38. package/docs/configuration.md +0 -149
  39. package/docs/logging.md +0 -70
  40. package/docs/proxy.md +0 -44
  41. package/docs/security.md +0 -56
  42. package/docs/trace-context.md +0 -436
@@ -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
-