Skip to content

Commit 2d7e2f7

Browse files
committed
feat: add request body diagnostic channels
1 parent f184975 commit 2d7e2f7

File tree

8 files changed

+115
-3
lines changed

8 files changed

+115
-3
lines changed

docs/docs/api/DiagnosticsChannel.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,22 @@ diagnosticsChannel.channel('undici:request:create').subscribe(({ request }) => {
2727

2828
Note: a request is only loosely completed to a given socket.
2929

30+
## `undici:request:bodyChunkSent`
31+
32+
This message is published when a chunk of the request body is being sent.
33+
34+
```js
35+
import diagnosticsChannel from 'diagnostics_channel'
36+
37+
diagnosticsChannel.channel('undici:request:bodyChunkSent').subscribe(({ request, chunk }) => {
38+
// request is the same object undici:request:create
39+
})
40+
```
3041

3142
## `undici:request:bodySent`
3243

44+
This message is published after the request body has been fully sent.
45+
3346
```js
3447
import diagnosticsChannel from 'diagnostics_channel'
3548

@@ -54,6 +67,18 @@ diagnosticsChannel.channel('undici:request:headers').subscribe(({ request, respo
5467
})
5568
```
5669

70+
## `undici:request:bodyChunkReceived`
71+
72+
This message is published after a chunk of the response body has been received.
73+
74+
```js
75+
import diagnosticsChannel from 'diagnostics_channel'
76+
77+
diagnosticsChannel.channel('undici:request:bodyChunkReceived').subscribe(({ request, chunk }) => {
78+
// request is the same object undici:request:create
79+
})
80+
```
81+
5782
## `undici:request:trailers`
5883

5984
This message is published after the response body and trailers have been received, i.e. the response has been completed.

lib/core/diagnostics.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ const channels = {
1616
// Request
1717
create: diagnosticsChannel.channel('undici:request:create'),
1818
bodySent: diagnosticsChannel.channel('undici:request:bodySent'),
19+
bodyChunkSent: diagnosticsChannel.channel('undici:request:bodyChunkSent'),
20+
bodyChunkReceived: diagnosticsChannel.channel('undici:request:bodyChunkReceived'),
1921
headers: diagnosticsChannel.channel('undici:request:headers'),
2022
trailers: diagnosticsChannel.channel('undici:request:trailers'),
2123
error: diagnosticsChannel.channel('undici:request:error'),

lib/core/request.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,9 @@ class Request {
194194
}
195195

196196
onBodySent (chunk) {
197+
if (channels.bodyChunkSent.hasSubscribers) {
198+
channels.bodyChunkSent.publish({ request: this, chunk })
199+
}
197200
if (this[kHandler].onBodySent) {
198201
try {
199202
return this[kHandler].onBodySent(chunk)
@@ -252,6 +255,9 @@ class Request {
252255
assert(!this.aborted)
253256
assert(!this.completed)
254257

258+
if (channels.bodyChunkReceived.hasSubscribers) {
259+
channels.bodyChunkReceived.publish({ request: this, chunk })
260+
}
255261
try {
256262
return this[kHandler].onData(chunk)
257263
} catch (err) {

test/node-test/diagnostics-channel/get.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const { Client } = require('../../..')
77
const { createServer } = require('node:http')
88

99
test('Diagnostics channel - get', (t) => {
10-
const assert = tspl(t, { plan: 32 })
10+
const assert = tspl(t, { plan: 36 })
1111
const server = createServer({ joinDuplicateHeaders: true }, (req, res) => {
1212
res.setHeader('Content-Type', 'text/plain')
1313
res.setHeader('trailer', 'foo')
@@ -105,16 +105,36 @@ test('Diagnostics channel - get', (t) => {
105105
assert.equal(response.statusText, 'OK')
106106
})
107107

108+
let bodySent = false
109+
diagnosticsChannel.channel('undici:request:bodySent').subscribe(({ request }) => {
110+
assert.equal(_req, request)
111+
bodySent = true
112+
})
113+
diagnosticsChannel.channel('undici:request:bodyChunkSent').subscribe(() => {
114+
assert.fail('should not emit undici:request:bodyChunkSent for GET requests')
115+
})
116+
108117
let endEmitted = false
109118

110119
return new Promise((resolve) => {
120+
const respChunks = []
121+
diagnosticsChannel.channel('undici:request:bodyChunkReceived').subscribe(({ request, chunk }) => {
122+
assert.equal(_req, request)
123+
respChunks.push(chunk)
124+
})
125+
111126
diagnosticsChannel.channel('undici:request:trailers').subscribe(({ request, trailers }) => {
127+
assert.equal(bodySent, true)
112128
assert.equal(request.completed, true)
113129
assert.equal(_req, request)
114130
// This event is emitted after the last chunk has been added to the body stream,
115131
// not when it was consumed by the application
116132
assert.equal(endEmitted, false)
117133
assert.deepStrictEqual(trailers, [Buffer.from('foo'), Buffer.from('oof')])
134+
135+
const respData = Buffer.concat(respChunks)
136+
assert.deepStrictEqual(respData, Buffer.from('hello'))
137+
118138
resolve()
119139
})
120140

test/node-test/diagnostics-channel/post-stream.js

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const { Client } = require('../../..')
88
const { createServer } = require('node:http')
99

1010
test('Diagnostics channel - post stream', (t) => {
11-
const assert = tspl(t, { plan: 33 })
11+
const assert = tspl(t, { plan: 43 })
1212
const server = createServer({ joinDuplicateHeaders: true }, (req, res) => {
1313
req.resume()
1414
res.setHeader('Content-Type', 'text/plain')
@@ -107,20 +107,43 @@ test('Diagnostics channel - post stream', (t) => {
107107
assert.equal(response.statusText, 'OK')
108108
})
109109

110+
let bodySent = false
111+
const bodyChunks = []
112+
diagnosticsChannel.channel('undici:request:bodyChunkSent').subscribe(({ request, chunk }) => {
113+
assert.equal(_req, request)
114+
// Chunk can be a string or a Buffer, depending on the stream writer.
115+
assert.equal(typeof chunk, 'string')
116+
bodyChunks.push(Buffer.from(chunk))
117+
})
110118
diagnosticsChannel.channel('undici:request:bodySent').subscribe(({ request }) => {
111119
assert.equal(_req, request)
120+
bodySent = true
121+
122+
const requestBody = Buffer.concat(bodyChunks)
123+
assert.deepStrictEqual(requestBody, Buffer.from('hello world'))
112124
})
113125

114126
let endEmitted = false
115127

116128
return new Promise((resolve) => {
129+
const respChunks = []
130+
diagnosticsChannel.channel('undici:request:bodyChunkReceived').subscribe(({ request, chunk }) => {
131+
assert.equal(_req, request)
132+
respChunks.push(chunk)
133+
})
134+
117135
diagnosticsChannel.channel('undici:request:trailers').subscribe(({ request, trailers }) => {
136+
assert.equal(bodySent, true)
118137
assert.equal(request.completed, true)
119138
assert.equal(_req, request)
120139
// This event is emitted after the last chunk has been added to the body stream,
121140
// not when it was consumed by the application
122141
assert.equal(endEmitted, false)
123142
assert.deepStrictEqual(trailers, [Buffer.from('foo'), Buffer.from('oof')])
143+
144+
const respData = Buffer.concat(respChunks)
145+
assert.deepStrictEqual(respData, Buffer.from('hello'))
146+
124147
resolve()
125148
})
126149

test/node-test/diagnostics-channel/post.js

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const { Client } = require('../../../')
77
const { createServer } = require('node:http')
88

99
test('Diagnostics channel - post', (t) => {
10-
const assert = tspl(t, { plan: 33 })
10+
const assert = tspl(t, { plan: 39 })
1111
const server = createServer({ joinDuplicateHeaders: true }, (req, res) => {
1212
req.resume()
1313
res.setHeader('Content-Type', 'text/plain')
@@ -105,20 +105,42 @@ test('Diagnostics channel - post', (t) => {
105105
assert.equal(response.statusText, 'OK')
106106
})
107107

108+
let bodySent = false
109+
const bodyChunks = []
110+
diagnosticsChannel.channel('undici:request:bodyChunkSent').subscribe(({ request, chunk }) => {
111+
assert.equal(_req, request)
112+
assert.equal(Buffer.isBuffer(chunk), true)
113+
bodyChunks.push(chunk)
114+
})
108115
diagnosticsChannel.channel('undici:request:bodySent').subscribe(({ request }) => {
109116
assert.equal(_req, request)
117+
bodySent = true
118+
119+
const requestBody = Buffer.concat(bodyChunks)
120+
assert.deepStrictEqual(requestBody, Buffer.from('hello world'))
110121
})
111122

112123
let endEmitted = false
113124

114125
return new Promise((resolve) => {
126+
const respChunks = []
127+
diagnosticsChannel.channel('undici:request:bodyChunkReceived').subscribe(({ request, chunk }) => {
128+
assert.equal(_req, request)
129+
respChunks.push(chunk)
130+
})
131+
115132
diagnosticsChannel.channel('undici:request:trailers').subscribe(({ request, trailers }) => {
133+
assert.equal(bodySent, true)
116134
assert.equal(request.completed, true)
117135
assert.equal(_req, request)
118136
// This event is emitted after the last chunk has been added to the body stream,
119137
// not when it was consumed by the application
120138
assert.equal(endEmitted, false)
121139
assert.deepStrictEqual(trailers, [Buffer.from('foo'), Buffer.from('oof')])
140+
141+
const respData = Buffer.concat(respChunks)
142+
assert.deepStrictEqual(respData, Buffer.from('hello'))
143+
122144
resolve()
123145
})
124146

test/types/diagnostics-channel.test-d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,15 @@ const connectParams = {
2828
}
2929

3030
expectAssignable<DiagnosticsChannel.RequestCreateMessage>({ request })
31+
expectAssignable<DiagnosticsChannel.RequestBodyChunkSentMessage>({ request, chunk: Buffer.from('') })
32+
expectAssignable<DiagnosticsChannel.RequestBodyChunkSentMessage>({ request, chunk: '' })
33+
expectAssignable<DiagnosticsChannel.RequestBodyChunkSentMessage>({ request, chunk: new ArrayBuffer(8) })
3134
expectAssignable<DiagnosticsChannel.RequestBodySentMessage>({ request })
3235
expectAssignable<DiagnosticsChannel.RequestHeadersMessage>({
3336
request,
3437
response
3538
})
39+
expectAssignable<DiagnosticsChannel.RequestBodyChunkReceivedMessage>({ request, chunk: Buffer.from('') })
3640
expectAssignable<DiagnosticsChannel.RequestTrailersMessage>({
3741
request,
3842
trailers: [Buffer.from(''), Buffer.from('')]

types/diagnostics-channel.d.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,16 @@ declare namespace DiagnosticsChannel {
3131
export interface RequestBodySentMessage {
3232
request: Request;
3333
}
34+
35+
type BufferLike = string | Buffer | Uint8Array | ArrayBuffer | ArrayBufferView
36+
export interface RequestBodyChunkSentMessage {
37+
request: Request;
38+
chunk: BufferLike;
39+
}
40+
export interface RequestBodyChunkReceivedMessage {
41+
request: Request;
42+
chunk: Buffer;
43+
}
3444
export interface RequestHeadersMessage {
3545
request: Request;
3646
response: Response;

0 commit comments

Comments
 (0)